Aprendendo TDD: Testando a camada Model [parte 1]

Ricardo Silva
Safety System Technology
8 min readMar 31, 2016

--

Continuando meus estudos sobre TDD (e consequentemente minha série de posts sobre isso), que ja teve um primeiro post publicado, onde montamos nossa suite de testes, chegamos ao momento de colocar a mão na massa (aaêêê \o/).

Objetivo de hoje

Nessa parte 1 nós vamos começar a implementar nossos testes na camada model, focando no que quase sempre trabalhamos nela: validações e relacionamentos. Também vamos tratar do conceito red-green-refactor, algumas particularidades das gems da nossa suite de testes, e outras coisas mais.

Camada model

No MVC , a camada model representa a ligação entre a aplicação e a fonte de dados, na maioria das vezes um banco de dados. No Rails também definimos relacionamentos e validações de dados. Por ex: um model User pode ter email e name como atributos cobrigatorios e esse model pode se relacionar com outro model, o model Show , que por sua vez tem o atributo title como obrigatório. Obviamente tudo isso pode (e deve) ser testado.

Red-Green-Refactor

Testar é importante, seja antes ou depois, o importante é testar. Mas quando se fala em seguir o TDD à risca, existem três passos que executamos no processo de testar nosso código:

Red- Escrever o teste antes de implementar o código.

Green- Implementar o mínimo de código para fazer o teste passar

Refactor- Refatorar o código para retirar, por exemplo, duplicações, melhorando a qualidade do mesmo.

Esses três passos nem sempre serão obrigatórios (muitas vezes você não precisará refatorar o código), da mesma forma que o ciclo red-green-refactor nem sempre será feito apenas uma vez na implementação do código.

Cenário

Uma nova task apareceu, nela precisamos criar um cadastro de bandas e um cadastro de músicas. Uma música pertence a uma banda, uma banda possui várias músicas. Tanto bandas quanto músicas teremos de deixar como obrigatórios alguns campos. Vamos pegar o detalhamento da task para saber exatamente o que precisa ser feito, e , consequentemente, testado.

No cadastro de bandas

  • Campos nome, gênero e site oficial devem ser obrigatórios
  • Uma banda poderá ter várias músicas

No cadastro de músicas

  • Campos título e nome do autor devem ser obrigatórios
  • Cada música pertencerá a uma banda

Model band

Para gerar o model de bandas vamos executar o seguinte comando no terminal:

rails g model band name musical_genre site

Essa será a saída no seu terminal:

invoke active_record
create db/migrate/20160327152119_create_bands.rb
create app/models/band.rb
invoke rspec
create spec/models/band_spec.rb
invoke factory_girl
create spec/factories/bands.rb

O legal de usar generates nesse caso é que ele vai criar pra gente não só o model e migration de band , mas também a factory(spec/factories/bands.rb), arquivo de testes para nosso model (spec/models/band_spec.rb) e, dentro dele, haverá a estrutura inicial para escrevermos nossos testes.

Ao rodar o comando rspec spec, vocês verão a seguinte saída no terminal:

*

Pending: (Failures listed here are expected and do not affect your suite’s status)

1) Band add some examples to (or delete) /home/ricardo/open-source/demo_tdd/spec/models/band_spec.rb
# Not yet implemented
# ./spec/models/band_spec.rb:4

Finished in 0.00114 seconds (files took 7.01 seconds to load)
1 example, 0 failures, 1 pending

Coverage report generated for RSpec to /home/ricardo/open-source/demo_tdd/coverage. 65 / 65 LOC (100.0%) covered.

Ele diz que existe um exemplo(teste), nenhuma falha, porém o único teste que existe está pendente. Logo mais abaixo vemos que o SimpleCov(lembra,né? Aquela gem para verificar a porcentagem de código testado) diz que 100% do nosso código está testado. Ou seja, sim, o SimpleCov nos dá um falso positivo. Então não use as informações geradas por ele como unica forma de verificar se seus testes realmente são eficazes.

