TDD - Algumas lições aprendidas com o livro GOOS

2.268 visualizações

Publicada em

- TDD: Red – Green – Refactor
- Testes de Unidade x Testes de Integração
- Ports and Adapters Pattern
- Business Domain versus Technical Domain
- Testes de Unidade com JMock
- Coverage Tests
- Logging Tests

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

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

Nenhuma nota no slide

TDD - Algumas lições aprendidas com o livro GOOS

  1. 1. Test Driven Development <ul>Fábio Luiz Nery de Miranda baseado no livro Growing OO Software Guided by Tests </ul>
  2. 2. Roadmap <ul><li>Problema: autenticação em um service provider de terceiros.
  3. 3. TDD: Red – Green – Refactor
  4. 4. Testes de Unidade x Testes de Integração
  5. 5. Ports and Adapters Pattern </li><ul><li>(Business Domain versus Technical Domain) </li></ul><li>Testes de Unidade com JMock
  6. 6. Coverage Tests
  7. 7. Logging Tests </li></ul>
  8. 8. Autenticação num Service Provider de Terceiros <ul><li>API REST / XML </li></ul>POST http://isp-authenticator.dev.globoi.com:8280/ws/rest/autenticacao XML Resquest Autenticação: <usuarioAutenticado> <glbId>303a6a6f616f646576406d61696c</glbId> <login>fulano</login> <senha>123456</senha> <ip>127.0.0.1</ip> </usuarioAutenticado> XML Response Autenticação <usuarioAutenticado> <emailPrincipal>fab1@spam.la</emailPrincipal> <glbId>12257993f74d2b1f</glbId> <login>fab1@spam.la</login> <status>AUTENTICADO</status> <statusUsuario>ATIVO</statusUsuario> <tipoUsuario>NAO_ASSINANTE</tipoUsuario> <usuarioID>21588993</usuarioID> </usuarioAutenticado>
  9. 9. Por onde começar? <ul><li>Defina a classe de domínio que deseja testar (System Under Test – SUT)
  10. 10. Queremos uma classe que realize autenticação (um Authenticator)
  11. 11. O ponto de partida será AuthenticatorTests </li></ul>
  12. 12. Qual o foco do teste? O nome do teste expressa a intenção? <ul><li>Defina o foco do teste, criando um método de teste cujo nome expresse a intenção </li><ul><li>(Ajuda a não se afastar do foco) </li></ul><li>Um Authenticator deve realizar autenticação caso o nome do usuário e a senha estejam corretos.
  13. 13. @Test
  14. 14. public void shouldAuthenticateWhenCredentialsAreValid() {
  15. 15. } </li></ul>
  16. 16. Exercitando o teste <ul><li>Comece a implementar o teste exercitando o SUT (mesmo que o SUT ainda não exista)
  17. 17. @Test
  18. 18. public void shouldAuthenticateWhenCredentialsAreValid() { </li><ul><ul><li>AuthenticationResponse response = authenticator.authenticate(validUsername, validPassword); </li></ul></ul><li>} </li></ul>
  19. 19. Setup e Assertions <ul><li>Faça as asserções e o setup necessários.
  20. 20. private final Authenticator authenticator = new Authenticator();
  21. 21. private final String validUsername = “valid@userna.me”;
  22. 22. private final String validUsername = “va1idpassw0rd”;
  23. 23. @Test
  24. 24. public void shouldAuthenticateWhenCredentialsAreValid() { </li><ul><ul><li>AuthenticationResponse response = authenticator.authenticate(validUsername, validPassword);
  25. 25. assertThat(“authentication response”, response, is(notNullValue()));
  26. 26. assertThat(“authentication response”, response.isAuthenticated(), is(true)); </li></ul></ul><li>} </li></ul>
  27. 27. Implemente as classes do domínio (stubs vazios) <ul><li>Crie as classes, mas apenas o suficiente para desaparecerem os erros de compilação.
  28. 28. Observe o teste falhar
  29. 29. A mensagem de falha do teste expressa suficientemente bem o que está quebrado? </li></ul>
  30. 30. A importância de falhar antes <ul><li>Evitar falsos positivos
  31. 31. Promove boas práticas de diagnóstico de falhas (GOOS Best Practice!) </li><ul><li>A clareza nas mensagens de falha é muito importante
  32. 32. Elas ajudarão a diagnosticar problemas, quando novas features provoquem quebra de código existente.
  33. 33. A ausência de mensagens claras sobre a falha forçará o desenvolvedor a ter que ler e entender a implementação para saber o que quebrou (pior caso: quando o dev que fez quebrar o código não é o próprio autor do código) </li></ul><li>Hamcrest Library: matchers são “synthax sugar” e ajudam a criar mensagens de falha mais detalhadas. </li></ul>
  34. 34. Implemente o código mais simples que permita o teste PASSAR <ul><li>Não precisa implementar toda a lógica complexa de uma só vez
  35. 35. KISS – Keep It Simple
  36. 36. Não perca o foco do teste.
  37. 37. Não se distraia com premissas que devem ser validadas em outros testes. </li></ul>
  38. 38. DONE? <ul><li>O que o Authenticator deveria fazer? </li><ul><li>POST num serviço REST/XML </li></ul><li>O que o Authenticator está fazendo? </li></ul>public AuthenticationResponse authenticate(String username, String password) throws AuthenticationFailure { return new AuthenticationResponse(); // lazy boy... } public class AuthenticationResponse { public Boolean isAuthenticated() { return true ; // aham... } } <ul><li>Authenticator é um fanfarrão... </li></ul>
  39. 39. Pausa para Reflexão <ul><li>Authenticator deve se preocupar com os detalhes de como a autenticação é realizada?
  40. 40. Resposta: Depende (nenhuma verdade é absoluta)
  41. 41. Se está interessado em criar um Domain Model, é importante separar classes de domínio das classes que possuem detalhes técnicos de como a funcionalidade é implementada. </li></ul>
  42. 42. Ports and Adapters Pattern <ul><li>Ports: interfaces p/ abstrair relações domínio x dependências externas)
  43. 43. Adapters: implementam as interfaces, encapsulando o domínio técnico da aplicação
  44. 44. Testes de Unidade: verificam o comportamento do Authenticator.
  45. 45. Testes de Integração: verificam a implementação do delegate. </li></ul>
  46. 46. Ports and Adapters Pattern (GOOS)
  47. 47. Retornando foco ao teste... <ul><li>Queremos saber se o Authenticator comanda corretamente o delegate, sem se preocupar em como o delegate será implementado. </li></ul>private Mockery context = new Mockery(); private final Authenticator.Delegate delegate = context .mock(Authenticator.Delegate. class ); private Authenticator authenticator = new Authenticator( delegate ); @Test public void shouldAuthenticateSucessfully() throws Exception { context .checking( new Expectations() {{ AuthenticationResponse sucessfullAuthentication = new AuthenticationResponse(); sucessfullAuthentication.setAuthenticated( true ); oneOf( delegate ).performAuthentication(with( validUsername ), with( validPassword )); will( returnValue (sucessfullAuthentication)); }}); AuthenticationResponse response = authenticator .authenticate( validUsername , validPassword ); assertThat ( &quot;authentication response&quot; , response, is ( notNullValue ())); assertThat ( &quot;authentication response&quot; , response.isAuthenticated(), is ( true )); }
  48. 48. Rodando os Testes... <ul><li>Pegadinha do Malandro: @RunWith(JMock.class), para o Jmock verificar se os Mock Objects do contexto foram executados de acordo com as expectativas.
  49. 49. Falhar o teste : o teste falha de forma clara, facilitando o correto diagnóstico?
  50. 50. Corrigir Authenticator e AuthenticationResponse e passar </li></ul>
  51. 51. DONE? <ul><li>A classe Authenticator está coberta por testes de unidade.
  52. 52. O Delegate responsável pela autenticação via POST/XML é injetado no Authenticator, via construtor.
  53. 53. Resta prover uma implementação do Delegate – um RestTemplateDelegate, por exemplo – e escrever um teste de integração. </li></ul>
  54. 54. Teste de Integração @RunWith (SpringJUnit4ClassRunner. class ) @ContextConfiguration (locations= &quot;classpath:beans.xml&quot; ) public class RestTemplateAuthenticatorTests { @Autowired private RestTemplateAuthenticationDelegate restTemplateDelegate ; private Authenticator authenticator ; @Before public void setup() { authenticator = new Authenticator( restTemplateDelegate ); } @Test public void authenticateUser() throws Exception { assertTrue ( authenticator .authenticate( &quot;fab1@spam.la&quot; , &quot;teste123456&quot; ).isAuthenticated()); }
  55. 55. RestTemplateAutenticationDelegate public class RestTemplateAuthenticationDelegate implements Authenticator.Delegate { private final String url ; private final RestTemplate rest ; public RestTemplateAuthenticationDelegate(String url, RestTemplate rest) { this . url = url; this . rest = rest; } public AuthenticationResponse performAuthentication(String username, String password) throws AuthenticationFailure { try { XmlAuthenticationRequest xmlRequest = new XmlAuthenticationRequest(); xmlRequest.setIp( &quot;127.0.0.1&quot; ); xmlRequest.setLogin(username); xmlRequest.setSenha(password); XmlAuthenticationResponse xmlResponse = rest .postForObject( url , xmlRequest, XmlAuthenticationResponse. class ); AuthenticationResponse response = new AuthenticationResponse(); response.setAuthenticated(xmlResponse.getStatus().equals( &quot;AUTENTICADO&quot; )); return response; } catch (Throwable e) { throw new AuthenticationFailure(e); } } }
  56. 56. E depois? <ul><li>Refactor – Clean Code
  57. 57. Coverage Test (Eclipse Emma)
  58. 58. Manter a cobertura dos testes em nível elevado (acima de 90%) é praticamente impossível se você não pratica Test First.
  59. 59. Aumentar a cobertura de código “Test After” é muito mais difícil, e pode dar um falsa sensação de cobertura. </li></ul>
  60. 60. Se der tempo... <ul><li>shouldNotAuthenticateInvalidUsername() </li><ul><li>Red – Green – Refactor – Coverage </li></ul><li>ShouldNotAuthenticateInvalidPassword() </li><ul><li>Red – Green – Refactor – Coverage </li></ul><li>shouldLogFailureOnAuthenticationErrors()
  61. 61. LoggingTests! </li></ul>
  62. 62. Logging Tests <ul><li>Como normalmente fazemos logging: </li></ul>private static final Logger logger = Logger.getLogger(Authenticator. class ); ... if ( logger .isDebugEnabled()) logger .debug(message); <ul><li>“ Logging is a Feature”: logging é a User Interface do time de suporte!
  63. 63. Deve-se dar tanta importância a testes de Logging como damos a testes de UI!
  64. 64. Não é desejável que as mensagens críticas de logging quebrem com a evolução da aplicação
  65. 65. Caso existam ferramentas automáticas de análise de log, elas irão quebrar caso o logging não seja estável com o passar do tempo.
  66. 66. Mensagens de debbuging não precisam ser testadas.
  67. 67. Deve-se selecionar com critério as mensagens críticas para a aplicação, que precisam ser testadas.
  68. 68. Tratar como Logging como Requisitos de Suporte? </li></ul>
  69. 69. Logging: Business Domain x Technical Domain <ul><li>É possível testar o arquivo de logging e verificar se as mensagens esperadas estão presentes.
  70. 70. Amarra a implementação à biblioteca de Logging (mistura o domínio do negócio com o domínio técnico). </li></ul>
  71. 71. Ports and Adapters strikes back <ul><li>Sugestão: Ports and Adapters Pattern. </li><ul><li>Authenticator interage com um delegate FailureReporter.
  72. 72. Testes de unidade verificam a relação entre Authenticator e FailureReporter
  73. 73. Testes de Integração verificam a implementação do FailureReporter. </li></ul></ul>
  74. 74. Logging com Ports e Adapters <ul><li>Bonus: permite a implementação de diferentes estratégias de FailureReporting </li><ul><li>Envio de Email, Persistência / Estatísticas de falha, etc... </li></ul></ul>
  75. 75. Dúvidas? <ul>Obrigado! </ul>

×