DOJO - TDD com C++

1.343 visualizações

Publicada em

Slides usados em DOJOs de Test-Driven Development com C++
por Thiago Delgado Pinto

Publicada em: Tecnologia
0 comentários
1 gostou
Estatísticas
Notas
  • Seja o primeiro a comentar

Sem downloads
Visualizações
Visualizações totais
1.343
No SlideShare
0
A partir de incorporações
0
Número de incorporações
187
Ações
Compartilhamentos
0
Downloads
0
Comentários
0
Gostaram
1
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

DOJO - TDD com C++

  1. 1. TDD em C++Thiago Delgado Pinto
  2. 2.  Parte 1 – Verificação de Estado Parte 2 – Verificação de Comportamento
  3. 3. Verificação de EstadoYAFFUT_CHECK( ok );YAFFUT_EQUAL( e, o );YAFFUT_UNEQUAL( e, o );YAFFUT_FAIL( msg );YAFFUT_ASSERT_THROW( func, e );
  4. 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. 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. 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.
  7. 7. void somaDoisNumerosInteirosCorretamente(){ assert( soma( -1, -1 ) == -2 ); Suposições devem assert( soma( -1, 0 ) == -1 ); tentar exercitar combinações com assert( soma( -1, 1 ) == 0 ); valores mínimos, assert( soma( 0, 0 ) == 0 ); máximos e assert( soma( 0, 1 ) == 1 ); potencialmente problemáticos. assert( soma( 1, 1 ) == 2 );} Devemos, porém, dividir os casos em mais de um teste.
  8. 8. void somaUsandoNumerosNegativosCorretamente(){ assert( soma( -1, -1 ) == -2 ); assert( soma( -1, 1 ) == 0 );}void somaComZeroResultaNoProprioNumero(){ assert( soma( -1, 0 ) == -1 ); assert( soma( 0, 0 ) == 0 ); assert( soma( 0, 1 ) == 1 );}void somaUsandoNumerosPositivosCorretamente(){ assert( soma( 1, 1 ) == 2 ); assert( soma( 1, -1 ) == 0 );}
  9. 9. void chuteAltoTiraUmDecimoDaEnergia(){ Lutador a, b; a.DefinirEnergia( 100 ); b.DesferirChuteAltoEm( a ); assert( a.Energia() == 90 );}
  10. 10. void transferenciaFazDebitarDaContaOrigem(){ Conta origem, destino; origem.DefinirSaldo( 10500 ); double valorATransferir = 500; double saldoEsperadoAposTransferencia = origem.Saldo() – valorATransferir; origem.Transferir( destino, valorATransferir ); assert( origem.Saldo() == saldoEsperadoAposTransferencia );}
  11. 11. void transferenciaFazCreditarNaContaDestino(){ Conta origem, destino; origem.DefinirSaldo( 10500 ); destino.DefinirSaldo( 2000 ); double valorATransferir = 500; double saldoEsperadoAposTransferencia = destino.Saldo() + valorATransferir; origem.Transferir( destino, valorATransferir ); assert( destino.Saldo() == saldoEsperadoAposTransferencia );}
  12. 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. 
  13. 13.  ligarNitroFazCarroAcelerarQuandoEmMovimento () acelerarComFreioDeMaoLigadoFazRodasTraseiras Derraparem() finalizarVendaFazBaixarEstoqueDeItensVendidos() cairNoPrecipicioRetiraVidaDoJogador()
  14. 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. 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. 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. 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. 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() );}
  19. 19. 5/6 Outras funções:YAFFUT_UNEQUAL( esperado, obtido )YAFFUT_FAIL( mensagem )YAFFUT_ASSERT_THROW( método, exceção )
  20. 20. 6/6 Como pode ser o programa principal de teste:#include <yaffut.h>#include “TesteContaBancaria.h”// ... outras bibliotecas de testeint main(int argc, const char* argv[]){ return yaffut::Factory::Instance().Main (argc, argv);}
  21. 21.  CppUnit CppUnitLite boost::test TUT CxxTest ...
  22. 22. Verificação de ComportamentoMockRepository mr;Intf *intf = mr.InterfaceMock< Intf >();mr.ExpectCall( intf, Intf::Method1 ) .With( “hello” ) .Return( 10 );AClass ac;ac.DoSomething( *intf );
  23. 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. 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. 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.
  26. 26. 3/10class ImpressoraExtrato {public: // Retorna true se conseguir imprimir bool Imprimir(const ContaBancaria &conta) = 0;};
  27. 27. 4/10 Então, podemos pensar que nosso terminal bancário tenha o seguinte método:// Retorna true se conseguir imprimirbool 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. 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 ! }};
  29. 29. 6/10 Nosso teste pode ficar assim:TEST( TesteContaBancaria, terminalConsegueImprimirExtrato ){ ContaBancaria conta; conta.Depositar( 1000 ); conta.Sacar( 500 ); ImpressoraExtrato *impressora = new ImpressoraExtratoFalsa(); TerminalBancario terminal; bool imprimiu = terminal.Imprimir(conta, *impressora ); delete impressora; YAFFUT_CHECK( imprimiu );}
  30. 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. 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. 32. 9/10class ImpressoraExtratoFalsa : public ImpressoraExtrato{ ImpressoraExtratoFalsa() { chamadasAImprimir = 0; } bool Imprimir(const ContaBancaria &conta) { chamadasAImprimir++; return true; } int ChamadasAImprimir() const { return chamadasAImprimir; }private: int chamadasAImprimir;};
  33. 33. 10/10 Nosso teste pode ficar assim:TEST( TesteContaBancaria, terminalConsegueImprimirExtrato ){ ContaBancaria conta; conta.Depositar( 1000 ); conta.Sacar( 500 ); ImpressoraExtrato *impressora = new ImpressoraExtratoFalsa(); TerminalBancario terminal; bool imprimiu = terminal.Imprimir(conta, *impressora ); YAFFUT_EQUAL( 1, impressora->ChamadasAImprimir() ); YAFFUT_CHECK( imprimiu ); delete impressora;}
  34. 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. 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. 36.  Feito usando Yaffut (já vem com ele). É opensource. É simples. É poderoso. É portátil. http://www.assembla.com/spaces/hippomocks
  37. 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. 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. 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}
  40. 40. TDD em C++Thiago Delgado Pinto

×