paint-brush
Testes em Godot: como eu abordo isso pessoalmentepor@dlowl
603 leituras
603 leituras

Testes em Godot: como eu abordo isso pessoalmente

por D. Lowl4m2024/03/07
Read on Terminal Reader

Muito longo; Para ler

O Godot Unit Test (GUT) pode ser instalado a partir do AssetLib com um clique. Ele fornece uma classe que podemos estender para nossos scripts de teste. Possui uma interface agradável com a capacidade de usar o depurador de Godot e executar testes individuais. Ele pode ser executado a partir de CLI e em CI (mas tratarei disso mais tarde)
featured image - Testes em Godot: como eu abordo isso pessoalmente
D. Lowl HackerNoon profile picture

Para uma mudança de ritmo, gostaria de fazer um pequeno registro de desenvolvimento. Há algum tempo, participei de um game jam e fiz este jogo – Of Mice and Bad Choices – um pequeno jogo de quebra-cabeça, onde você coloca queijo ao redor do labirinto para atrair os ratos. Foi divertido, mas evidentemente houve algumas deficiências.


Um dos principais é que o comportamento dos ratos não é intuitivo. Os jogadores mencionaram que esperariam que os ratos fossem repelidos por um queijo desagradável, e não apenas congelados. Além disso, a implementação dessa mecânica permitiria um design de quebra-cabeça muito mais rico.


Então, acho que esta é uma boa oportunidade de como os testes automatizados podem ser feitos no Godot.

Uma ilustração de um comportamento esperado: o rato deve se afastar do queijo azul

Ferramentas de teste

Existem algumas estruturas de teste disponíveis para Godot 4, mas a que me chamou a atenção é o Godot Unit Test (GUT) . GUT é bem simples:


  • Ele pode ser instalado a partir do AssetLib com um clique.


  • Ele fornece uma classe que podemos estender para nossos scripts de teste: basta adicionar funções começando com test_ e escrever algumas afirmações – estrutura típica de teste unitário.


  • Possui uma interface agradável com a capacidade de usar o depurador de Godot e executar testes individuais.


  • Ele pode ser executado na CLI e no CI (mas tratarei disso mais tarde).

Minha estrutura de teste

Para este caso em particular, queria ter uma forma de definir cenários complexos, da mesma forma que defino os níveis do jogo – no editor do motor e não no código (desta forma, os testes ficariam mais próximos da realidade). Portanto, quero fazer estas coisas:


  • Tenha uma única função de executor que pega um mapa e executa os testes.


  • Tenha uma coleção de mapas, cada um com um conjunto de cenários (casos de teste) para executar.


  • Tenha uma maneira de definir casos de teste arrastando e soltando: coloque o mouse e defina onde ele deve estar em N turnos.


Então, vamos desembrulhar isso.

Definições de casos de teste

Vamos definir uma nova classe `MouseTestCase.` Queremos que ele herde Node2D (como queremos colocá-lo em uma cena. E queremos que ele encontre um de seus filhos (que nós mesmos colocaremos em uma cena): um mouse e sua posição final esperada (como marcador)

 extends Node2D class_name MouseTestCase @export var steps_left = 0 # How many steps to simulate @export var done = false @onready var mouse: Mouse = $Mouse @onready var expected_position = SnapUtils.get_tile_map_position($TestMarker.position)


Agora podemos colocar isso em cena e estamos bem! Sabemos onde um mouse começa, sabemos onde ele deve terminar e em quantos passos.

Uma árvore de nós para definir um caso de teste

É assim que fica no mapa: o mouse a ser testado em verde, o marcador de alvo em vermelho

Mapas de teste

Agora, vamos fazer mais deles e fazer um mapa para testar nosso comportamento repelente.

O mapa de teste resultante para testes mecânicos de 'repelir'

Este comportamento é um tanto complexo, portanto, queremos cobrir muitos casos ligeiramente diferentes:

  • Um rato quer se afastar do queijo que não gosta/


  • Um mouse deseja manter a direção do movimento (ou seja, evita curvas)


  • Um mouse prefere curvas à esquerda para a direita e inversões de marcha


O mapa resultante que define 12 casos de teste para cobrir esse comportamento é mostrado acima (imagine como seria tedioso codificar todas essas coordenadas no código).

Executor de teste

A única coisa que resta a fazer é a função do executor de testes. A função precisa:

  • Carregue o mapa que definimos acima.


  • Simule as etapas do jogo até que todos os casos de teste sejam concluídos.


  • Em cada etapa, itere todos os casos de teste e, caso seja concluído, verifique se a posição esperada foi alcançada.


O código é bastante simples.

 func run_level_with_mouse_test_cases(map_path: String): var level = load(map_path) map.load_level(level) var cases = MouseTestCase.cast_all_cases(get_tree().get_nodes_in_group(MouseTestCase.MTC_GROUP_NAME)) while (cases.any(func(case): return not case.done)): map.move_mice() for case in cases: if not case.done: case.steps_left -= 1 if case.steps_left == 0: case.done = true assert_eq(case.get_mouse_position(), case.expected_position, case.get_parent().name+"/"+case.name)

Imagino que isso irá evoluir, mas a implementação atual é boa o suficiente por enquanto. Eu escrevi os testes, implementei a mecânica e os testes realmente confirmaram que a mecânica foi implementada corretamente!

O painel do GUT mostrando a execução do teste bem-sucedida

Discussões

Aqui, mostrei uma maneira de abordar os testes em jogos. Obviamente, há muito mais coisas para melhorar aqui, e encorajo os leitores a pegar o código e a estrutura e adaptá-los às suas necessidades.


Como sempre, o código está disponível no GitHub: https://github.com/d-lowl/of-mice-and-bad-choices Você também pode dar uma olhada no PR específico que apresenta os testes. Para ganhar pontos extras, se alguém puder fazê-los trabalhar na CI, isso seria brilhante. Saúde.