TDD rara vez se usa en el desarrollo de juegos. Por lo general, es más fácil contratar a un probador que reservar un desarrollador para que escriba las pruebas; esto ahorra recursos y tiempo. Por lo tanto, cada uso exitoso de TDD se vuelve más interesante. Debajo del corte, la traducción del material, donde se utilizó esta técnica de desarrollo para crear el movimiento de los personajes en el juego ElemenTerra.

El desarrollo basado en pruebas o TDD (desarrollo a través de pruebas) es una técnica de desarrollo de software en la que todo el proceso se divide en muchos ciclos pequeños. Las pruebas unitarias se escriben, luego se escribe el código que pasa estas pruebas y luego se realiza la refactorización. Y el algoritmo se repite.
Conceptos básicos de TDD
Supongamos que escribimos una función que suma dos números. En un flujo de trabajo normal, simplemente lo escribiríamos. Pero para usar TDD, debe comenzar creando una función de marcador de posición y pruebas unitarias:
// Placeholder-, : int add(int a, int b){ return -1; } // Unit-, , add : void runTests(){ if (add(1, 1) is not equal to 2) throw error; if (add(2, 2) is not equal to 4) throw error; }
Al principio, nuestras pruebas unitarias no funcionarán, porque la función de marcador de posición devuelve -1 para cada entrada. Ahora podemos ejecutar
add correctamente para devolver
a + b . Se aprobarán las pruebas. Esto puede parecer una solución, pero hay varias ventajas:
Si escribimos erróneamente
add como
a - b , nuestras pruebas no funcionarán e inmediatamente aprenderemos cómo solucionar la función. Sin pruebas, no podemos detectar este error y ver una reacción no estándar que llevará tiempo depurar.
Podemos continuar con las pruebas y ejecutarlas en cualquier momento mientras escribimos el código. Esto significa que si otro programador cambia accidentalmente
agregar , reconocerá inmediatamente el error; las pruebas fallarán nuevamente.
TDD en desarrollo de juegos
Hay dos problemas con TDD en el desarrollo del juego. En primer lugar, muchas funciones del juego tienen objetivos subjetivos que no se pueden medir. Y en segundo lugar, es difícil escribir pruebas que cubran todas las posibilidades del espacio de mundos que están llenos de objetos interactivos complejos. Los desarrolladores que desean que los movimientos de sus personajes se "vean bien" o que las simulaciones físicas "no se vean desiguales" tendrán dificultades para expresar estas métricas como condiciones deterministas de "aprobado / no aprobado".
Sin embargo, la técnica TDD es aplicable a características complejas y subjetivas, por ejemplo, el movimiento del personaje. Y en el juego ElemenTerra lo hicimos.
Pruebas unitarias contra niveles de depuración
Antes de comenzar a practicar, quiero distinguir entre una prueba unitaria automática y un "nivel de depuración" tradicional. Crear ubicaciones ocultas con condiciones artificiales es algo común en gamedev. Esto permite a los programadores y QA monitorear eventos individuales.
Nivel de depuración secreto en The Legend of Zelda: The Wind WakerElemenTerra tiene muchos niveles: un nivel lleno de geometría problemática para el personaje de un jugador, niveles con interfaces de usuario especiales que activan ciertos estados del juego y otros.
Al igual que las pruebas unitarias, estos niveles de depuración se pueden usar para reproducir y diagnosticar errores. Pero en algunos aspectos difieren:
Las pruebas unitarias dividen los sistemas en partes y evalúan cada uno individualmente, mientras que los niveles de depuración llevan a cabo las pruebas de una manera más integral. Después de encontrar el error en el nivel de depuración, los desarrolladores aún pueden necesitar buscar manualmente el punto de error.
Las pruebas unitarias son automáticas y cada vez deben dar resultados deterministas, mientras que muchos niveles de depuración son "controlados" por el jugador. Esto hace la diferencia en las sesiones.
Pero esto no significa que las pruebas unitarias sean mejores que los niveles de depuración. Estos últimos son a menudo más prácticos. Sin embargo, las pruebas unitarias pueden incluso usarse en sistemas donde tradicionalmente no ha estado presente.
Bienvenido al infierno del conejo
En ElemenTerra, los jugadores usan las fuerzas místicas de la naturaleza para salvar a las criaturas afectadas por una tormenta espacial. Una de esas fuerzas es la capacidad de allanar el camino que lleva a las criaturas a la comida y al refugio. Dado que estos caminos son cuadrículas dinámicas creadas por los jugadores, el movimiento de la criatura debe lidiar con casos geométricos inusuales y un terreno arbitrariamente complejo.
El movimiento del personaje es uno de esos sistemas complejos donde "todo afecta a todo lo demás". Si alguna vez ha hecho esto, sabe que al escribir código nuevo, es muy fácil romper la funcionalidad existente. ¿Necesitas conejos para escalar pequeñas repisas? Está bien, pero ahora están temblando, subiendo las laderas. ¿Quieres que los caminos de lagarto no se crucen? Funcionó, pero ahora su comportamiento típico está arruinado.
Como la persona responsable de la IA y la mayoría del código de juego, sabía que no tenía tiempo para errores sorpresa. Quería notar de inmediato la regresión, por lo que el desarrollo con TDD me pareció una buena opción.
El siguiente paso fue la creación de un sistema en el que podía identificar fácilmente cada caso de movimiento en forma de una prueba de pasa / falla simulada:

