You are currently browsing the archives for the refactoring category


Um pouco de TDD com Eunit

No final do ano passado estava bem empolgado com a implementação Erlang do Restfulie e, algum tempo depois de ter começado o projeto, resolvi deixar de ser cowboy e escrever alguns testes. De lá pra cá, vez ou outra, dei uma melhorada nos testes, primeiro pra ficar mais útil, depois pra deixar mais fluente. Ainda não está lá essas coisas, mas já está, no mínimo, interessante. Se você quiser, pode conferir no meu GitHub.

Bem, no final das contas, isso acabou me motivando a escrever esse post, como um tutorial básico de TDD (talvez alguns queriam chamar de BDD, não tem problema, podem chamar como assim) com Eunit, a biblioteca de testes padrão que vem com qualquer instalação de Erlang.

A idéia desse tutorial é seguir numa linha um pouco diferente da documentação do Eunit e dos exemplos contidos no próprio projeto, escrevendo testes mais fluentes e interessantes de ler.

Mão na massa

Passo 1: criar a estrutura do projeto

O nome do nosso projeto será making_tdd_with_eunit (bem criativo!). Vamos criar então a seguinte estrutura de diretórios, que é frequentemente encontrada em projetos Erlang:

- making_tdd_with_eunit
  |
  +-- ebin
  +-- src
  +-- test

OK, apesar dos diretórios terem nomes auto explicativos, não custa nada reforçar, né?

ebin, contém os binários (.beam) resultantes da compilação dos fontes;
src, contém os fontes (.erl) dos módulos;
test, contém os fontes (.erl) dos testes.

Passo 2: escrever o primeiro teste que inevitavelmente falhará

Qual é o fluxo de trabalho com TDD mesmo, hein? Escrever o código a ser testado, rodar e, se funcionar, escrever o tal teste que passa de primeira, não é isso? Não, não é. Isso não é TDD.

Pois muito bem, vamos fazer então como realmente tem que ser feito. No diretório test, crie o seguinte teste (mobile_test.erl):

-module(mobile_test).

-include_lib("eunit/include/eunit.hrl").

describe_client_test_() ->
  {"Mobile",
    [fun should_have_a_number/0]}.

should_have_a_number() ->
  ?assertMatch({number, _}, mobile:number()).

Feito isso, compile e rode o teste com os seguintes comandos:

$ erlc -o ebin/ test/mobile_test.erl
$ erl -pa ebin/ -noshell -run mobile_test test -run init stop

Qual foi o resultado? O teste falhou, certo? Pois é, como manda o figurino!

“Escreva um teste, rode todos os testes e veja-o falhar; escreva o código mais simples possível para fazê-lo passar, mesmo que num primeiro momento não seja o mais bonito e eficiente; rode todos os testes e veja-os passar; depois, refatore para remover duplicações.” [1]

Passo 3: fazer o teste passar

Agora vamos fazer esse teste passar. No diretório src, crie o seguinte módulo:

-module(mobile).

-export([number/0]).

number() ->
  {number, "1212-1212"}.

Como fizemos anteriormente – agora acrescentando o módulo mobile à compilação –, compile e rode o teste com os seguintes comandos:

$ erlc -o ebin/ src/mobile.erl test/mobile_test.erl
$ erl -pa ebin/ -noshell -run mobile_test test -run init stop

Resultado? “Test passed.”

Passo 4: escrever mais testes

Só para não prolongar muito – este que deveria ser um pequeno tutorial – vamos para o ponto em que magicamente passamos a ter testes para as quatro funções (ultra dummies) contidas no módulo mobile, tudo bem?

-module(mobile_test).

-include_lib("eunit/include/eunit.hrl").

describe_client_test_() ->
  {"Mobile",
    [fun should_have_a_number/0,
     fun should_have_a_area_code/0,
     fun should_have_a_company/0,
     fun should_have_a_owner/0]}.

should_have_a_fixed_number() ->
  ?assertMatch({number, "1212-1212"}, mobile:number()).

should_have_a_fixed_area_code() ->
  ?assertMatch({area_code, "11"}, mobile:area_code()).

should_have_a_fixed_company() ->
  ?assertMatch({company, "DEAD"}, mobile:company()).

should_have_a_fixed_owner() ->
  ?assertMatch({owner, "Little Jose"}, mobile:owner()).
-module(mobile).

-export([number/0, area_code/0, company/0, owner/0]).

number() ->
  {number, "1212-1212"}.

area_code() ->
  {area_code, "11"}.

company() ->
  {company, "DEAD"}.

owner() ->
  {owner, "Little Jose"}.

OK, repetindo o passo anterior de compilação e execução dos testes, temos quatro testes passando, certo? Então é hora de um pouco de refatoração…

Passo 5: refatorar os testes para ficarem mais fluentes

Nesse ponto, vamos adicionar mais fluência ao nosso teste e melhorar a sua comunicação, inclusive, descrevendo um cenário “bem interessante”.

-module(mobile_test).

