Como projetar o software de um jogo para facilitar o teste de unidade?

25

É prático usar uma estrutura de teste como o JUnit em uma situação de desenvolvimento de jogos? Que tipo de considerações de design você pode seguir para tornar seu jogo mais testável? Quais partes de um jogo podem / devem ser testadas e quais partes devem / devem ser deixadas para testes em humanos?

Por exemplo, se o loop do jogo estiver encapsulado em uma função, parece que seria terrivelmente difícil de testar. Eu gosto de refatorar uma função de "atualização" que leva um tempo delta e avança a lógica do jogo; isso permite alguns truques interessantes, como a capacidade de desacelerar o jogo, alimentando deltas falsos e mais lentos.

Ricket
fonte
6
Por que desperdiçar horas-homem escrevendo testes de unidade quando você tem um exército praticamente infinito de trabalho escravo para fazer testes para você? Eu brinco, eu brinco ...;)
Mike Strobel
11
Na verdade você não precisa brincar, esse é um ponto muito bom! Eu não pensei nisso, mas os jogos são o tipo mais fácil de software para que outras pessoas testem. Eu acho que isso compensa um pouco a dificuldade dos testes automatizados de jogos (unitários ou não).
Ricket
3
O teste de unidade não é necessariamente garantir que seu aplicativo funcione corretamente como um todo (isto é, teste funcional). É mais uma questão de garantir que alterações futuras não quebrem a funcionalidade existente. Certamente, um usuário pode ver quando a nave está de cabeça para baixo, mas quando o algoritmo de desvio está desativado em 0,001, os efeitos podem não ser aparentes até que o jogo seja jogado por 200 horas. Esse é o tipo de coisa que os testes de unidade podem detectar antes que o código saia pela porta.
Wade Williams
11
Os jogos são o software mais fácil para os usuários jogarem , mas jogando! = Testando . Obter bons relatórios de erros é bastante difícil. Além disso, rastrear onde ocorre um erro no código e verificar se as novas alterações não quebram o código existente se beneficiam bastante dos testes automatizados.
munificente

Respostas:

25

Um dos princípios do TDD é que você permite que o TDD, em alguns casos, influencie seu design. Você escreve um teste para o sistema, depois escreve o código para fazer esse teste passar, mantenha as dependências o mais superficial possível.

Para mim, existem apenas duas coisas que não testo como parte do teste de unidade:

Primeiro, não testo elementos visuais e como as coisas se parecem. Eu testei que e o objeto estará no lugar certo após a atualização, que uma câmera selecionará um objeto fora dos limites, que as transformações (pelo menos as que são feitas fora dos shaders) são executadas corretamente antes de serem entregues ao mecanismo gráfico , mas assim que atinge o sistema gráfico, eu traço a linha. Não gosto de zombar de coisas como o DirectX.

Segundo, eu realmente não testei a função principal do loop do jogo. Testo que todo sistema funcionará quando for aprovado em um delta razoável e que os sistemas funcionem juntos corretamente quando necessário. Depois, atualizo cada sistema com o delta correto no loop do jogo. Na verdade, eu poderia fazer um teste para mostrar que cada sistema foi chamado com o delta correto, mas em muitos casos eu acho esse exagero (a menos que você esteja fazendo uma lógica complexa para obtê-lo, então não há exagero).

Jeff
fonte
6
O primeiro parágrafo é a chave. O fato é que o TDD exige que você projete seu código melhor do que seria sem ele, simplesmente para permitir o teste. É claro que muitos pensam que eles são especialistas em design, mas é claro que os mais impressionado com suas próprias habilidades são geralmente aqueles com o mais para aprender ...
traço-tom-bang
2
Eu não concordaria que o código seja necessariamente projetado melhor como resultado. Eu já vi muitas abominações em que uma interface anteriormente limpa tinha que ser quebrada e o encapsulamento quebrado para injetar dependências testáveis.
Kylotan
4
Acho que isso acontece mais nos casos em que os testes são escritos para classes pré-existentes, e não o contrário.
21410 Jeff
@ Kylotan, que provavelmente é mais um sintoma de design inadequado / design de teste ruim. Refatore e use zombarias para fazer testes limpos, não deixe seu código em um estado pior; é o contrário do que deveria acontecer.
timoxley
2
O encapsulamento é uma coisa boa, mas o mais importante é entender POR QUE é uma coisa boa. Se você apenas usá-lo para ocultar dependências e uma interface grande e feia, ele praticamente grita que seu projeto provavelmente não é muito bom. O encapsulamento não se trata principalmente de ocultar códigos e dependências feias, mas principalmente de facilitar a compreensão do código e permitir que o consumidor obtenha menos caminhos a seguir ao tentar raciocinar sobre o código.
Martin Odhelius
19

Noel Llopis cobriu os testes unitários na medida em que acredito que você está procurando:

