sexta-feira, 4 de novembro de 2011

Test-Driven Development - Adoção sem Aprovação

Algumas práticas ágeis causam um grande impacto no ambiente. Frequentemente envolvem mais de uma pessoa e, não raro, vários setores da empresa. Mas para praticar o Test-Driven Development (TDD), o desenvolvedor precisa apenas  convencer a si mesmo.

O desenvolvimento orientado a testes, como qualquer prática, necessita de tempo e dedicação, pois a cultura atual diz para construir e testar (se der tempo), nesta ordem. Não raro, construímos vários componentes e só depois testamos.

Invertendo esta ordem, em primeiro lugar, aumentamos a importância do teste, garantido que sempre seja escrito. Em segundo lugar, aumentamos a confiança no que estamos fazendo e, assim, melhoramos frequentemente a implementação, pois não temos mais medo de alterar.

A teoria é mais simples que a prática. O grande desafio do TDD é aprendermos a escrever bons testes. É neste ponto que entra a experiência. Quando os testes estão cobrindo a funcionalidade de forma eficiente, temos uma boa suite para aquele requisito do usuário.

Ao escrever os testes, precisamos pensar em mais que na correção do requisito, isto é, precisamos testar os limites, todas as condições de um if, fazer passar em laços, condições de exceção, etc. Para isso, precisamos calcular bem os inputs, garantindo que informações consistentes estão sendo passadas para a aplicação.

E é justamente estas técnicas que mostrarei aqui, com um dos requisitos de um sistema que estou desenvolvendo como free-lancer, com uma equipe composta de quatro pessoas, sendo uma delas, o dono do produto. O sistema consiste em processos de leitura de informações para um sistema de monitoramento baseado em GPS. Tenho boa experiencia com este tipo de sistema, já que trabalhei três anos consecutivos com uma ampla variedade de satélites e sistemas, quando fui o responsável pela área de desenvolvimento de uma empresa especializada neste tipo de serviço.

O requisito que escolhi foi a leitura da data da mensagem. A implementação da leitura será omitida. Na verdade, creio que esta omissão não prejudicará o objetivo deste texto, que deve focar em como testar.

Bem, para ler a data de geração da mensagem precisamos, inicialmente, garantir que existe mensagem. Assim:

    @Test(expected=IllegalArgumentException.class)
    public void recusarMensagemSemCaracteres() {
        final Rastreador people = People.getRastreador("");
        people.lerDataDeGeracao();
    }


O proximo teste garante que o texto informado tenha a quantidade de campos necessária para a leitura da data de geração:

    @Test(expected=IllegalArgumentException.class)
    public void recusarMensagemComMenosDe15PartesSeparadasPorVirgula() {
        final Rastreador people = People.getRastreador("abc");
        people.lerDataDeGeracao();
    }

No próximo, garantimos que seja recusada uma mensagem sem a data com o formato esperado, na posição esperada:

    @Test(expected=IllegalArgumentException.class)
    public void recusarMensagemSemDataNoFormatoEsperadoNaUltimaParte() {
        final String mensagem = "357566000009224    $GPRMC,120111.00,V,2300.52128,S,04321.95074,W,3.185,301.85,030911,,,A*78,V=3.438(LP),Device BATTERY LOW!!!";
        final Rastreador people = People.getRastreador(mensagem);
        people.lerDataDeGeracao();
    }


E, finalmente, no ultimo teste desta suite, garantimos que conseguimos ler a data de geração:

    @Test
    public void recuperarDataDeGeracao() throws ParseException {
        final String mensagem = "357566000009224    $GPRMC,120111.00,V,2300.52128,S,04321.95074,W,3.185,301.85,030911,,,A*78,V=3.438(LP),Device BATTERY LOW!!!    2011/9/3 ?? 12:01:11";
        final Rastreador people = People.getRastreador(mensagem);
        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
        Date dataEsperada = sdf.parse("03/09/2011 12:01:11");
        Date dataObtida = people.lerDataDeGeracao();
       
        Assert.assertEquals(dataEsperada, dataObtida);
    }


