O documento discute TDD (Desenvolvimento Guiado por Testes) em C++, abordando verificação de estado através de testes de unidade e verificação de comportamento usando mocks. É apresentado o framework Yaffut para execução de testes e o HippoMocks para criação automática de mocks, facilitando a simulação de objetos em testes.
4. No Desenvolvimento Guiado por Testes, a
corretude de um software é verificada
através da escrita de testes para o mesmo.
Nesses testes, verificamos se algo funciona
conforme o esperado.
Essa expectativa é o que devemos criar no
teste.
5. Uma expectativa é uma suposição sobre
como algo funciona.
Por exemplo:
Se você quiser fazer uma função que some
dois números naturais e retorne essa soma.
Você pode supor que dados dois números, por
exemplo, 1 e 2, a função retorne 3.
6. void somaDoisNumerosNaturaisCorretamente()
{ Nome do Teste
assert( soma( 1, 2 ) == 3 );
}
A função assert recebe um valor booleano.
Se for true, a função não faz nada.
Se for false, ela interrompe a execução do
programa informando a linha em que houve a
falha na afirmação.
12. O nome deve tentar explicar o que é
verificado e qual o resultado esperado.
Nunca tente explicar como é verificado.
O nome pode ser bem longo.
Não se preocupe, você nunca precisará
digitá-lo novamente.
14. Precisamos criar um programa que chame todos
os nossos testes.
Há frameworks (bibliotecas de código que
podem ser estendidas) que podemos usar para
simplificar esse processo.
Usando um framework, nosso main não irá
precisar fazer nada além de iniciar o framework.
O framework se encarregará de chamar os
testes.
15. 1/6
Yet Another Framework For Unit Testing
É opensource
É portátil (Windows,GNU/Linux,MacOS,...)
É pequeno
É simples
É poderoso
http://members.home.nl/rutger.van.beusekom/
16. 2/6
Para usar o Yaffut, basta incluir seu único arquivo de código:
#include <yaffut.h>
Criar uma classe (vazia) que servirá para agrupar os testes:
class TesteContaBancaria {};
Use a macro TEST que recebe por parâmetro o nome da classe de
teste e o nome do teste. Ex:
TEST( TesteContaBancaria,
transferenciaFazDebitarDaContaOrigem )
{
...
}
17. 3/6
No lugar de assert, use YAFFUT_CHECK.
#include <yaffut.h>
class TesteContaBancaria {};
TEST( TesteContaBancaria, transferenciaFazDebitarDaContaOrigem )
{
Conta origem, destino;
origem.DefinirSaldo( 10500 );
double valorATransferir = 500;
double saldoEsperadoAposTransferencia =
origem.Saldo() – valorATransferir;
origem.Transferir( destino, valorATransferir );
YAFFUT_CHECK( origem.Saldo() ==
saldoEsperadoAposTransferencia );
}
18. 4/6
Porém, para comparar igualdades, prefira YAFFUT_EQUAL.
Recebe dois parâmetros: valor esperado e valor obtido.
TEST( TesteContaBancaria,
transferenciaFazDebitarDaContaOrigem )
{
Conta origem, destino;
origem.DefinirSaldo( 10500 );
double valorATransferir = 500;
double saldoEsperadoAposTransferencia =
origem.Saldo() – valorATransferir;
origem.Transferir( destino, valorATransferir );
YAFFUT_EQUAL( saldoEsperadoAposTransferencia,
origem.Saldo() );
}
20. 6/6
Como pode ser o programa principal de teste:
#include <yaffut.h>
#include “TesteContaBancaria.h”
// ... outras bibliotecas de teste
int main(int argc, const char* argv[])
{
return yaffut::Factory::Instance().Main (argc,
argv);
}
23. TDD serve não só para verificar valores, mas,
principalmente, para verificar o
comportamento de objetos em suas
interações com outros objetos.
Para isso, criamos objetos substitutos,
chamados de Mocks, que simulam a
execução de métodos reais.
24. 1/10
Se ao fazer uma classe TerminalBancario
quisermos ter um método que permita
imprimir o extrato de uma conta bancária.
Como testar se o extrato foi impresso ?
(Só olhar o papel saindo, certo?)
E se não tivermos impressora disponível para
verificar ?
25. 2/10
Precisamos mesmo da impressora, ou
podemos apenas simular o comportamento
que se espera dela ?
Se estamos interessados em seu
comportamento, e não em sua
implementação, podemos representar a
impressora como uma interface.
27. 4/10
Então, podemos pensar que nosso terminal
bancário tenha o seguinte método:
// Retorna true se conseguir imprimir
bool TerminalBancario::ImprimirExtrato(
const ContaBancaria &conta,
const ImpressoraExtrato &impressora);
Não importa como essa impressora funciona,
desde que ela diga que imprimiu, para o terminal
está OK.
28. 5/10
Daí, podemos criar uma implementação falsa da
impressora, para ser nossa substituta da
impressora real:
class ImpressoraExtratoFalsa : public
ImpressoraExtrato
{
bool Imprimir(const ContaBancaria &conta)
{
return true; // Só diz que imprimiu !
}
};
30. 7/10
Mas como saber se o terminal interagiu
corretamente com a impressora ?
Sabemos apenas que a impressora foi passada
como parâmetro, mas como saber se ela foi
usada ?
Ou seja, se o terminal chamou o método
Imprimir como esperado (uma vez, passando a
conta como parâmetro) e obteve um resultado
dela ?
31. 8/10
Podemos, em implementação falsa, criar um
controle para saber se o método foi chamado
como esperado.
E depois, no teste, verificamos se o método
Imprimir foi chamado.
32. 9/10
class ImpressoraExtratoFalsa : public ImpressoraExtrato
{
ImpressoraExtratoFalsa() {
chamadasAImprimir = 0;
}
bool Imprimir(const ContaBancaria &conta) {
chamadasAImprimir++;
return true;
}
int ChamadasAImprimir() const {
return chamadasAImprimir;
}
private:
int chamadasAImprimir;
};
34. Ter que criar manualmente implementações
falsas (classes falsas) e contabilizar a execução
de cada método é trabalhoso.
Porém, há frameworks de teste que permitem a
criação automática de implementações falsas
(chamadas de Mocks), bastando apenas definir
o comportamento esperado dos métodos.
A contabilização da chamada também é feita
automaticamente.
35. Em C++ há bons frameworks de teste
automatizado, porém, poucos com recursos de
criação automática de mocks:
Isolator++
AMOP
MockitoPP
HippoMocks
etc.
Adotaremos o HippoMocks com Yaffut1.
1. Veja análise em http://devhints.blogspot.com/2010/11/bons-frameworks-c-para-criacao.html
36. Feito usando Yaffut (já vem com ele).
É opensource.
É simples.
É poderoso.
É portátil.
http://www.assembla.com/spaces/hippomocks
37. Sua classe principal é MockRepository, que
permite criar um repositório de objetos falsos
(mocks).
Permite criação de mocks através do método
InterfaceMock.
Permite definir expectativas através do
método ExpectCall.
38. TEST( TesteContaBancaria, terminalConsegueImprimirExtrato )
{
ContaBancaria conta;
conta.Depositar( 1000 );
conta.Sacar( 500 );
MockRepository mr; // Repositório de mocks
ImpressoraExtrato *impressora =
mr.InterfaceMock< ImpressoraExtrato >(); // Cria um mock
// Cria a expectativa
mr.ExpectCall( impressora, ImpressoraExtrato::Imprimir )
.With( conta )
.Return( true );
TerminalBancario terminal;
bool imprimiu = terminal.Imprimir(conta, *impressora );
YAFFUT_CHECK( imprimiu );
delete impressora;
// Não precisa verificar NADA.
// O framework verifica se a expectativa criada foi atendida!
}
39. TEST( TesteBomba, atirarNumaBombaFazExplodiLa )
{
MockRepository mr; // Repositório de objetos falsos
// Bomba falsa
Bomba *bomba = mr.InterfaceMock< Bomba >();
// Expectativa do que deve acontecer com a bomba
// ao ser acertada por um tiro
mr.ExpectCall( bomba, Bomba::Explodir );
Jogador jogador;
Revolver revolver( 38 );
jogador.SegurarObjeto( revolver );
revolver.AtirarNoObjeto( bomba ); // Deve fazê-la explodir
}