-include_lib("eunit/include/eunit.hrl").

describe_client_test_() ->
  {"Mobile",
    {"when is a dummy",
      [
        {"should have a fixed number",
          fun should_have_a_fixed_number/0},
        {"should have a fixed area code",
          fun should_have_a_fixed_area_code/0},
        {"should have a fixed company",
          fun should_have_a_fixed_company/0},
        {"should have a fixed owner",
          fun should_have_a_fixed_owner/0}
      ]}}.

should_have_a_fixed_number() ->
  ?assertMatch({number, "1212-1212"}, mobile:number()).

should_have_a_fixed_area_code() ->
  ?assertMatch({area_code, "11"}, mobile:area_code()).

should_have_a_fixed_company() ->
  ?assertMatch({company, "DEAD"}, mobile:company()).

should_have_a_fixed_owner() ->
  ?assertMatch({owner, "Little Jose"}, mobile:owner()).

Compilando e rodando os testes: “All 4 tests passed.”

Próximo passo: escrever mais testes e refatorar o módulo mobile

Até aqui, temos um módulo mobile totalmente dummy e um teste que usa apenas a macro ?assertMatch. Ainda há muito que pode ser feito, desde adicionar algum comportamento útil ao módulo mobile, até melhorar os testes, fazer diferentes asserções, adicionar befor_all e after_all (como nos testes do Restfulierl, por exemplo) para estado inicial e final, e por aí vai.

Bem, esse passo deixo por sua conta. É um bom exercício pra você praticar mais Erlang e Eunit.

Espero que tenha gostado e até mais!

[1] Beck, K. Test-Driven Development: By Example. Addison-Wesley Professional, 2002.

Refatorar é preciso!

Definitivamente, refatorar é preciso!

Ninguém consegue escrever o melhor código do mundo na primeira vez que o escreve. Aliás, arrisco dizer que isto não é verdade apenas com software, mas com uma infinidade de coisas que fazemos ao longo da vida.

Desde que me entendo por gente garoto, sempre tive o hábito de refatorar. Nos meus trabalhos escolares, composições musicais, estudos bíblicos. Dia desses mesmo tive que escrever uma carta para ser lida publicamente [a detendos e familiares] em um presídio de São Paulo. Que responsabilidade! Escrevi e refatorei várias vezes numa mesma madrugada até a tal carta ficar muito boa.

No meu trabalho não é diferente. Escrevo e re-escrevo códigos sempre. Não por terem sido mal escritos por mim ou por algum colega, mas porque acredito que eles sempre podem ser aprimorados.

Refatoração como disciplina da Engenharia de software:

Martin Fowler, em seu site oficial sobre refatoração, diz que refatorar é alterar a estrutura interna de um código sem alterar o seu comportamento externo. É fazer pequenas modificações de cada vez, que somadas, resultem em modificações significativas, sem contudo, afetar o funcionamento do código.

De forma bem prática, no meu dia a dia, costumo enumerar as tarefas da refatoração em: Revisar, remolelar e refinar. Três ‘R‘s indispensáveis na vida de todo bom programador, porque:

1. Todo código precisa ser revisado;
2. Todo código é forte candidato a ser remodelado, pelo menos uma vez;
3. Todo código deve ser refinado sempre.

Quando aplicada corretamente, a refatoração trás excelentes ganhos, tais como:

1. Códigos mais legíveis;
2. Códigos mais performáticos;
3. Códigos mais extensíveis;
4. E sobretudo, código mais simples!

Este e o sentimento da refatoração. Ganhos hoje; e mais ainda, amanhã. 

Agora, sabe qual é a triste realidade? Poucos gestores de projetos entendem e encorajam esta prática. Uma pena, porque o projetos só tem a ganhar. Quantas vezes você já não entrou em um projeto e perdeu horas e horas pra entender o código macarrão que alguém que nem está mais na empresa fez? Quantas vezes mais isso ainda não vai acontecer neste mesmo projeto? Ah, se estas horas tivessem sido empregas em refatoração…

Mas, sabe de uma outra coisa? A culpa não é só dos gestores de projetos. Desenvolvedores de software também precisam aprender a vender suas idéias, soluções, e mostrar resultados reais da aplicação de determinadas técnicas. Afinal de contas, cientistas renomados como Martin Fowler costumam ter razão naquilo que dizem.

Bom, ainda bem que aqui na equipe de arquitetura da CVC Turismo não é assim. Aqui refatorar é visto com muito bons olhos. Bons olhos sobre quem faz a refatoração, dado o seu pró-ativismo; e bons olhos sobre o próprio patrimônio de software da empresa que é valorizado a cada dia.

Se seu gestor não quer nem ouvir falar em refatoração, eu tenho uma sugestão pra você: Faça algumas refatorações por conta própria, sem que estas afetem o andamento do seu projeto, é claro, e depois apresente a ele um relatório de ganhos, conforme citados anteriormente.

Não se esqueça: Frutos falam mais que palavras.

Depois poste aqui o resultado…