Começando pela Factory

Nessa altura do campeonato seu arquivo spec/factories/band.rb deve estar dessa forma:

Vamos fazer uma alteração na nossa factory, usando os poderes da gem Faker, deixando-o desse jeito:

Dessa forma, sempre que precisarmos gerar um novo objeto, nossa factory criará esses objetos com atributos não-estáticos. Caso queira saber mais sobre a gem Faker , basta ir na documentação da mesma (aqui está o link).

band_spec.rb

Aqui as coisas ficam interessantes, pois é onde escreveremos os testes relativos ao model Band.

Testando validações

A primeira coisa que a task pede é que os campos nome, gênero e site oficial devem ser obrigatórios. Precisamos testar isso antes de declarar esses campos como obrigatórios, e contaremos com a ajuda da gem shoulda-matchers que também vimos no post anterior:

Comecei testando a validação do atributo name (linha 4) . Aqui ja podemos ver algumas coisas novas aparecendo e vou falar um pouco sobre cada uma:

is_expected.to > É uma forma de pegar o sujeito implícito (Banda) e testá-lo. Nesse caso estamos testando se o atributo name de Band possui validação de preenchimento obrigatório, e para isso samos o validate_presence_of

validate_presence_of > É um recurso da gem shoulda-matcher para verificar se existem validates para determinado atributo. Existem outras formas de testar validações, você pode encontrar na documentação da gem

Agora chegou a hora de rodar os testes. No terminal execute o seguinte comando para executar somente os testes referentes ao model band:

rspec spec/models/band_spec.rb

F

Failures:

1) Band should validate that :name cannot be empty/falsy
Failure/Error: it { is_expected.to validate_presence_of(:name) }

Band did not properly validate that :name cannot be empty/falsy.
After setting :name to ‹nil›, the matcher expected the Band to be
invalid, but it was valid instead.
# ./spec/models/band_spec.rb:4:in `block (2 levels) in <top (required)>’

Finished in 0.62398 seconds (files took 2.99 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/models/band_spec.rb:4 # Band should validate that :name cannot be empty/falsy

Acima temos a saída do terminal. Nela podemos ver que existe 1 exemplo e uma falha. Isso quer dizer que precisamos implementar o validates em nosso name, dessa forma:

Rode novamente os testes e essa será a saída

.

Finished in 0.59552 seconds (files took 2.57 seconds to load)
1 example, 0 failures

Boa! Um exemplo, nenhuma falha. Criamos o teste, ele quebrou e após isso escrevemos o mínimo de código para ele passar. Por ser pouco código, não há necessidade de refatorá-lo.

Os mais atentos devem ter notado um .(ponto) lá em cima. Isso se deve ao formato de exibição padrão que o Rspec nos dá. Mas podemos exibi-lo em um formato mais claro, tipo um documento. Para mudar isso vamos no nosso arquivo .rspec e adicionamos a seguinte linha:

--format documentation

Vamos rodar novamente nosso teste e teremos a seguinte saída:

Band
should validate that :name cannot be empty/falsy

Finished in 0.59794 seconds (files took 2.59 seconds to load)
1 example, 0 failures

Olha só como a saída dos nossos testes ficaram mais claras. Agora vamos seguir em frente e adicionar os testes de validação para os demais atributos do band_spec.rb, deixando o arquivo dessa forma:

Já nosso model band.rb fica assim:

Testando relacionamentos

Olhando o detalhamento da task, de novo, podemos ver que a segunda coisa que se pede é a declaração de relacionamento de 1 para N entre Band e Music (uma banda tem muitas músicas). A gem shoulda-matchers também dispõe de recursos para testar relacionamentos.

Uma banda pode possuir várias músicas, certo? Então vamos definir isso no nosso teste:

Na linha oito declaramos que banda deverá ter várias músicas. Vamos rodar o teste e ver o que sai no nosso terminal

Band
should validate that :name cannot be empty/falsy
should validate that :musical_genre cannot be empty/falsy
should validate that :site cannot be empty/falsy
should have many musics (FAILED — 1)

Failures:

1) Band should have many musics
Failure/Error: it { is_expected.to have_many(:musics)}
Expected Band to have a has_many association called musics (Music does not exist)
# ./spec/models/band_spec.rb:7:in `block (2 levels) in <top (required)>’

