Test-Driven Development no Rails: Unit Tests

11 de Maio de 2007

Todo mundo fala que Test-Driven Development aumenta sua produtividade, reduz a quantidade de erros do seu código e deixa todo mundo mais feliz. O quem ninguém fala é como fazer isso, quando você não conhece nada de testes. Por isso, resolvi escrever este texto, mostrando o pouco que aprendi nas últimas semanas sobre esse tema.

Test-Driven Development (TDD) — Desenvolvimento Orientado a Testes ou Desenvolvimento Guiado por Testes — é uma técnica de desenvolvimento de software onde primeiro são criados os testes e somente depois é escrito o código necessário para passar por eles. Dessa maneira, você escreverá códigos melhores e, o que é mais importante, muito mais rapidamente. Veja como é o ciclo de TDD, segundo o livro Test-Driven Development by Example, de Kent Back (ISBN-0321146530):

  1. Crie um teste: Cada nova funcionalidade deve começar com um teste escrito. Este teste deve falhar antes da funcionalidade ser implementada. Você deve conhecer claramente os requisitos e especificações da funcionalidade.
  2. Execute todos os testes: Você saberá que a rotina de testes está funcionando corretamente e que o novo teste não passou sem que o teste da funcionalidade tenha sido implementado.
  3. Escreva o código: Escreva o código que irá passar naquele teste que você criou na etapa anterior, sem se preocupar em torná-lo elegante/otimizado. É muito importante que o código implementado reflita somente o teste escrito.
  4. Execute novamente todos os teste: Se todos os testes passarem, você terá certeza que o código atende todos os requisitos testados e que esta nova funcionalidade não afetou outras partes do sistema.
  5. Refatore o código: Agora você pode "limpar" o código, se for necessário. Lembre-se de executar os testes constantemente durante esta etapa, pois só assim você saberá se o sistema não foi modificado de maneira incorreta, gerando erros.

Os testes, quando devidamente implementados, oferecem uma certa "garantia" de que a aplicação está funcionando da maneira como deveria.

TDD no Rails

Este texto não tem a pretensão de ser o "guia definitivo" de TDD; ao invés disso, você verá uma abordagem simples e direta do assunto, utilizando Ruby on Rails. Não irei explicar detalhadamente como desenvolver em Rails; para isso você tem outras fontes um tanto quanto completas.

O que é um teste?
Teste é um método que contém asserções — segundo o dicionário Houaiss, asserção significa "afirmação categórica" — e que representam um cenário de testes em particular. Um teste só passará caso todas as asserções sejam verdadeiras.
No Ruby, um teste é um método iniciado por "test"; assim, você pode nomear seu método como "test_", "testing_", "testando_", e por aí vai!

O Rails trabalha com alguns tipos diferentes de testes. Existem os testes unitários que são responsáveis pelos testes de modelos; existem os testes funcionais, responsáveis por testar os controllers; e, por último, temos os testes de integração, responsáveis por testar múltiplas camadas de seu aplicativo e a integração entre elas.

O teste unitário será, provavelmente, o primeiro lugar onde você irá trabalhar em qualquer projeto. Isso acontece porque não é preciso escrever muito código — vou além e digo que não é preciso escrever nenhum código — para se criar tais testes, a não ser o próprio teste.

Quando estamos fazendo TDD, é importante que todos os seus testes iniciais não passem na validação, pois você precisa identificar os itens a serem validados para depois corrigi-los. Você deve também criar pelo menos um teste que passe na validação.

Pages: 1 2 3 4

Posts relacionados
Como migrar suas Stored Procedures no ambiente de teste
Use o método save! ao escrever seus testes
Test-driven Development no Rails - Começando seu projeto com o pé direito
Test Driven Development
Colorindo o output dos testes no Rails

Comentários #

#1 Davis Zanetti Cabral disse:
11 Maio 07, 09:28AM

Muito bom o artigo. Uma dica para visualizar melhor os testes é o uso do RadRails ou então a instalação do gem RedGreen. Dá uma força bacana na hora de visualizar.

Abraço!

#2 TDD on Rails « Coding Dojo Floripa disse:
11 Maio 07, 09:36AM

[...] Vale a pena conferir. [...]

#3 Klaus Paiva disse:
11 Maio 07, 09:44AM

Muito bom (Ferdi)Nando!

Matou a pau no texto! Ficou muito claro e didático. O trio migrate, tests e fixtures (que eu realmente só conhecia por pouco mais do que o nome) agora me parecem muito mais interessantes!

Valeu! :D

#4 Silfar disse:
11 Maio 07, 10:07AM

Muito show, mas me tira uma dúvida:

