GameDev TDD ou Rabbit Hell

TDD raramente é usado no desenvolvimento de jogos. Geralmente, é mais fácil contratar um testador do que reservar um desenvolvedor para escrever testes - isso economiza recursos e tempo. Portanto, cada uso bem-sucedido do TDD se torna mais interessante. Sob o corte, a tradução do material, onde essa técnica de desenvolvimento foi usada para criar o movimento de personagens no jogo ElemenTerra.



O desenvolvimento orientado a testes ou TDD (desenvolvimento através de testes) é uma técnica de desenvolvimento de software na qual todo o processo é dividido em muitos pequenos ciclos. Os testes de unidade são gravados, o código que passa nesses testes e a refatoração é feita. E o algoritmo se repete.

TDD Basics


Suponha que escrevamos uma função que adicione dois números. Em um fluxo de trabalho normal, basta escrevê-lo. Mas, para usar o TDD, é necessário começar criando uma função de espaço reservado e testes de unidade:

// 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; } 

Inicialmente, nossos testes de unidade não funcionarão, porque a função de espaço reservado retorna -1 para cada entrada. Agora podemos executar add corretamente para retornar a + b . Os testes serão aprovados. Isso pode parecer uma solução alternativa, mas há várias vantagens:

Se escrevermos add por a-b por engano, nossos testes não funcionarão e aprenderemos imediatamente como consertar a função. Sem testes, não podemos detectar esse erro e ver uma reação não padrão que levará tempo para depurar.
Podemos continuar os testes e executá-los a qualquer momento enquanto escrevemos o código. Isso significa que, se outro programador mudar acidentalmente, ele reconhecerá imediatamente o erro - os testes falharão novamente.

TDD no jogo dev


Existem dois problemas com o TDD no game dev. Em primeiro lugar, muitas funções do jogo têm objetivos subjetivos que não podem ser medidos. Em segundo lugar, é difícil escrever testes que abranjam todas as possibilidades do espaço de mundos cheios de objetos complexos em interação. Os desenvolvedores que desejam que os movimentos de seus personagens “pareçam bem” ou simulações físicas “não pareçam espasmódicos” terão dificuldade em expressar essas métricas como condições determinísticas “passadas / não passadas”.

No entanto, a técnica TDD é aplicável a características complexas e subjetivas - por exemplo, movimento de caracteres. E no jogo ElemenTerra nós fizemos.

Testes de unidade em relação aos níveis de depuração


Antes de começar a praticar, quero distinguir entre um teste de unidade automático e um "nível de depuração" tradicional. Criar locais ocultos com condições artificiais é algo comum no gamedev. Isso permite que programadores e controle de qualidade monitorem eventos individuais.


Nível de depuração secreto em The Legend of Zelda: The Wind Waker

O ElemenTerra possui muitos níveis: um nível cheio de geometria problemática para o personagem de um jogador, níveis com interfaces de usuário especiais que acionam determinados estados do jogo e outros.

Como testes de unidade, esses níveis de depuração podem ser usados ​​para reproduzir e diagnosticar erros. Mas de certa forma eles diferem:

Os testes de unidade dividem os sistemas em partes e avaliam cada um individualmente, enquanto os níveis de depuração conduzem os testes de maneira mais holística. Depois de encontrar o erro no nível de depuração, os desenvolvedores ainda podem precisar procurar manualmente o ponto de erro.
Os testes de unidade são automatizados e devem sempre fornecer resultados determinísticos, enquanto muitos níveis de depuração são "controlados" pelo jogador. Isso faz a diferença nas sessões.

Mas isso não significa que os testes de unidade sejam melhores que os níveis de depuração. Estes últimos costumam ser mais práticos. No entanto, o teste de unidade pode até ser usado em sistemas onde tradicionalmente não existe.

Bem-vindo ao inferno do coelho


