You are currently browsing the archives for the tdd 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.