Com relação ao teste de todo o ciclo do jogo, há outra técnica para impedir regressões funcionais no seu código. A idéia é fazer com que seu jogo seja reproduzido por meio de um sistema de repetição (por exemplo, primeiro a gravação das entradas dos jogadores e, em seguida, reproduzi-las em uma execução diferente). Durante a reprodução, você gera um instantâneo do estado de cada objeto para cada quadro. Essa coleção de estados pode então ser chamada de "imagem de ouro". Ao refatorar seu código, você executa a reprodução novamente e compara o estado de cada quadro ao estado de imagem dourada. Se for diferente, o teste falhou.

Embora os benefícios dessa técnica sejam óbvios, há algumas coisas com as quais você precisa ter cuidado:

  • Seu jogo precisa ser determinístico; portanto, você deve ter cuidado com coisas como, dependendo do relógio do sistema, eventos do sistema / rede, geradores de números aleatórios que não são deterministicamente propagados. etc.
  • alterações de conteúdo após a geração da imagem dourada podem causar diferenças legítimas com sua imagem dourada. Não há como contornar isso - ao alterar seu conteúdo, você precisa regenerar sua imagem dourada.
jpaver
fonte
+1 Também vale a pena notar que o valor dessa abordagem está diretamente relacionado ao comprimento da "imagem de ouro": se não for longo o suficiente, você poderá facilmente perder bugs; se for muito longo, você terá muito o que esperar. É importante tentar fazer com que o jogo rode talvez uma ordem de magnitude mais rápida nesse modo, do que você faria em circunstâncias normais de tempo de execução, para economizar tempo. Ignorar a renderização pode ajudar!
Engenheiro de
9

Concordo com os comentários de Jeff e jpaver. Eu também queria acrescentar que a adoção de um modelo de componente para sua arquitetura aumenta muito a testabilidade. Com um modelo de componente, cada componente deve estar executando uma única unidade de trabalho e deve ser testado isoladamente (ou com objetos simulados limitados).

Da mesma forma, as outras partes do jogo que dependem de entidades devem confiar apenas em um subconjunto dos componentes para funcionar. Na prática, isso significa que você geralmente pode simular alguns componentes falsos para facilitar o teste dessas áreas ou simplesmente compor entidades parciais para fins de teste. por exemplo, você deixa de fora os componentes de renderização e entrada porque eles não são necessários para testar a física.

Por fim, eu ignoraria os conselhos sobre desempenho que você recebe de algumas pessoas da comunidade de jogos sobre interfaces. Escreva o código bem com interfaces e, se tiver problemas de desempenho no futuro, poderá criar um perfil, identificar e refatorar facilmente para resolver o problema. Não fiquei convencido pelas preocupações de Noel sobre o impacto no desempenho das interfaces ou a sobrecarga de complexidade que elas adicionaram.

Dito isto, não exagere tentando testar cada coisinha de forma independente. Como a maioria das coisas, o teste e o design têm como objetivo encontrar o equilíbrio certo.

Alex Schearer
fonte
Observe que, embora você não pareça ser novo no SX, essas respostas não são ordenadas e dizer algo como "ambos os comentários acima" provavelmente será preterido rapidamente, pois atualmente é agora que você está com um voto à frente de um das duas outras respostas no momento. :)
Ricket 18/08/10
Obrigado, eu não pensei nisso enquanto comentava. Desde então, atualizei o comentário para escolher os comentários individuais.
Alex Schearer
3

Pensei em adicionar uma segunda resposta, respondendo ao comentário do OP de que o usuário poderia substituir os testes de unidade. Acredito que isso esteja completamente errado, pois o objetivo dos testes de unidade não é garantir a qualidade. Se você deseja realizar testes para garantir a qualidade do seu programa, provavelmente deve investigar testes de cenário ou investir em um ótimo monitoramento.

(Com um ótimo monitoramento e um produto em execução como serviço, é realmente possível enviar alguns "testes" ao cliente, mas você precisa de um sistema sofisticado que detecte erros rapidamente e reverta as alterações responsáveis. Provavelmente isso é demais para uma pequena equipe de desenvolvimento.)

O principal benefício dos testes de unidade é que eles forçam o desenvolvedor a escrever um código melhor projetado. O ato de testar desafia o desenvolvedor a separar preocupações e encapsular dados. Também incentiva o desenvolvedor a usar interfaces e outras abstrações para que ele teste apenas uma coisa.

Alex Schearer
fonte
Concordo, exceto que os testes de unidade não forçam os desenvolvedores a escrever um bom código. Há outra opção: testes muito mal escritos. Você precisa se comprometer com a idéia de testes de unidade de seus codificadores para impedir que eles fiquem cansados.
tenpn
2
Claro, mas isso não é motivo para jogar o bebê fora com a água do banho. Use as ferramentas com responsabilidade, mas primeiro use as ferramentas.
Alex Schearer