Este "infierno del conejo" consiste en 18 corredores aislados. Cada uno con una criatura y su propia ruta, diseñada para moverse solo si una determinada función de movimiento funciona. Las pruebas se consideran exitosas si el conejo puede moverse durante un tiempo infinitamente largo sin atascarse. De lo contrario, sin éxito. Tenga en cuenta que solo probamos el cuerpo de las criaturas (peón en términos irreales), no la inteligencia artificial. En ElemenTerra, las criaturas pueden comer, dormir y reaccionar ante el mundo, pero en el "infierno del conejo" su única instrucción es correr entre dos puntos.
Aquí hay algunos ejemplos de tales pruebas:
1, 2, 3: movimiento libre, obstáculos estáticos y obstáculos dinámicos
8 y 9: pendientes uniformes y terreno irregular
10: piso que desaparece
13: Reproducción de un error en el que las criaturas giraban sin cesar alrededor de objetivos cercanos
14 y 15: capacidad de navegar por salientes planos y complejos
Hablemos de las similitudes y diferencias entre mi implementación y el TDD "limpio".
Mi sistema era similar a TDD en esto:
- Comencé a trabajar en funciones creando pruebas, y luego escribí el código necesario para ejecutarlas.
- Continué ejecutando pruebas antiguas, agregando nuevas características.
- Cada prueba midió exactamente una parte del sistema, lo que me permitió encontrar problemas rápidamente.
- Las pruebas fueron automatizadas y no requirieron la participación del jugador.
Y difería en esto:
- Al evaluar las pruebas, había un elemento de subjetividad. Si bien los errores reales de movimiento (el personaje no pasó de A a B) se pudieron detectar mediante programación. Es decir, por ejemplo, una posición sesgada, problemas de sincronización de animación y movimientos de contracción requirieron una evaluación humana.
- Las pruebas no fueron completamente deterministas. Factores aleatorios, como las fluctuaciones en la velocidad de fotogramas, causaron pequeñas desviaciones. Pero, en general, las criaturas suelen seguir los mismos caminos y tienen el mismo éxito / fracaso entre sesiones.
Limitaciones
Usar TDD para mover una criatura ElemenTerra fue una gran ventaja, pero mi enfoque tenía varias limitaciones:
- Las pruebas unitarias evaluaron cada característica del movimiento individualmente, por lo que no se consideraron los errores con combinaciones de varias características. A veces era necesario complementar las pruebas unitarias con los niveles de depuración tradicionales.
- ElemenTerra tiene cuatro tipos de criaturas, pero las pruebas solo contienen conejos. Esta es una característica de nuestro programa de producción (los otros tres tipos se agregaron mucho más tarde en el desarrollo). Afortunadamente, los cuatro tienen la misma movilidad, pero el gran cuerpo de Mossmork causó varios problemas. La próxima vez, las pruebas generarían dinámicamente las especies seleccionadas en lugar de usar conejos pre-colocados.
Este Mossmork requiere un poco más de espacio que un conejo.TDD es tu elección?
Los desarrolladores pueden gastar demasiado esfuerzo en niveles de prueba de unidad que el jugador nunca apreciará. No lo niego, yo mismo recibí mucho placer al crear el "infierno del conejo". Dichas funciones internas pueden llevar mucho tiempo y poner en peligro hitos más importantes. Para evitar que esto suceda, estudie cuidadosamente dónde y cuándo usar las pruebas unitarias. A continuación, he resaltado varios criterios que justifican la TDD para el movimiento de una criatura ElemenTerra.
1. ¿Tomará mucho tiempo completar manualmente las tareas de prueba?
Antes de dedicar tiempo a las pruebas automatizadas, debe verificar si podemos evaluar la función utilizando los controles convencionales del juego. Si quieres asegurarte de que tus llaves abran las puertas, genera la llave y ábreles la puerta. Crear pruebas unitarias para esta función sería una pérdida de tiempo: las pruebas manuales solo demoran unos segundos.
2. ¿Es difícil crear casos de prueba manualmente?
Las pruebas unitarias automatizadas se justifican cuando hay casos conocidos y difíciles de reproducir. La prueba n. ° 7 del "infierno del conejo" verifica cómo caminan a lo largo de las repisas, algo que la IA generalmente trata de evitar. Tal situación puede ser difícil o imposible de reproducir usando los controles del juego, y las pruebas son fáciles.
3. ¿Sabes que los resultados deseados no cambiarán?
El diseño del juego se basa completamente en iteraciones, por lo que los objetivos de las características pueden cambiar a medida que cambia el juego. Incluso pequeños cambios pueden invalidar las métricas mediante las cuales evalúa sus características y, por lo tanto, cualquier prueba unitaria. Si el comportamiento de las criaturas durante la comida, el sueño y la interacción con el jugador cambiaron varias veces, la transición del punto A al punto B se mantuvo sin cambios. Por lo tanto, el código de movimiento y sus pruebas unitarias siguieron siendo relevantes durante todo el desarrollo.
4. ¿Es probable que las regresiones pasen desapercibidas?
¿Tuviste una situación cuando completaste una de las últimas tareas antes de enviar el juego, y de repente encuentras un error que rompe las reglas? Y en la función que terminaste hace muchos años. Los juegos son sistemas interconectados gigantescos y, por lo tanto, es natural que agregar una nueva función B pueda conducir al fallo de la antigua función A.
No es tan malo cuando una función interrumpida se usa en todas partes (por ejemplo, un salto): inmediatamente debe notar una falla en la mecánica. Los errores descubiertos en un desarrollo posterior pueden interrumpir la programación y, después del lanzamiento, pueden dañar el juego.
5. ¿Lo peor que puede pasar cuando se usan pruebas y sin ellas?
Crear pruebas es una forma de gestión de riesgos. Imagine que decide si compra un seguro de vehículo. Necesita responder tres preguntas:
- ¿Cuánto cuestan las primas de seguro mensuales?
- ¿Qué posibilidades hay de que se dañe el automóvil?
- ¿Qué tan costoso sería el peor de los casos si no estuviera asegurado?
Para TDD, podemos imaginar contribuciones mensuales en forma de costos de producción para el servicio de nuestras pruebas unitarias, la probabilidad de daños al automóvil en forma de probabilidad de un error y el costo de un reemplazo completo del automóvil como el peor de los casos para un error de regresión.
Si lleva mucho tiempo crear una prueba de características, es simple y es poco probable que se cambie (o se puede solucionar si se rompe en el desarrollo posterior), entonces las pruebas unitarias pueden causar más problemas que beneficios. Si las pruebas son fáciles de realizar, la función es inestable e interconectada (o sus errores tomarán mucho tiempo), entonces las pruebas ayudarán.
Límites de automatización
Las pruebas unitarias pueden ser una gran adición para encontrar y eliminar errores, pero no reemplazan la necesidad de un control de calidad profesional en juegos a gran escala. QA es un arte que requiere creatividad, juicio subjetivo y excelente comunicación técnica.