Você está curioso para saber como o teste de unidade funciona no Unity? Não sabe ao certo o que é o teste de unidade? Se você respondeu positivamente a essas perguntas, este tutorial será útil para você. Com isso, você aprenderá o seguinte sobre o teste de unidade:
- O que é isso
- Seu benefício
- Vantagens e desvantagens
- Como funciona no Unity usando o Test Runner
- Como escrever e executar testes de unidade que serão testados
Nota : este tutorial pressupõe que você esteja familiarizado com a linguagem C # e os conceitos básicos de desenvolvimento no Unity. Se você é novo no Unity, verifique primeiro os outros tutoriais neste mecanismo .
O que é um teste de unidade?
Antes de se aprofundar no código, é importante entender claramente o que é o teste de unidade. Simplificando, o teste de unidade está testando ... unidades.
O teste de unidade (idealmente) foi projetado para testar uma unidade de código separada. A composição de uma "unidade" pode variar, mas é importante lembrar que o teste de unidade deve testar exatamente um "elemento" de cada vez.
Testes de unidade precisam ser criados para verificar se um pequeno trecho de código lógico em um cenário específico é executado exatamente como o esperado. Isso pode ser difícil de entender antes de você começar a escrever seus próprios testes de unidade, então vamos ver um exemplo:
Você escreveu um método que permite ao usuário inserir um nome. O método é escrito para que os números não sejam permitidos no nome e o próprio nome possa consistir em apenas dez ou menos caracteres. Seu método intercepta o pressionamento de tecla de cada tecla e adiciona o caractere correspondente ao campo de
name
:
public string name = "" public void UpdateNameWithCharacter(char: character) {
O que está acontecendo aqui:
- Se o caractere não for uma letra, o código sairá da função previamente e não o adicionará à string.
- Se o nome tiver dez ou mais caracteres, o código não permitirá que o usuário adicione outro caractere.
- Se essas duas verificações forem aprovadas, o código adicionará um caractere ao final do nome.
Esta unidade pode ser testada, porque é um "módulo" do trabalho realizado. Os testes de unidade
aplicam a lógica do método.
Exemplo de teste de unidade
Como escrevemos testes de unidade para o método
UpdateNameWithCharacter
?
Antes de começar a implementar esses testes de unidade, precisamos considerar cuidadosamente o que esses testes fazem e criar nomes para eles.
Dê uma olhada nos exemplos de nome de teste de unidade abaixo. Dos nomes, deve ficar claro que eles verificam:
UpdateNameDoesntAllowCharacterAddingToNameIfNameIsTenOrMoreCharactersInLength
UpdateNameAllowsLettersToBeAddedToName
UpdateNameDoesntAllowNonLettersToBeAddedToName
A partir desses nomes de métodos de teste, vemos que realmente verificamos se a "unidade" de trabalho é
UpdateNameWithCharacter
pelo método
UpdateNameWithCharacter
. Esses nomes de teste podem parecer muito longos e detalhados, mas são bons para nós.
Cada teste de unidade que você escreve faz parte de um conjunto de testes.
O conjunto de testes contém todos os testes de unidade relacionados ao grupo lógico funcional (por exemplo, "testes de unidade de batalha"). Se algum teste do kit falhar, todo o conjunto de testes falhará.
Lançamento do jogo
Abra o
projeto Crashteroids Starter (você pode baixá-lo
aqui ) e abra a cena do
jogo na pasta
Assets / RW / Scenes .
Clique em
Play para iniciar o Crashteroids e, em seguida, clique no botão
Start Game . Mova a nave espacial com as setas
esquerda e
direita do teclado.
Para disparar um raio laser, pressione a
barra de espaço . Se o feixe atingir o asteróide, a pontuação aumentará em um. Se um asteróide colidir com um navio, o navio explode e o jogo termina (com a capacidade de começar de novo).
Tente jogar um pouco e certifique-se de que após a colisão do asteróide com o navio apareça a inscrição Game Over.
Introdução ao Unity Test Runner
Agora que sabemos como o jogo funciona, é hora de escrever testes de unidade para verificar se tudo funciona como deveria. Portanto, se você (ou outra pessoa) decidir atualizar o jogo, você terá certeza de que a atualização não quebrará nada que funcionou antes.
Para escrever testes, você primeiro precisa aprender sobre o Unity Test Runner.
O Test Runner permite executar testes e verificar se eles são aprovados com êxito. Para abrir o Unity Test Runner, selecione
Janela ▸ Geral ▸ Test Runner .
Depois que o Test Runner abrir em uma nova janela, você poderá simplificar sua vida clicando na janela Test Runner e
arrastando -a para o local próximo à janela Scene.
Preparando NUnit e pastas de teste
O Test Runner é um recurso de teste de unidade fornecido pelo Unity, mas usa a estrutura do
NUnit . Quando você começar a trabalhar com testes de unidade com mais seriedade, recomendo estudar o
wiki no NUnit para saber mais. Sobre tudo o que você precisa pela primeira vez será discutido neste artigo.
Para executar os testes, primeiro precisamos criar uma pasta de teste na qual as classes de teste serão armazenadas.
Na janela
Projeto, selecione a pasta
RW . Dê uma olhada na janela
Test Runner e verifique se
PlayMode está selecionado.
Clique no botão chamado
Criar pasta de montagem de teste do PlayMode . Você verá uma nova pasta aparecer na pasta RW. Estamos satisfeitos com o nome padrão de
testes , para que você possa simplesmente pressionar
Enter .
Você pode estar se perguntando o que essas duas guias diferentes estão dentro do Test Runner.
A guia
PlayMode é usada para testes realizados no modo Play (quando o jogo está sendo executado em tempo real). Os testes na guia
EditMode são executados fora do modo Play, o que é conveniente para testar itens como comportamentos personalizados no Inspector.
Neste tutorial, abordaremos os testes do PlayMode. Mas quando você se sentir confortável, tente experimentar os testes no EditMode.
Ao trabalhar com o Test Runner neste tutorial, sempre verifique se a guia PlayMode está selecionada .
O que há na suíte de testes?
Como aprendemos acima, um teste de unidade é uma função que testa o comportamento de um pequeno pedaço específico de código. Como o teste de unidade é um método, para executá-lo, ele deve estar no arquivo de classe.
O Test Runner ignora todos os arquivos de classe de teste e executa testes de unidade a partir deles. Um arquivo de classe contendo testes de unidade é chamado de suíte de testes.
No conjunto de testes, subdividimos logicamente nossos testes. Devemos separar o código de teste em conjuntos lógicos separados (por exemplo, um conjunto de testes para a física e um conjunto separado para a batalha). Neste tutorial, precisamos de apenas um conjunto de testes e é hora de criar um.
Preparando um Conjunto de Testes e um Conjunto de Testes
Selecione a pasta
Testes e, na janela
Test Runner , clique no botão
Criar Script de Teste na pasta atual . Nome do novo arquivo
TestSuite .
Além do novo arquivo C #, o mecanismo do Unity também cria outro arquivo chamado
Tests.asmdef . Este é o
arquivo de definição de montagem , usado para mostrar ao Unity onde estão as dependências do arquivo de teste. Isso é necessário porque o código final do aplicativo está contido separadamente do código de teste.
Se você tiver uma situação em que o Unity não possa encontrar arquivos ou testes de teste, verifique se há um arquivo de definição de montagem que inclua seu conjunto de testes. O próximo passo é configurá-lo.
Para que o código de teste tenha acesso às classes de jogo, criaremos uma montagem do código de classe e definiremos o link na montagem Testes. Clique na pasta
Scripts para selecioná-la. Clique com o botão direito nessa pasta e selecione
Criar Definition Definição de montagem .
Nomeie o arquivo
GameAssembly .
Clique na pasta
Testes e, em seguida, no arquivo de definição de criação
Testes . No
Inspetor, clique no botão de
adição sob o cabeçalho
Referências de definição de montagem .
Você verá o campo
Referência ausente . Clique no
ponto ao lado deste campo para abrir a janela de seleção. Selecione o arquivo
GameAssembly .
Você deve ver o arquivo de montagem do GameAssembly na seção de links. Clique no botão
Aplicar para salvar essas alterações.
Se você não seguir estas etapas, não poderá fazer referência aos arquivos de classe do jogo dentro dos arquivos de teste de unidade. Depois de lidar com isso, você pode prosseguir para o código.
Escrevemos o primeiro teste de unidade
Clique duas vezes no script
TestSuite para abri-lo no editor de código. Substitua todo o código por este:
using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; using System.Collections; public class TestSuite { }
Quais testes precisamos escrever? Honestamente, mesmo em um jogo minúsculo como o Crashteroids, você pode escrever alguns testes para verificar se tudo está funcionando como deveria. Neste tutorial, nos restringimos a apenas áreas-chave: reconhecimento de colisão e mecânica básica de jogo.
Nota : quando se trata de escrever testes de unidade de um produto em um nível de produção, você deve dedicar tempo suficiente para levar em consideração todos os casos de fronteira que precisam ser testados em todas as áreas do código.
Como primeiro teste, é bom verificar se os asteróides estão realmente se movendo para baixo. Será difícil para eles colidirem com o navio se afastarem dele! Adicione o seguinte método e variável privada ao script
TestSuite :
private Game game;
Existem apenas algumas linhas de código, mas elas fazem muitas coisas. Então, vamos parar e lidar com cada parte:
- Este é um atributo . Os atributos definem comportamentos específicos do compilador. Este atributo informa ao compilador Unity que o código é um teste de unidade. Devido a isso, ele aparecerá no Test Runner ao iniciar os testes.
- Crie uma instância do jogo. Todo o resto está incorporado no jogo, portanto, quando o criarmos, ele conterá tudo o que precisa ser testado. Em um ambiente de produção, provavelmente todos os elementos não estarão dentro da mesma pré-fabricada. Portanto, você precisará recriar todos os objetos necessários na cena.
- Aqui criamos um asteróide para que possamos monitorar se ele está se movendo. O método
SpawnAsteroid
retorna uma instância do asteróide criado. O componente Asteroid possui um método Move
(se você estiver curioso para saber como o movimento funciona, consulte o script Asteroid em RW / Scripts ). - O rastreamento da posição inicial é necessário para garantir que o asteróide tenha descido.
- Todos os testes de unidade do Unity são corotinas, portanto, é necessário adicionar um retorno suave. Também adicionamos um intervalo de tempo de 0,1 segundos para simular a passagem de tempo pela qual o asteróide deveria descer. Se você não precisar simular uma etapa do tempo, poderá retornar nulo.
- Este é o estágio de afirmação , no qual afirmamos que a posição do asteróide é menor que a posição inicial (ou seja, ele se moveu para baixo). Compreender asserções é uma parte importante do teste de unidade, e o NUnit fornece vários métodos de asserção. Passar ou não passar no teste é determinado por esta linha.
- Obviamente, ninguém o repreenderá pela bagunça deixada após a conclusão dos testes, mas outros testes poderão falhar devido a isso. É sempre importante limpar (excluir ou redefinir) o código após o teste de unidade, para que, ao executar o próximo teste de unidade, não haja artefatos restantes que possam afetar esse teste. Basta que excluamos o objeto do jogo, pois para cada teste criamos uma instância completamente nova do jogo.
Passando nos testes
Bem, você escreveu seu primeiro teste de unidade, mas como você sabe se funciona? Claro, com o Test Runner! Na janela Test Runner, expanda todas as linhas com setas. Você deve ver o teste
AsteroidsMoveDown
na lista com círculos em cinza:
Um círculo cinza indica que o teste ainda não foi concluído. Se o teste foi iniciado e passou, uma seta verde é exibida ao lado. Se o teste falhar, um X vermelho será exibido próximo a ele. Execute o teste clicando no botão
RunAll .
Isso criará uma cena temporária e executará o teste. Após a conclusão, você deve ver que o teste foi aprovado.
Você escreveu com sucesso seu primeiro teste de unidade, declarando que os asteróides criados estão se movendo para baixo.
Nota : antes de começar a escrever seus próprios testes de unidade, você precisa entender a implementação que está testando. Se você estiver curioso para saber como a lógica que está testando está funcionando, estude o código na pasta RW / Scripts .
Usando testes de integração
Antes de avançar mais fundo na toca do coelho dos testes de unidade, é hora de dizer o que são os testes de integração e como eles diferem dos testes de unidade.
Testes de integração são testes que verificam como os "módulos" do código funcionam juntos. "Módulo" é outro termo confuso. Uma diferença importante é que os testes de integração devem testar a operação do software na produção real (ou seja, quando um jogador realmente joga um jogo).
Digamos que você fez um jogo de batalha em que um jogador mata monstros. Você pode criar um teste de integração para garantir que, quando um jogador mate 100 inimigos, uma conquista seja aberta ("Realização").
Este teste afetará vários módulos de código. O mais provável é que ele se refira ao mecanismo físico (reconhecimento de colisão), despachantes inimigos (rastreando a saúde do inimigo e processando danos, bem como passando para outros eventos relacionados) e um rastreador de eventos que rastreia todos os eventos desencadeados (por exemplo, "o monstro está morto"). Então, quando for a hora de desbloquear a conquista, ele poderá ligar para o gerente de conquista.
O teste de integração simulará o jogador matando 100 monstros e verificará se a conquista está desbloqueada. É muito diferente do teste de unidade porque testa componentes de código grandes que trabalham juntos.
Neste tutorial, não estudaremos testes de integração, mas isso deve mostrar a diferença entre a unidade de trabalho (e por que está sendo testada) e o módulo do código (e por que está sendo testada).
Adicionando um teste a um conjunto de testes
O próximo teste testará o final do jogo quando o navio colidir com um asteróide. Com o
TestSuite aberto no
editor de código, adicione o teste mostrado abaixo no primeiro teste de unidade e salve o arquivo:
[UnityTest] public IEnumerator GameOverOccursOnAsteroidCollision() { GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game")); Game game = gameGameObject.GetComponent<Game>(); GameObject asteroid = game.GetSpawner().SpawnAsteroid();
Já vimos a maior parte desse código no teste anterior, mas existem algumas diferenças:
- Forçamos o asteróide e a nave a colidir, dando claramente ao asteróide a mesma posição que a nave. Isso criará uma colisão de seus hitboxes e levará ao final do jogo. Se você estiver curioso para saber como esse código funciona, consulte os arquivos Navio , Jogo e Asteróide na pasta Scripts.
- Uma etapa de tempo é necessária para que o evento Collision do mecanismo físico seja acionado, portanto, um atraso de 0,1 segundos é retornado.
- Essa afirmação é verdadeira e verifica se o sinalizador
gameOver
no script Game é verdadeiro. A bandeira é verdadeira durante a operação do jogo, quando a nave é destruída, ou seja, testamos para garantir que ela seja verdadeira após a destruição da nave.
Volte à janela Test Runner e você verá que um novo teste de unidade apareceu lá.
Desta vez, executaremos este em vez de todo o conjunto de testes. Clique em
GameOverOccursOnAsteroidCollision e, em seguida, no botão
Executar Selecionado .
E pronto, passamos em outro teste.
Estágios de sintonia e destruição
Você deve ter notado que em nossos dois testes há um código repetido: onde o objeto Game é criado e onde um link para o script Game é definido:
GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game")); game = gameGameObject.GetComponent<Game>();
Você também notará que há uma repetição na destruição do objeto Jogo:
Object.Destroy(game.gameObject);
Ao testar isso acontece com muita frequência. Quando se trata de executar testes de unidade, na verdade existem duas fases: a fase
Setup e a fase
Tear Down .
Todo o código dentro do método Setup será executado antes do teste de unidade (neste conjunto) e todo código dentro do método Tear Down será executado após o teste de unidade (neste conjunto).
É hora de simplificar nossas vidas, movendo a configuração e o código de detalhamento para métodos especiais. Abra o editor de código e adicione o seguinte código no início do arquivo
TestSuite , logo antes do primeiro atributo [UnityTest]:
[SetUp] public void Setup() { GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game")); game = gameGameObject.GetComponent<Game>(); }
O atributo
SetUp
indica que esse método é chamado antes da
SetUp
cada teste.
Em seguida, adicione o seguinte método e salve o arquivo:
[TearDown] public void Teardown() { Object.Destroy(game.gameObject); }
O atributo
TearDown
indica que esse método é chamado após a
TearDown
cada teste.
Depois de preparar o código de configuração e destruição, exclua as linhas de código presentes nesses métodos e substitua-as por chamadas aos métodos correspondentes. Depois disso, o código ficará assim:
public class TestSuite { private Game game; [SetUp] public void Setup() { GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game")); game = gameGameObject.GetComponent<Game>(); } [TearDown] public void Teardown() { Object.Destroy(game.gameObject); } [UnityTest] public IEnumerator AsteroidsMoveDown() { GameObject asteroid = game.GetSpawner().SpawnAsteroid(); float initialYPos = asteroid.transform.position.y; yield return new WaitForSeconds(0.1f); Assert.Less(asteroid.transform.position.y, initialYPos); } [UnityTest] public IEnumerator GameOverOccursOnAsteroidCollision() { GameObject asteroid = game.GetSpawner().SpawnAsteroid(); asteroid.transform.position = game.GetShip().transform.position; yield return new WaitForSeconds(0.1f); Assert.True(game.isGameOver); } }
Teste de game over e tiro a laser
Tendo preparado os métodos de ajuste e destruição que simplificam nossas vidas, podemos começar a adicionar novos testes nos quais eles são usados. O próximo teste é verificar se, quando um jogador clica em
Novo jogo , o valor do
gameOver bool não
é verdadeiro. Adicione esse teste ao final do arquivo e salve-o:
[UnityTest] public IEnumerator NewGameRestartsGame() {
Isso já deve lhe parecer familiar, mas vale a pena mencionar o seguinte:
- Este trecho de código prepara esse teste para que o
gameOver
booleano gameOver
seja verdadeiro. Ao chamar o método NewGame
, ele deve novamente definir o sinalizador como false
. - Aqui argumentamos que o bool
isGameOver
é false
, o que deve ser verdadeiro ao invocar um novo jogo.
Retorne ao Test Runner e você verá que há um novo teste
NewGameRestartsGame . Execute este teste, como fizemos anteriormente, e você verá que ele é executado com êxito:
Declaração do raio laser
O próximo teste é adicionar o teste de que o raio laser disparado pelo navio está voando (semelhante ao primeiro teste de unidade que escrevemos). Abra o arquivo
TestSuite no editor. Adicione o seguinte método e salve o arquivo:
[UnityTest] public IEnumerator LaserMovesUp() {
Aqui está o que esse código faz:
- Obtém um link para o raio laser gerado emitido pelo navio.
- A posição inicial é registrada para que possamos verificar se está subindo.
- Essa afirmação é consistente com a afirmação do teste de unidade
AsteroidsMoveDown
, mas agora afirmamos que o valor é maior (ou seja, o laser sobe).
Test Runner.
LaserMovesUp :
, , .
,
, .
TestSuite , :
[UnityTest] public IEnumerator LaserDestroysAsteroid() {
:
- , .
- . , UnityEngine.Assertions ? , Unity Null , «» Null. NUnit
Assert.IsNull()
Unity null. null Unity, UnityEngine.Assertions.Assert, Assert NUnit.
Test Runner . .
—
- — , . . ,
(Test Driven Development, TDD).
TDD, . , , , , . , , .
, . -, , .
: — , . , . «» , , . , . , . , , .
, - :
-
- , :
-
-. :
- , .
- .
- .
- , .
- , -.
- ( ), .
- - .
- UI .
- .
- .
,
. ,
TestSuite :
[UnityTest] public IEnumerator DestroyedAsteroidRaisesScore() {
, , , . :
- , . , .
- , game.score 1 ( 0, ).
Test Runner, , :
Awesome! .
Para onde ir a seguir?
. ,
, .
, - Unity. , -, , -.
? . - . :
-,
mock- . .
NUnit , NUnit.
.
!