No ElemenTerra, os jogadores usam as forças místicas da natureza para salvar criaturas afetadas por uma tempestade espacial. Uma dessas forças é a capacidade de pavimentar o caminho que leva as criaturas a alimento e abrigo. Como esses caminhos são grades dinâmicas criadas pelos jogadores, o movimento da criatura deve lidar com casos geométricos incomuns e um terreno arbitrariamente complexo.

O movimento do personagem é um daqueles sistemas complexos em que "tudo afeta todo o resto". Se você já fez isso, sabe que, ao escrever um novo código, é muito fácil quebrar a funcionalidade existente. Você precisa de coelhos para escalar pequenas saliências? Ok, mas agora eles estão tremendo, subindo as encostas. Deseja que os caminhos dos lagartos não se cruzem? Funcionou, mas agora seu comportamento típico está arruinado.

Como responsável pela IA e pela maior parte do código de jogo, eu sabia que não tinha tempo para erros de surpresa. Eu queria perceber imediatamente a regressão, então o desenvolvimento usando TDD me pareceu uma boa opção.

O próximo passo foi a criação de um sistema no qual eu pudesse identificar facilmente cada caso de movimento na forma de um teste simulado de aprovação / reprovação:



Esse "inferno dos coelhos" consiste em 18 corredores isolados. Cada uma com uma criatura e sua própria rota, projetada para se mover apenas se uma determinada função de movimento funcionar. Os testes são considerados bem-sucedidos se o coelho for capaz de se mover por um tempo infinitamente longo sem ficar preso. Caso contrário, sem êxito. Note que apenas testamos o corpo das criaturas (peão em termos irreais), não a inteligência artificial. No ElemenTerra, as criaturas podem comer, dormir e reagir ao mundo, mas no "inferno dos coelhos", sua única instrução é correr entre dois pontos.

Aqui estão alguns exemplos de tais testes:


1, 2, 3: Livre circulação, obstáculos estáticos e obstáculos dinâmicos


8 e 9: Inclinações uniformes e terrenos acidentados


10: Piso desaparecendo


13: Reprodução de um bug no qual criaturas giravam sem parar em torno de alvos próximos


14 e 15: Capacidade de navegar por saliências planas e complexas

Vamos falar sobre as semelhanças e diferenças entre minha implementação e o TDD "limpo".

Meu sistema era semelhante ao TDD nisso:

  • Comecei a trabalhar nas funções criando testes e depois escrevi o código necessário para executá-las.
  • Continuei a executar testes antigos, adicionando novos recursos.
  • Cada teste mediu exatamente uma parte do sistema, o que me permitiu encontrar rapidamente problemas.
  • Os testes foram automatizados e não exigiram a entrada do jogador.

E diferia nisso:

  • Ao avaliar os testes, houve um elemento de subjetividade. Enquanto os verdadeiros erros de se mover (o personagem não passou de A para B) puderam ser detectados programaticamente. Isto é, por exemplo, uma posição distorcida, problemas de sincronização da animação e movimento de contração exigem uma avaliação humana.
  • Os testes não foram completamente determinísticos. Fatores aleatórios, como flutuações na taxa de quadros, causaram pequenos desvios. Mas, em geral, as criaturas geralmente seguem os mesmos caminhos e têm o mesmo sucesso / fracasso entre as sessões.

Limitações


Usar o TDD para mover uma criatura ElemenTerra foi uma grande vantagem, mas minha abordagem tinha várias limitações:

  • Os testes de unidade avaliaram cada característica do movimento individualmente, portanto, erros com combinações de várias características não foram considerados. Às vezes, era necessário complementar os testes de unidade com os níveis tradicionais de depuração.
  • O ElemenTerra tem quatro tipos de criaturas, mas os testes contêm apenas coelhos. Esse é um recurso do nosso cronograma de produção (os outros três tipos foram adicionados muito mais tarde no desenvolvimento). Felizmente, todos os quatro têm a mesma mobilidade, mas o corpo grande de Mossmork causou vários problemas. Na próxima vez, eu teria que fazer testes dinamicamente nas espécies selecionadas, em vez de usar coelhos pré-colocados.


