O Ciclo TDD: Do Teste Falhado ao Código Funcional
O processo TDD segue um fluxo iterativo conhecido como Red-Green-Refactor:
- Verde (Green): O teste passa após a implementação da funcionalidade.
- Verde (Red): Inicialmente, o teste falha porque a funcionalidade ainda não existe.
- Reestruturação (Refactor): Melhorias no código sem alterar o comportamento.
No FlappySwift, os testes estão localizados em FlappyBirdTests/FlappyBirdTests.swift. Um exemplo básico mostra como usar XCTAssert para validações simples:
func testExample() {
XCTAssert(true, "Este teste deve passar")
}
func testPerformanceExample() {
self.measure {
// Código a ser medido
}
}
Testes Estratégicos para Mecanismos de Jogo
Os principais sistemas do jogo incluem:
- Sistema de física do pássaro
- Geração e detecção de colisão com tubos
- Sistema de pontuação
- Gerenciamento de estado do jogo
Para esses componentes, adotamos três tipos de testes:
- Testes unitários: Validam funções isoladas, como incremento de pontuação.
- Testes de integração: Verificam interações entre módulos, como contato entre objeto e tubo.
- Testes de desempenho: Avaliam eficiência em operações críticas, como spawn de tubos.
Caso Prático: Implementação da Pontuação com TDD
A lógica de pontuação é acionada quando o pássaro atravessa um tubo, detectado pelo método didBegin(_ contact:) no GameScene. Vamos construir esse sistema passo a passo.
Etapa 1: Escrever o Teste que Falha
Adicione um novo teste para verificar se a pontuação aumenta corretamente:
func testScoreIncreasesWhenBirdPassesPipe() {
let sceneSize = CGSize(width: 320, height: 480)
let gameScene = GameScene(size: sceneSize)
gameScene.didMove(to: nil)
let initialScore = gameScene.score
// Criar nós simulados
let birdBody = SKPhysicsBody(rectangleOf: CGSize(width: 10, height: 10))
birdBody.categoryBitMask = gameScene.birdCategory
let scoreZone = SKPhysicsBody(rectangleOf: CGSize(width: 10, height: 10))
scoreZone.categoryBitMask = gameScene.scoreCategory
let contact = SKPhysicsContact()
contact.bodyA = birdBody
contact.bodyB = scoreZone
gameScene.didBegin(contact)
XCTAssertEqual(gameScene.score, initialScore + 1, "Pontuação deveria aumentar em 1")
}
Etapa 2: Preparar o Ambiente de Teste
Garanta que cada teste tenha um cenário limpo e consistente:
var gameScene: GameScene!
override func setUp() {
super.setUp()
let size = CGSize(width: 320, height: 480)
gameScene = GameScene(size: size)
gameScene.didMove(to: nil)
}
override func tearDown() {
gameScene = nil
super.tearDown()
}
Etapa 3: Implementar a Funcionalidade
Na classe GameScene, certifique-se de que a propriedade score seja inicializada corretamente:
var score = 0
// Em didMove(to view:)
scoreLabelNode.text = "0"
Depois de ajustar a lógica de detecção de contato, execute novamente o teste. Ele agora deve passar — estamos no estágio verde.
Etapa 4: Refatorar para Clareza e Manutenibilidade
Extraia a lógica de atualização de pontuação em um método dedicado:
func increaseScore() {
score += 1
scoreLabelNode.text = String(score)
scoreLabelNode.run(SKAction.sequence([
SKAction.scale(to: 1.5, duration: 0.1),
SKAction.scale(to: 1.0, duration: 0.1)
]))
}
// Chamado dentro de didBegin
increaseScore()
Após refatorar, execute todos os testes para conifrmar que o comportamento permanece inalterado.
Validando a Física do Mundo com Testes
O mundo físico no SpriteKit é configurado em didMove(to view:):
self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -5.0)
self.physicsWorld.contactDelegate = self
Um teste para validar essa configuração:
func testPhysicsGravityConfiguration() {
XCTAssertEqual(gameScene.physicsWorld.gravity.dx, 0.0, "Gravidade X deve ser zero")
XCTAssertEqual(gameScene.physicsWorld.gravity.dy, -5.0, "Gravidade Y deve ser -5.0")
XCTAssertNotNil(gameScene.physicsWorld.contactDelegate, "O delegado de contato deve estar definido")
}
Teste de Desempenho na Geração de Tubos
A criação contínua de tubos impacta diretamente a fluidez do jogo. Use o measure para avaliar o tempo de execução:
func testPipeSpawnPerformance() {
self.measure {
gameScene.spawnPipes()
}
}
O resultado apresentado pelo Xcode inclui média e desvio padrão, permitindo identificar gargalos. Para melhorar, utilize texturas pré-carregadas com filtro mais leve:
pipeTextureUp = SKTexture(imageNamed: "PipeUp")
pipeTextureUp.filteringMode = .nearest
pipeTextureDown = SKTexture(imageNamed: "PipeDown")
pipeTextureDown.filteringMode = .nearest
Benefícios e Desafios do TDD em Jogos
Aplicar TDD no FlappySwift traz vantagens claras:
- Qualidade de código: Maior cobertura de testes reduz erros regressivos.
- Documentação viva: Testes funcionam como exemplos práticos de uso.
- Confiança na refatoração: Mudanças seguras com garantia de funcionamento.
- Design mais limpo: Promove modularidade e baixo acoplamento.
Contudo, há desafios específicos:
- Elementos aleatórios: Posições de tubos devem ser controladas nos testes.
- Elementos visuais: Animações e UI são difíceis de testar diretamente.
- Testes de performance: Resultados podem variar entre simulação e dispositivo real.
Conclusão: Construindo Jogos com Confiança
O TDD transforma o desenvolvimento de jogos em um processo estruturado e previsível. Através do FlappySwift, vimos como testes bem elaborados garantem funcionalidades centrais como pontuação, física e geração de conteúdo. Embora elementos visuais exijam abordagens complementares, a base lógica pode ser robustamente testada e evoluída.
Para começar, clone o repositório:
git clone https://gitcode.com/gh_mirrors/fl/FlappySwift
Abra o projeto em Xcode (FlappyBird.xcodeproj) e execute os testes com Cmd+U. Com o tempo, expanda a cobertrua de testes para tornar o TDD parte natural do fluxo de desenvolvimento.