Finished in 0.62976 seconds (files took 2.58 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./spec/models/band_spec.rb:7 # Band should have many musics

Temos uma falha. O teste fala pra gente que Music não existe, e realmente não criamos o model Music. Esse feedback é importante, pois muitas vezes interrompemos nossas atividades ao final do dia e no dia posterior esquecemos em que ponto paramos.Agora vamos criar o model Music:

rails g model music title name_author

Criado o model Music, vamos rodar o teste de novo e vamos conferir nosso terminal:

Band
should validate that :name cannot be empty/falsy
should validate that :musical_genre cannot be empty/falsy
should validate that :site cannot be empty/falsy
should have many musics (FAILED — 1)

Failures:

1) Band should have many musics
Failure/Error: it { is_expected.to have_many(:musics)}
Expected Band to have a has_many association called musics (no association called musics)
# ./spec/models/band_spec.rb:7:in `block (2 levels) in <top (required)>’

Finished in 0.62401 seconds (files took 6.61 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./spec/models/band_spec.rb:7 # Band should have many musics

A notícia boa é que a falha mudou (no association called musics, ao inves de Music does not exist), a má notícia é que ainda temos um teste falhando. O feedback passado é que não existe relacionamento declarado. Vamos informar isso no nosso model?

Agora vamos rodar o teste mais uma vez:

Band
should validate that :name cannot be empty/falsy
should validate that :musical_genre cannot be empty/falsy
should validate that :site cannot be empty/falsy
should have many musics (FAILED — 1)

Failures:

1) Band should have many musics
Failure/Error: it { is_expected.to have_many(:musics)}
Expected Band to have a has_many association called musics (Music does not have a band_id foreign key.)
# ./spec/models/band_spec.rb:7:in `block (2 levels) in <top (required)>’

Finished in 0.62949 seconds (files took 2.56 seconds to load)
4 examples, 1 failure

Failed examples:

rspec ./spec/models/band_spec.rb:7 # Band should have many musics

Pois é, o teste fala que esquecemos de criar a chave estrangeira band_id no model Music. Vamos criá-la:

rails g migration add_band_id_to_musics band_id:integer:index

Mais um rake db:migrate e mais uma vez vamos rodar nossos testes:

Band
should validate that :name cannot be empty/falsy
should validate that :musical_genre cannot be empty/falsy
should validate that :site cannot be empty/falsy
should have many musics

Finished in 0.61291 seconds (files took 4.43 seconds to load)
4 examples, 0 failures

Coverage report generated for RSpec to /home/ricardo/open-source/demo_tdd/coverage. 75 / 75 LOC (100.0%) covered.

Agora sim, 4 testes, nenhuma falha! \o/

Percebam que a gente desenvolveu aqui seguindo os famosos “baby steps”, sempre escrevendo um teste e implementando através do feedback passado pelo próprio teste. Mas esses passos podem ir aumentando na medida em que você se sente confiante em não errar. No meu caso, eu criaria o model Music já com o atributo band_id , pois eu já sabia que seria necessário, devido ao tipo de relacionamento entre os models em questão.

Bom, vou encerrando por aqui essa primeira parte para que o texto não fique muito extenso e mais cansativo do que eu acho que já ficou. Espero que tenham gostado e logo mais eu posto a segunda parte, ok?

O projeto está aqui nesse link.

Lembrando que qualquer feedback , seja positivo ou negativo é só entrar em contato através do meu email, facebook, linkedin ou os comentários aqui em baixo :)

--

--