Desenvolvemos diversos algoritmos recursivos e, para verificar se eles funcionavam corretamente, realizamos testes manuais executando as funções com diferentes entradas e verificando seus resultados. No entanto, à medida que nossos programas se tornam mais complexos, essa abordagem manual se torna ineficiente e propensa a erros. Neste capítulo, introduziremos o conceito de testes automatizados, que nos permitirá verificar de forma sistemática e confiável se nossas funções estão operando como esperado.
Por que Testar?¶
Quando escrevemos código, queremos ter certeza de que ele está funcionando corretamente. Os testes automatizados nos ajudam a:
Verificar se o código produz os resultados esperados para diferentes entradas
Identificar bugs e erros antes que o programa seja utilizado
Garantir que modificações no código não quebrem funcionalidades existentes
Documentar o comportamento esperado de nossas funções
Em desenvolvimento de software, uma prática comum é o TDD (Test-Driven Development), onde primeiro escrevemos os testes e depois implementamos o código que satisfaz esses testes. Esta abordagem incentiva um design mais claro e uma melhor compreensão dos requisitos antes mesmo de começar a programar.
Escrevendo Testes Com Condicionais¶
Vamos começar com uma abordagem mais simples para criar testes automatizados usando estruturas condicionais. Consideraremos o “Problema das Escadas” que vimos no capítulo anterior.
Relembrando, o problema consistia em determinar de quantas maneiras diferentes podemos subir uma escada com degraus, se podemos dar passos de 1 ou 2 degraus por vez. Nossa solução foi:
function maneiras_subir_escada(n)
# Casos base
if n == 0 || n == 1
return 1
else
# Caso recursivo: soma das maneiras de chegar a partir de n-1 e n-2
return maneiras_subir_escada(n - 1) + maneiras_subir_escada(n - 2)
end
endmaneiras_subir_escada (generic function with 1 method)Agora, vamos criar uma função de teste para verificar se nossa implementação está correta:
function testa_maneiras_subir_escada()
# Geralmente, verificamos alguns casos conhecidos ou que sabemos a resposta
if maneiras_subir_escada(1) != 1
println("Erro para n = 1")
return false
end
if maneiras_subir_escada(2) != 2
println("Erro para n = 2")
return false
end
if maneiras_subir_escada(3) != 3
println("Erro para n = 3")
return false
end
if maneiras_subir_escada(4) != 5
println("Erro para n = 4")
return false
end
println("Todos os testes para a função maneiras_subir_escada passaram!")
return true
end
# Executamos os testes
testa_maneiras_subir_escada()Todos os testes para a função maneiras_subir_escada passaram!
trueNesta função de teste, verificamos se a nossa implementação retorna os valores corretos para diferentes entradas. Se algum teste falhar, exibimos uma mensagem indicando qual caso falhou. Se todos os testes passarem, exibimos uma mensagem de sucesso.
Este é um princípio importante para testes automatizados: se o teste passar, ele deve indicar apenas que deu certo! Isso significa que, idealmente, os testes não devem imprimir muitas mensagens quando tudo estiver funcionando corretamente, apenas quando algo der errado.
Vamos fazer o mesmo para o cálculo do “Coeficiente Binomial”, que também vimos no capítulo anterior:
function coeficiente_binomial(n, k)
if k == 0 || k == n
return 1
else
return coeficiente_binomial(n - 1, k - 1) + coeficiente_binomial(n - 1, k)
end
end
function testa_coeficiente_binomial()
if coeficiente_binomial(5, 2) != 10
println("Erro para (5, 2)")
return false
end
if coeficiente_binomial(10, 4) != 210
println("Erro para (10, 4)")
return false
end
if coeficiente_binomial(7, 3) != 35
println("Erro para (7, 3)")
return false
end
println("Todos os testes para a função coeficiente_binomial passaram!")
return true
end
# Executamos os testes
testa_coeficiente_binomial()Todos os testes para a função coeficiente_binomial passaram!
trueTestes com o Módulo Test¶
Até agora, criamos funções de teste manualmente usando estruturas condicionais. No entanto, Julia fornece um módulo de testes integrado chamado Test, que oferece funcionalidades mais avançadas para testes automatizados.
Vamos reescrever nossos testes usando o módulo Test:
using Test
@testset "Testes para maneiras_subir_escada" begin
@test maneiras_subir_escada(1) == 1
@test maneiras_subir_escada(2) == 2
@test maneiras_subir_escada(3) == 3
@test maneiras_subir_escada(4) == 5
end
@testset "Testes para coeficiente_binomial" begin
@test coeficiente_binomial(5, 2) == 10
@test coeficiente_binomial(10, 4) == 210
@test coeficiente_binomial(7, 3) == 35
endTest Summary: | Pass Total Time
Testes para maneiras_subir_escada | 4 4 0.3s
Test Summary: | Pass Total Time
Testes para coeficiente_binomial | 3 3 0.0s
Test.DefaultTestSet("Testes para coeficiente_binomial", Any[], 3, false, false, true, 1.789736773935702e9, 1.789736773935743e9, false, "In[4]")Com o módulo Test, utilizamos a macro @testset para agrupar testes relacionados e a macro @test para verificar condições específicas. Se um teste falha, o módulo exibe automaticamente informações úteis sobre a falha, como a expressão que falhou e os valores esperados versus os valores obtidos.
Além disso, o módulo Test oferece outras macros úteis:
@test_throws: verifica se uma expressão lança uma exceção específica@test_approx_eq: verifica se dois valores de ponto flutuante são aproximadamente iguais (considerando erros de arredondamento)@test_broken: marca um teste que é esperado falhar (útil para documentar bugs conhecidos)
Mais Exemplos¶
Vamos implementar duas novas funções e seus respectivos testes: uma função para calcular a soma dos dígitos de um número e outra para verificar se um número é primo.
Soma dos Dígitos¶
Primeiramente, vamos criar uma função que calcula a soma dos dígitos de um número inteiro. Por exemplo, para o número 123, a soma dos dígitos seria 1 + 2 + 3 = 6.
Antes de implementar a função, vamos pensar nos casos de teste:
Se a função recebe um inteiro de um único dígito ela deve retornar esse dígito
Se a função recebe 100, ela deve retornar 1 + 0 + 0 = 1
Se a função recebe 123, ela deve retornar 1 + 2 + 3 = 6
Se a função recebe 99, ela deve retornar 9 + 9 = 18
Podemos implementar a função usando recursão. A ideia é “descascar” o número, extraindo um dígito de cada vez:
function soma_digitos(n)
if n <= 0
return 0
else
# Obtemos o último dígito com o resto da divisão por 10
ultimo_digito = n % 10
# Removemos o último dígito com a divisão inteira por 10
resto_numero = n ÷ 10
# Somamos o último dígito com a soma dos dígitos do resto do número
return ultimo_digito + soma_digitos(resto_numero)
end
endsoma_digitos (generic function with 1 method)Os casos de teste discutidos acima podem ser implementados utilizando o módulo Test:
@testset "Testes para soma_digitos" begin
@test soma_digitos(0) == 0
@test soma_digitos(1) == 1
@test soma_digitos(100) == 1
@test soma_digitos(123) == 6
@test soma_digitos(99) == 18
endTest Summary: | Pass Total Time
Testes para soma_digitos | 5 5 0.0s
Test.DefaultTestSet("Testes para soma_digitos", Any[], 5, false, false, true, 1.789736774072842e9, 1.789736774076389e9, false, "In[6]")Verificação de Números Primos¶
Vamos criar uma função para verificar se um número é primo. Um número primo é aquele que é divisível apenas por 1 e por ele mesmo. Antes de escrever a função vamos pensar nos testes:
Por definição, qualquer número menor ou igual a 1 não é primo
O número 2 é primo (fácil de verificar)
O número 3 é primo (também fácil de verificar)
O número 4 não é primo, pois 2 também divide 4
O número 17 é primo
O número 25 não é primo, pois 5 também divide 25
Podemos implementar a função usando uma abordagem recursiva que tenta dividir o número por cada inteiro de 2 até a raiz quadrada do número:
function verifica_divisor(n, divisor)
# Se encontramos um divisor, o número não é primo
if n % divisor == 0
return false
# Se já testamos até a raiz quadrada, o número é primo
elseif divisor * divisor > n
return true
else
# Continua verificando com o próximo divisor
return verifica_divisor(n, divisor + 1)
end
end
function e_primo(n)
if n <= 1 # Por definição
return false
elseif n == 2 # Primeiro primo
return true
else
# Verifica se n tem algum divisor começando com 2
return verifica_divisor(n, 2)
end
ende_primo (generic function with 1 method)Os testes podem ser escritos como:
@testset "Testes para e_primo" begin
@test e_primo(2) == true
@test e_primo(3) == true
@test e_primo(4) == false
@test e_primo(17) == true
@test e_primo(25) == false
endTest Summary: | Pass Total Time
Testes para e_primo | 5 5 0.0s
Test.DefaultTestSet("Testes para e_primo", Any[], 5, false, false, true, 1.789736774156016e9, 1.789736774166256e9, false, "In[8]")Verifique seu Aprendizado¶
Qual é a diferença entre usar estruturas condicionais e o módulo Test para testes automatizados?
Por que os testes automatizados são importantes no desenvolvimento de software?
Explore por Conta Própria¶
Explore outras macros disponíveis no módulo Test de Julia e experimente usá-las em seus próprios testes.