Este Mossmork requer um pouco mais de espaço do que um coelho.

TDD é sua escolha?


Os desenvolvedores podem gastar muito esforço em níveis de teste de unidade que o player nunca apreciará. Eu não nego, eu mesmo recebi muito prazer ao criar o “inferno dos coelhos”. Tais funções internas podem consumir tempo e comprometer marcos mais importantes. Para evitar que isso aconteça, estude cuidadosamente onde e quando usar testes de unidade. Abaixo, destaquei vários critérios que justificam o TDD para o movimento de uma criatura ElemenTerra.

1. Levará muito tempo para concluir manualmente as tarefas de teste?

Antes de gastar tempo em testes automatizados, é necessário verificar se podemos avaliar a função usando controles de jogo convencionais. Se você quiser ter certeza de que suas chaves destrancam as portas, solte a chave e abra a porta para elas. Criar testes de unidade para esta função seria uma perda de tempo - o teste manual leva apenas alguns segundos.

2. É difícil criar casos de teste manualmente?

Os testes de unidade automatizados são justificados quando há casos conhecidos e difíceis de reproduzir. O teste n ° 7 do “inferno dos coelhos” verifica como eles andam ao longo das bordas - algo que a IA geralmente tenta evitar. Essa situação pode ser difícil ou impossível de reproduzir usando os controles do jogo, e os testes são fáceis.

3. Você sabia que os resultados desejados não serão alterados?

O design do jogo é inteiramente baseado em iterações, para que os objetivos dos recursos possam mudar à medida que o jogo muda. Mesmo pequenas alterações podem invalidar as métricas pelas quais você avalia seus recursos e, portanto, qualquer teste de unidade. Se o comportamento das criaturas durante a comida, o sono e a interação com o jogador mudaram várias vezes, a transição do ponto A para o ponto B permaneceu inalterada. Portanto, o código de movimento e seus testes de unidade permaneceram relevantes durante todo o desenvolvimento.

4. É provável que as regressões passem despercebidas?

Você teve uma situação ao concluir uma das últimas tarefas antes de enviar o jogo e, de repente, encontrou um erro que quebra as regras? E na função que você terminou há muitos anos. Os jogos são sistemas interconectados gigantescos e, portanto, é natural que a adição de uma nova função B possa levar à falha da antiga função A.

Não é tão ruim quando uma função quebrada é usada em qualquer lugar (por exemplo, um salto) - você deve notar imediatamente uma falha na mecânica. Erros descobertos em um desenvolvimento posterior podem atrapalhar a programação e, após o lançamento, podem prejudicar a jogabilidade.

5. O pior que pode acontecer ao usar testes e sem eles?

Criar testes é uma forma de gerenciamento de riscos. Imagine que você decide comprar um seguro de veículo. Você precisa responder três perguntas:

  • Quanto custam os prêmios mensais de seguro?
  • Qual a probabilidade de o carro ser danificado?
  • Quão caro seria o pior cenário se você não estivesse segurado?

Para o TDD, podemos imaginar contribuições mensais na forma de custos de produção para a manutenção de nossos testes de unidade, a probabilidade de danos ao carro na forma da probabilidade de um bug e o custo de uma substituição completa do carro como o pior cenário para um erro de regressão.

Se demorar muito tempo para criar um teste de recurso, é simples e improvável que seja alterado (ou pode ser resolvido se for interrompido no desenvolvimento posterior), os testes de unidade podem causar mais problemas do que benefícios. Se os testes são fáceis de executar, a função é instável e interconectada (ou seus erros levarão muito tempo), então os testes ajudarão.

Limites de automação


Os testes de unidade podem ser um ótimo complemento para encontrar e eliminar erros, mas não substituem a necessidade de controle de qualidade profissional em jogos de larga escala. O controle de qualidade é uma arte que requer criatividade, julgamento subjetivo e excelente comunicação técnica.

Source: https://habr.com/ru/post/pt455076/


All Articles