def test_should_require_longer_password
user = create(:password => 't')
assert user.errors.invalid?(:password), ":password should be 4 characters or longer"
assert_invalid user, "User shouldn't be created"
end

Pq usar as duas linhas:

assert user.errors….
assert_invalid user……

não entendi pq precisa das duas. Pode me explicar ?

#5 Nando Vieira disse:
11 Maio 07, 10:10AM

Silfar, não precisa. Mas é como eu falei: você pode deixar o seu teste tão completo quanto você queira. Paranoicamente, eu poderia ainda ter adicionado uma asserção para verificar se uma mensagem de erro foi definida. Precisa? Teoricamente, não. Mas é tão simples de fazer que eu realmente não me importo. Ah, mencionei que eu uso snippets para meus testes e que eu só preciso colocar "ai + tab"? :)

#6 Eduardo Fiorezi disse:
11 Maio 07, 11:29AM

Nando, parabéns pela excelente abordagem.

Igual vc falou no comentário, com TDD não é necessário cobrir "100%" das situações, mas é bom cobrir todas necessárias naquele momento… Ficar imaginando tudo que pode acontecer é prejudicial e essas milhões de situações imaginadas talvez nunca apareçam.

Espero os próximos…

#7 Shairon disse:
11 Maio 07, 11:59AM

Muito bom. Estou ansioso para ver o de funcionalidade

#8 Luciano Pacheco disse:
11 Maio 07, 02:30PM

def test_should_require_name
comment = create(:name = nil)
assert comment.errors.invalid?(:name), ":name should have had an error"
assert_invalid comment, "Comment shouldn't be created"
end

":name should have had an error"

Nas mensagens não devemos utilizar mensagens mais explicativa, tipo "name can't be blank" ou algo parecido ?

Estou perguntando, pois também estou iniciando nesse mundo de XP e TDD. :)

Valeu pelo artigo. ;)

#9 Nando Vieira disse:
11 Maio 07, 03:06PM

@Luciano: Melhor do que isso, você pode fazer algo assim:

assert comment.errors.invalid?(:name), ":name should have had an error\n#{comment.errors.full_messages.to_sentence}"

Você decide como quer fazer! ;)

#10 Renato Elias disse:
11 Maio 07, 03:29PM

Mandou bem nando ! Vlw, como você me falou no "praianha" ruby é babaca de tão fácil =)

#11 Diogo Lopes disse:
19 Jun 07, 11:23AM

Parabens cara… nao sei como vim parar aqui, mas estou assinando.
Tem muita gente falando que ABC , XCD , ZZZ é bacana, fundamental, etc… Mas tu mostrou na pratica que vale a pena.
E tudo em Ruby, que é melhor ainda. (até hj, só tinha visto TDD em JAVA em pt)

abcs!

#12 rapha disse:
23 Jun 07, 06:28AM

Fantástico velho. Acabei de escrever os testes de um projeto aqui, e realmente facilita a vida. Agora eu fico muito mais seguro de implementar qualquer funcionalidade, porque eu sei que posso testar tudo de novo em um instante. :D

Rails sem tdd? Nem a pau!

#13 Nome do Jogo » Blog Archive » Vamos falar sobre teste... disse:
05 Out 07, 04:36PM

[...] aprender mais sobre como funcionam os teste no Rails você pode clicar aqui e aqui. E lembrem-se: "Quem não testar é mulher do [...]

#14 davi disse:
15 Nov 07, 04:05PM

Excelente artigo, Nando… :-)

Estou usando isso e o Agile Web Development With Rails e estou gostando.

Mas eu vi uma coisa no livro que você não citou (para simplificar?), o Hash default_error_messages do ActiveRecord::Errors:

@@default_error_messages = {
:inclusion => "is not included in the list",
:exclusion => "is reserved",
:invalid => "is invalid",
:confirmation => "doesn't match confirmation",
:accepted => "must be accepted",
:empty => "can't be empty",
:blank => "can't be blank",
:too_long => "is too long (maximum is %d characters)",
:too_short => "is too short (minimum is %d characters)",
:wrong_length => "is the wrong length (should be %d characters)",
:taken => "has already been taken",
:not_a_number => "is not a number"
}

Eu coloquei o seguinte no meu RAILS_ROOT/test/test_helper.rb:

def assert_activerecord_errors(expected, got, message=")
assert_equal ActiveRecord::Errors.default_error_messages[expected], got, message
end

E agora posso testar as mensagens de erro usando um "simples" assert_activerecord_errors :taken, OBJECT.errors.on(:name).

Abraço

#15 Nando Vieira disse:
15 Nov 07, 04:27PM

@davi, como eu altero as mensagens-padrão do Rails através do atributo :messages, eu utilizo outra forma para validar tais mensagens. Dá uma olhada nos meus helpers[1], para ver como ficou!

[1] http://pastie.caboo.se/114671

#16 Simples Idéias. Por Nando Vieira. » Arquivo » Usando... disse:
01 Jun 08, 11:13PM

[...] muito tempo atrás, escrevi um artigo mostrando como testar uma aplicação Rails usando Test::Unit. Muita coisa aconteceu desde então e [...]

#17 Frolim disse:
24 Jul 08, 12:08PM

Ficou muito bom!
Finalmente iniciei testes unitários com a sua explicação.
Vlw!

#18 Test Helpers -- ArthurGeek.net disse:
04 Set 08, 01:15AM

[...] e se você ainda não entrou nessa de TDD, dê uma olhada neste artigo escrito pelo Nando Vieira sobre Unit Tests. Muito boa referência em português! Posted in [...]

#19 Test Helpers -- ArthurGeek.net disse:
04 Set 08, 12:25PM

[...] e se você ainda não entrou nessa de TDD, dê uma olhada neste artigo escrito pelo Nando Vieira sobre Unit Tests. Muito boa referência em português! Tags: helpers, [...]

#20 Eleudson disse:
20 Fev 09, 08:05AM

Primeiro, parabéns pelo post que me fez encarar o uso de TDD.

Gostaria de tirar uma dúvida com algun de vocês que tenham utilizado os helpers sugeridos pelo Nando em http://pastie.caboo.se/114671. É o seguinte:

A) Quando utilizo a rotina da seguinte forma…

test "should_belongs_to_publication" do
publication = create
assert_associated publication, "editor"
end

… acontece a seguinte falha..

1) Failure: test_should_belongs_to_publication(ReviewTest)
[./test/test_helper.rb:93:in `assert_associated'

expected but was
<#>.

B) Quando ponho, na criação de publication, um publication_id que não existe, dá certo…

test "should_belongs_to_publication" do
publication = create(:publication_id => 100)
assert_associated publication, "editor"
end

Minha dúvida é se o uso correto é este mesmo da opção B ou tem algo errado?

Perdão por por esta dúvida aqui, é que não encontrei referencias sobre o uso destas rotinas na web.

#21 Nando Vieira disse:
20 Fev 09, 12:39PM

Eleudson, na documentação tem uma nota sobre isso. Validar a associação não significa que o objeto deve estar presente. Experimente adicionar também na sua validação algo como validates_presence_of :publication, :publication_id.

#22 Eleudson disse:
20 Fev 09, 02:02PM

O model está assim:
class Review true
validates_presence_of :body
end

A mensagem de erro do post anterior ficou incompleta, por isso a repito a seguir:
1) Failure:
test_should_belongs_to_publication(ReviewTest)
[./test/test_helper.rb:93:in `assert_associated'
./test/unit/review_test.rb:14:in `test_should_belongs_to_publication'

-nil- expected but was
Publication id: 1322847960, editor_id: 1050302101, title: "The great saga of Naruto", isbn: "333333-GHI", source: "www.manganiponweb.net", media: "DVD", idiom: "English", license: "Creative Common", created_at: "2009-02-20 17:50:01", updated_at: "2009-02-20 17:50:01"

Quando eu não ponho um publication_id que não existe, as mudanças deste campo dentro de assert_associated, exemplo record.send("#{attribute}=", nil), não faz mudar o valor de record.publication, que continua apontando para o objeto criado no metodo create. Daí, a assertiva assert_nil(record.send(relationship)) se torna falsa.

Pelo que você falou, tenho mesmo que criar um objeto sem referencia (ex. :publication_id => 100)?

A qual documentação você se refere?

Grato por sua atenção!

#23 Eleudson disse:
20 Fev 09, 02:04PM

Ops!! Model correto sem maior e menor que.

class Review ActiveRecord::Base
belongs_to :publication

validates_associated :publication
validates_presence_of :publication, :publication_id
validates_numericality_of :publication_id, :only_integer => true

validates_presence_of :body
end

#24 Comin Blog » Posts interessantes sobre RubyOnRails! disse:
17 Maio 09, 10:49PM

[...] Já neste link ele fala sobre a programação de testes…que todo mundo fala maravilhas mais ninguém explica como fazer. http://simplesideias.com.br/tdd-no-rails-unit-tests/ [...]

Deixe um comentário




Este blog usa o Gravatar.


Não é aceito código HTML: adicione-o no pastie.org ou paste.milk-it.net e poste apenas o link.

Se este é seu primeiro comentário, ele terá que ser aprovado antes de ser exibido.