Como líder técnico deste projeto, foi fácil implantar a cultura do TDD e, com isso, mostrar ao time, na prática, os benefícios de testar antes. Entretanto, outra grande vantagem do TDD é a sua aplicabilidade. Aprovação de ninguém é necessária. A única necessidade é garantir que você está entregando a mesma quantidade de funcionalidades que entregava antes. Com prática no desenvolvimento orientado a testes, é possível, em pouco tempo, começar a entregar mais com menos quebras (bugs) sendo assim, mais eficiente.

Na minha visão, um dos grandes prazeres de praticar o TDD é descobrir testes que possam aumentar a confiança no que está sendo implementado.

9 comentários:

  1. Boa!
    Dia desses o @lucabastos fez um post sobre TDD, não sei se você viu:
    http://blog.concretesolutions.com.br/2011/10/falando-de-tdd/

    Muita gente pelo jeito não gosta de TDD! =(

    ResponderExcluir
  2. Vlw! Vi esse post sim.. tenho muita admiração pelo Luca..
    Eu trocaria "muita gente que não gosta de TDD" por "muita gente não gosta de refatorar e não está nem aí para a qualidade do que faz".. sabe gente que vive por viver? Existem pessoas que desenvolvem por desenvolver. Estas pessoas mal podem esperar por sua aposentadoria e "largar esse troço chato". Quem faz o que faz por paixão e não por obrigação, precisa encontrar um argumento muito bom para falar mal sobre TDD. Eu ainda não encontrei.

    Obrigado por sua participação!

    ResponderExcluir
  3. Meu user ai em cima saiu com asdfrw! o.O

    "muita gente não gosta de refatorar e não está nem aí para a qualidade do que faz", vejo isso todo dia, e quando você fala que programa pq gosta e não só pra ganhar dinheiro, o povo estranha!

    Mas uma pergunta pra ti: E aquele mega legado? vc costuma refatorar?
    No meu caso, quando tenho que criar novas features eu uso TDD.
    Quando tenho que alterar classes já existentes e muito ruins, tento criar testes pra ela e depois refatorá-las.
    Caso as classes estejam muito acopladas e a mega-refatoração vai comer muito tempo pra entregar aquela feature, tenho que "pesar" qual a melhor saída, se refatoro agora ou na próxima "brecha" que aparecer.

    ResponderExcluir
  4. Então... o Uncle Bob no livro Clean Code fala justamente isso. Sempre que você abrir uma classe que não esteja boa, refatore. Se não existe teste automatizado, crie um. Algumas vezes você entra em um ciclo, pois não consegue escrever o teste, pois o SRP foi violado. Com isso, você vai precisar identificar a menor responsabilidade e escrever um teste para ela e depois separa-la do método original.

    Ao nivel do teste unitário, temos muito mais liberdade, independente da pressão da gerencia. Quebre o método que está violando o SRP o máximo possível. Pode colocar um //TODO no que falta e repetir o processo na proxima vez que precisar abrir a classe.

    Tem um livro do Michael Feathers muito recomendado, mas ainda não li. http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052/ref=wl_it_dp_o_npd?ie=UTF8&coliid=I3PUYTLHH9KNEG&colid=13RA7RL1ZQN0P

    ResponderExcluir
  5. Excelente post, Celso! Concordo em absoluto!

    ResponderExcluir
  6. Aumentou muito a minha confiança,antes ficava estagnado em um ponto revendo as situações com medo de algo está errado,agora faço o modulo que estou desenvolvendo logo após testo e passo construir outros módulos sem duvidas,medos,angustias etc..

    ResponderExcluir
  7. Refatorei a solução. Na verdade, continuo refatorando.

    A ultima alteração importante foi semântica: criei a RuntimeException IllegalMessageReceivedException, pois IllegalArgumentException não expressava de fato o que estava acontecendo.

    Os testes ajudaram nesta percepção. Como está ajudando na percepção de outras refatorações.

    ResponderExcluir
  8. Estou fazendo meu TCC na area de testes e focado em TDD e percebi que muita gente não gosta de "perder" tempo testando, isso quer dizer que elas não se importam com a qualidade do que estão fazendo....os teste não nos levaram a um codigo perfeito e sem probremas, mas nos deixa mais proximos do do melhor

    ResponderExcluir