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.

Nosso exemplo

Nós vamos criar um sistema de blog — muito mais poderoso que o WordPress :P — totalmente feito em Rails. Então, a primeira coisa que temos que fazer é pensar nos requisitos de nosso projeto. Isso é importante, pois permite ter uma visão melhor do que precisa ser feito. Obviamente, podemos ajustar tais requisitos ao longo do tempo. A princípio, nosso blog deve:

  • permitir configurações sobre o autor (nome, email, etc)
  • criar posts com resumo
  • permitir que usuários postem comentários, informando email, nome e website

Completo, não? :)

Para começar, vamos criar nossa aplicação. Digite o comando rails blog. Nosso projeto será criado e a lista dos arquivos será exibida. Iremos, então, criar nosso banco de dados — MySQL, neste exemplo — tanto de desenvolvimento quanto de testes. Se você não se sente confortável com a linha de comandos, faça da maneira como está acostumado.

~$ mysqladmin -u root create blog_development
~$ mysqladmin -u root create blog_test

Abra o arquivo "config/database.yml" e insira o usuário e senha que terão acesso aos bancos de dados. Meu arquivo se parece com isso:

development:
  adapter: mysql
  database: blog_development
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
 
test:
  adapter: mysql
  database: blog_test
  username: root
  password:
  socket: /var/run/mysqld/mysqld.sock
 
production:
  adapter: mysql
  database: blog_production
  username: root
  password: 
  socket: /var/run/mysqld/mysqld.sock

É muito importante que você defina 2 bancos diferentes para desenvolvimento e testes, uma vez que o banco de dados "testes" é apagado quando estamos testando nossa aplicação.

Quando nosso desenvolvimento é orientado a testes, você inicialmente só cria os modelos e, logo depois, parte para os testes. Controllers? Não, agora. Você só irá criá-los muito mais à frente. Vamos trabalhar inicialmente no modelo "usuário".

~/blog$ script/generate model User
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/user.rb
      create  test/unit/user_test.rb
      create  test/fixtures/users.yml
      create  db/migrate
      create  db/migrate/001_create_users.rb

O Rails nos permite trabalhar com DDLs muito facilmente através das migrations. Então, neste texto não iremos lidar com SQL diretamente, mas Ruby.

Abra o arquivo "db/migrate/001_create_users.rb". Nossa tabela de usuários terá os campos "name", "email" e "password". Sua migração deverá ser algo como:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.column :name, :string, :null => false
      t.column :email, :string, :null => false
      t.column :password, :string, :null => false
    end
  end
 
  def self.down
    drop_table :users
  end
end

Execute o comando rake db:migrate para criar a tabela "users".

~/blog$ rake db:migrate
(in /home/nando/blog)
== CreateUsers: migrating =====================================================
-- create_table(:users)
   -> 0.0035s
== CreateUsers: migrated (0.0037s) ============================================

Com a tabela criada, podemos meter a mão na massa!

Abra o arquivo "test/unit/user_test.rb", que foi gerado automaticamente quando criamos nosso modelo. Uma das vantagens de se desenvolver em Rails é justamente esta; é tão simples de se criar testes para uma aplicação, com arquivos criados automaticamente, que você deve se sentir envergonhado de não fazê-lo.

Este arquivo possui uma única asserção chamada test_truth. Apesar de parecer inútil, ela ajuda a corrigir algumas configurações do ambiente, como quando o banco de dados de teste não existe, por exemplo.

require File.dirname(__FILE__) + '/../test_helper'
 
class UserTest < Test::Unit::TestCase
  fixtures :users
 
  # Replace this with your real tests.
  def test_truth
    assert true
  end
end

Para rodarmos nossos testes unitários, devemos executar o comando rake test:units. O Ruby irá executar os testes unitários e receberemos uma resposta como esta:

Started
.
Finished in 0.03095 seconds.
 
1 tests, 1 assertions, 0 failures, 0 errors

Esta resposta é bastante direta e fácil de entender. Cada ponto exibido na tela (logo abaixo da linha "Started") representa um teste que passou. Temos também uma linha que nos diz que foi executado 1 teste, com 1 asserção, mas que não retornou erro ou falha.

O teste que vem por padrão não faz muita coisa, então vamos criar o nosso! Nosso primeiro modelo a ser testado é o User. Alguns testes possíveis são:

  • nome, email e senha são obrigatórios
  • a senha deve ter no mínimo 6 caracteres
  • o e-mail é único

Podemos escrever um teste genérico para ver se o usuário é criado quando não passamos nenhuma informação.

def test_should_be_invalid
  user = User.create
  assert !user.valid?, "User shouldn't be created"
end

Primeiro, nós criamos um usuário (User.create) sem passar nenhuma informação. Se nosso modelo tivesse uma validação utilizando os métodos disponíveis do ActiveRecord, o método user.valid? retornaria false e nossa aplicação passaria nos testes. Rodando os testes temos uma surpresa:

~/blog$ rake test:units
Started
F
Finished in 0.050156 seconds.
 
  1) Failure:
test_should_be_invalid(UserTest) [./test/unit/user_test.rb:8]:
User shouldn't be created.
<false> is not true.
 
1 tests, 1 assertions, 1 failures, 0 errors
rake aborted!

Alguma coisa não está funcionando direito! Nosso teste deveria receber false do método valid?, o que não aconteceu. Não se preocupe em fazer o teste passar. Lembre-se que antes devemos criar os outros testes. Vamos, então, criar cada um dos testes em separado.

Não sei se você notou, mas ficou complicado entender a condição assert !user.valid? no teste que criamos. Para estes casos, podemos utilizar helpers, semelhantes ao que utilizamos nas views, mas que aqui são específicos para os testes. Abra o arquivo "tests/test_helper.rb" e adicione os métodos abaixo:

def deny(condition, message='')
  assert !condition, message
end
 
def assert_invalid(record, message='')
  deny record.valid?, message
end

O método deny faz a negativa de assert e o método assert_invalid apenas dá uma força, evitando que tenhamos que explicitar o .valid? toda vez. Não se preocupe em verificar se o método valid? existe ou não; nos testes, assumimos um ambiente e ele deve ser verdadeiro e, caso não seja, investigamos as causas do erro que foi apontado para então corrigí-lo.

Troque o método test_should_be_invalid que criamos anteriormente por este que utiliza nossos helpers.

def test_should_be_invalid
  user = User.create
  assert_invalid user, "User shouldn't be created"
end

Muito melhor, certo? E assim, você vive sem a culpa de ir contra o princípio DRY

Agora, temos que adicionar outros testes. Antes disso, já prevendo mais um pouco de repetição, vamos criar um método chamado create para nos ajudar. É assim que sua classe de testes deve estar neste momento.

require File.dirname(__FILE__) + '/../test_helper'
 
class UserTest < Test::Unit::TestCase
  fixtures :users
 
  def test_should_be_invalid
    user = create(:name => nil, :email => nil, :password => nil)
    assert_invalid user, "User shouldn't be created"
  end
 
  private
    def create(options={})
      User.create({
        :name => "Homer Simpson",
        :email => "homer@simpsons.com",
        :password => "test"
        }.merge(options))
    end
end

O método create será responsável por definir os valores padrão para os campos. Assim, não teremos que digitá-los toda vez que quisermos adicionar um teste.

Os outros testes que iremos criar irão verificar as condições impostas lá em cima. Vamos começar pelo teste que verifica se o nome foi informado.

def test_should_require_name
  user = create(:name => nil)
  assert user.errors.invalid?(:name), ":name should be required"
  assert_invalid user, "User shouldn't be created"
end

Não mudou muita coisa do primeiro teste que fizemos. Apenas adicionamos mais uma asserção que verifica se o campo "name" é inválido. No ActiveRecord, temos os métodos validates_* que necessitam do nome do campo; toda vez que uma validação não passa, um erro é adicionado ao campo. Além de verificar se nosso campo possui um erro, poderíamos verificar se uma mensagem também foi definida. A seguinte asserção faz justamente isso.

assert_not_nil user.errors.on(:name), ":name should have had a error message"

E os outros testes:

require File.dirname(__FILE__) + '/../test_helper'
 
class UserTest < Test::Unit::TestCase
  fixtures :users
 
  def test_should_be_invalid
    user = create(:name => nil, :email => nil, :password => nil)
    assert_invalid user, "User shouldn't be created"
  end
  
  def test_should_require_name
    user = create(:name => nil)
    assert user.errors.invalid?(:name), ":name should be required"
    assert_invalid user, "User shouldn't be created"
  end
  
  def test_should_require_email
    user = create(:email => nil)
    assert user.errors.invalid?(:email), ":email should be required"
    assert_invalid user, "User shouldn't be created"
  end
  
  def test_should_deny_bad_email
    user = create(:email => 'bad@format')
    assert user.errors.invalid?(:email), ":email should be in a valid format"
    assert_invalid user, "User shouldn't be created"
  end
  
  def test_should_require_password
    user = create(:password => nil)
    assert user.errors.invalid?(:password), ":password should be required"
    assert_invalid user, "User shouldn't be created"
  end
  
  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
  
  def test_should_deny_duplicate_user
    user = create
    assert_valid user
    
    user = create
    assert_invalid user, "User shouldn't be created"
  end
  
  private
    def create(options={})
      User.create({
        :name => "Homer Simpson",
        :email => "homer@simpsons.com",
        :password => "test"
        }.merge(options))
    end
end

Execute os testes e veja que uma longa lista de erros irá aparecer.

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
F.FFFFFF
Finished in 0.073839 seconds.

  1) Failure:
test_should_be_invalid(UserTest)
    [./test/unit/../test_helper.rb:29:in `deny'
     ./test/unit/../test_helper.rb:33:in `assert_invalid'
     ./test/unit/user_test.rb:8:in `test_should_be_invalid']:
User shouldn't be created.
<false> is not true.

  2) Failure:
test_should_deny_bad_email(UserTest) [./test/unit/user_test.rb:25]:
:email should be in a valid format.
<false> is not true.

  3) Failure:
test_should_deny_duplicate_user(UserTest)
    [./test/unit/../test_helper.rb:29:in `deny'
     ./test/unit/../test_helper.rb:33:in `assert_invalid'
     ./test/unit/user_test.rb:46:in `test_should_deny_duplicate_user']:
User shouldn't be created.
<false> is not true.

  4) Failure:
test_should_require_email(UserTest) [./test/unit/user_test.rb:19]:
:email should be required.
<false> is not true.

  5) Failure:
test_should_require_longer_password(UserTest) [./test/unit/user_test.rb:37]:
:password should be 4 characters or longer.
<false> is not true.

  6) Failure:
test_should_require_name(UserTest) [./test/unit/user_test.rb:13]:
:name should be required.
<false> is not true.

  7) Failure:
test_should_require_password(UserTest) [./test/unit/user_test.rb:31]:
:password should be required.
<false> is not true.

8 tests, 9 assertions, 7 failures, 0 errors

Foram executados 8 testes, com 9 asserções, sendo que 7 falharam. O único teste que passou foi test_should_create_user, como era de se esperar. O que temos que fazer agora? Criar o código que irá passar nestes testes. No caso dos testes unitários isso é bastante simples. Você trabalha basicamente com modelos, então, abra o arquivo "app/models/user.rb". Você não precisa resolver os testes que falharam na ordem em que foram exibidos. Comece pelo que você julgar ser mais simples e com menor dependência. Que tal começarmos pela falha 4: :email should be required. Esta falha é bastante simples de se resolver, bastando que você coloque o método validates_presence_of no modelo. Por equivalência, também podemos resolver as falhas 6 e 7.

class User < ActiveRecord::Base
  validates_presence_of :email
  validates_presence_of :name
  validates_presence_of :password
end

Execute os testes Agora você verá que 12 asserções foram executadas mas que apenas 3 falharam. Muito mais interessante que o nosso teste anterior!

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
..FF.F..
Finished in 0.065069 seconds.

  1) Failure:
test_should_deny_bad_email(UserTest) [./test/unit/user_test.rb:25]:
:email should be in a valid format.
<false> is not true.

  2) Failure:
test_should_deny_duplicate_user(UserTest)
    [./test/unit/../test_helper.rb:29:in `deny'
     ./test/unit/../test_helper.rb:33:in `assert_invalid'
     ./test/unit/user_test.rb:46:in `test_should_deny_duplicate_user']:
User shouldn't be created.
<false> is not true.

  3) Failure:
test_should_require_longer_password(UserTest) [./test/unit/user_test.rb:37]:
:password should be 4 characters or longer.
<false> is not true.

8 tests, 12 assertions, 3 failures, 0 errors

Vamos validar o atributo password: ele não deve ter menos que 6 caracteres. Basta adicionar o validador abaixo ao seu modelo.

validates_length_of :password, :minimum => 4

Mais uma vez, execute os testes. Apenas 2 testes falharam: test_should_deny_bad_email e test_should_deny_duplicate_user. Para, finalmente, passar por todos os testes, adicione os métodos abaixo.

validates_uniqueness_of :email, :case_sensitive => false
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i

Ao executar os testes, teremos uma resposta muito mais agradável!

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
........
Finished in 0.082506 seconds.

8 tests, 14 assertions, 0 failures, 0 errors

Sim! Todos os nossos testes passaram e não sei se você percebeu mas o esforço foi praticamente nulo. Agora, seguindo nossos requisitos, iremos implementar os posts.

No modelo Post, devemos escrever testes para validar os seguintes itens:

  • um autor pode ter inúmeros posts
  • os comentários podem ser permitidos ou não
  • o resumo é opcional, mas se for informado não deve ultrapassar 250 caracteres

Como ainda não temos o modelo Post, vamos criá-lo:

script/generate model Post

Abra o arquivo de migração 002_create_posts.rb e adicione o código abaixo.

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.column :title, :string, :limit => 250, :null => false
      t.column :excerpt, :string, :limit => 250, :null => true
      t.column :body, :text, :null => false
      t.column :created_at, :datetime
      t.column :updated_at, :datetime
      t.column :allow_comments, :boolean, :default => true, :null => false
      t.column :user_id, :integer, :null => false
    end
  end
 
  def self.down
    drop_table :posts
  end
end

O código acima dispensa maiores explicações. Execute o comando rake db:migrate para criarmos a tabela de posts.

~/blog$ script/generate model Post
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/post.rb
      create  test/unit/post_test.rb
      create  test/fixtures/posts.yml
      exists  db/migrate
      create  db/migrate/002_create_posts.rb

Já podemos criar os testes necessários para validar o modelo de posts. Como nossos testes dependem do modelo User — o post pertece a um autor — temos que carregar alguns usuários no banco de dados. Isso pode ser feito com fixtures.

O que são fixtures?
Fixtures são conteúdos de um modelo — ou modelos — que serão carregados no banco de dados para a execução dos testes.

As fixtures podem ser carregadas através de SQL (INSERT INTO ...), arquivos CSV ou, preferencialmente, arquivos YAML. Cada arquivo YAML de conter dados de um único modelo. O nome do arquivo de fixtures deve ser igual ao nome da tabela do banco de dados com a extensão .yml. O Rails cria estes arquivos para você, automaticamente, toda vez que você cria uma migração ou modelo.

# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
  id: 1
two:
  id: 2

O arquivo de fixtures é composto por diversos blocos que são equivalentes a registros do banco de dados. Lembre-se: use tabulação separadas por espaços. Vamos editar o arquivo "test/fixtures/users.yml" para adicionar alguns usuários válidos.

bart:
  id: 1
  name: Bart Simpson
  email: bart@simpsons.com
  password: test

krusty:
  id: 2
  name: Krusty The Clown
  email: krusty@simpsons.com
  password: test

Agora, abra o arquivo "test/unit/post_test.rb" e carregue as fixtures de usuários.

fixtures :posts, :users

O mais interessante de se utilizar fixtures é que você recebe automaticamente um método com o mesmo nome da tabela de banco de dados e cada registro pode ser acessado pelo nome — bart e krusty, no nosso caso — que você definiu no arquivo de fixtures. Utilize nomes significativos sempre que puder.

Vamos aproveitar e já criar algumas fixtures de posts. Abra o arquivo "test/unit/fixtures/posts.yml" e adicione o texto abaixo.

rails_rules:
  id: 1
  title: Rails rules
  body: Rails is a killer framework built with Ruby
  created_at: <%= Time.now %>
  updated_at: <%= Time.now %>
  user_id: 1
  allow_comments: false

ruby_rules:
  id: 2
  title: Ruby also rules
  body: Ruby is a charming language
  created_at: <%= Time.now %>
  updated_at: <%= Time.now %>
  user_id: 1
  allow_comments: true

Sim, você pode utilizar código Ruby dentro do arquivo de fixtures! Isso é extramente útil quando você precisa chamar algum método de um modelo (para criptografar a senha, por exemplo) ou trabalhar com datas, como é o nosso caso.

Vamos preparar a nossa classe, adicionando o método create, da mesma maneira que criamos nos testes do modelo User.

require File.dirname(__FILE__) + '/../test_helper'
 
class PostTest < Test::Unit::TestCase
  fixtures :posts, :users
 
  # Replace this with your real tests.
  def test_truth
    assert true
  end
  
  private
    def create(options={})
      Post.create({
        :title => 'Title',
        :excerpt => 'Excerpt',
        :body => 'Body',
        :allow_comments => true,
        :user_id => 1
      }.merge(options))
    end
end

Nossos primeiros teste irão validar os campos obrigatórios.

def test_should_be_invalid
  post = create(:title => nil, :excerpt => nil, 
      :body => nil, :allow_comments => nil, :user_id => nil)
  assert_invalid post, "Post shouldn't be created"
end
 
def test_should_require_title
  post = create(:title => nil)
  assert post.errors.invalid?(:title), ":title should be required"
  assert_invalid post, "Post shouldn't be created"
end
 
def test_should_require_body
  post = create(:body => nil)
  assert post.errors.invalid?(:body), ":body should be required"
  assert_invalid post, "Post shouldn't be created"
end
 
def test_should_require_author
  post = create(:user_id => nil)
  assert post.errors.invalid?(:user_id), ":user_id should be required"
  assert_invalid post, "Post shouldn't be created"
end

O resumo pode ter no máximo 250 caracteres mas é opcional. Então vamos aos testes.

def test_should_accept_excerpt
  post = create(:excerpt => 'Testing excerpt')
  deny post.errors.invalid?(:excerpt), ":excerpt should have been valid"
  assert_valid post
end
 
def test_should_deny_long_excerpt
  post = create(:excerpt => "a" * 251)
  assert post.errors.invalid?(:excerpt), ":excerpt should have had an error"
  assert_invalid post, "Post shouldn't be created"
end

Temos que verificar agora se o usuário existe e se o post foi corretamente associado a ele. Nossos testes:

def test_should_deny_non_integer_user
  post = create(:user_id => 'a')
  assert post.errors.invalid?(:user_id), ":user_id should have had an error"
  assert_invalid post, "Post shouldn't be created"
  
  post = create(:user_id => 1.397)
  assert post.errors.invalid?(:user_id), ":user_id should have had an error"
  assert_invalid post, "Post shouldn't be created"
end

def test_should_check_post_authorship
  # check all fixtures were loaded
  assert_equal 2, users(:bart).posts.size, "user should have had 2 posts"
  
  # assign a post without user_id
  post = create(:user_id => nil)
  
  # then, assign a post using the relationship method
  users(:bart).posts << post
  
  #now, check if user have one more post
  assert_equal 3, users(:bart).posts.size, "user should have had 3 posts"
  
  # assign a post to a user that doesn't exist
  post = create(:user_id => 100)
  assert post.errors.invalid?(:user), "User doesn't exist, so it should be required"
end

E aqui temos um novo método de asserção: assert_equal. Esse método verifica se dois valores são iguais. Veja alguns métodos de asserção que você pode usar.

assert(boolean, message)
Se o parâmetro boolean for nil ou false a asserção irá falhar.
assert_equal(expected, actual, message)
assert_not_equal(expected, actual, message)
A asserção irá falhar a menos que expected e actual sejam iguais/diferentes.
assert_nil(object, message)
assert_not_nil(object, message)
A asserção irá falhar a menos que object seja/não seja nil.
assert_raise(Exception, ..., message) { block... }
assert_not_raise(Exception, ..., message) { block... }
A asserção irá falhar a menos que block dispare/não dispare um erro da exceção especificada.
assert_match(pattern, string, message)
assert_no_match(pattern, string, message)
A asserção irá falhar a menos que string seja/não seja correspondente à expressão regular pattern.
assert_valid(record)
Falha a menos que record não tenha erros de validação.

Na parte dois deste artigo você verá outros métodos de asserção disponíveis para testes dos controllers.

E ao rodarmos os testes unitários, temos...

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
.FEFFFFF.......
Finished in 0.098366 seconds.

  1) Failure:
test_should_be_invalid(PostTest)
    [./test/unit/../test_helper.rb:30:in `deny'
     ./test/unit/../test_helper.rb:34:in `assert_invalid'
     ./test/unit/post_test.rb:9:in `test_should_be_invalid']:
Post shouldn't be created.
<false> is not true.

  2) Error:
test_should_check_post_authorship(PostTest):
NoMethodError: undefined method `posts' for #<User:0xb7334d2c>
    /usr/lib/ruby/gems/1.8/gems/activerecord-1.15.3/lib/active_record/base.rb:1860:in `method_missing'
    ./test/unit/post_test.rb:49:in `test_should_check_post_authorship'

  3) Failure:
test_should_deny_long_excerpt(PostTest) [./test/unit/post_test.rb:38]:
:excerpt should have had an error.
<false> is not true.

  4) Failure:
test_should_deny_non_integer_user(PostTest)
    [./test/unit/../test_helper.rb:30:in `deny'
     ./test/unit/../test_helper.rb:34:in `assert_invalid'
     ./test/unit/post_test.rb:44:in `test_should_deny_non_integer_user']:
Post shouldn't be created.
<false> is not true.

  5) Failure:
test_should_require_author(PostTest) [./test/unit/post_test.rb:26]:
:user_id should be required.
<false> is not true.

  6) Failure:
test_should_require_body(PostTest) [./test/unit/post_test.rb:20]:
:body should be required.
<false> is not true.

  7) Failure:
test_should_require_title(PostTest) [./test/unit/post_test.rb:14]:
:title should be required.
<false> is not true.

15 tests, 21 assertions, 6 failures, 1 errors

... uma verdadeira catástrofe! Um erro no teste test_should_check_post_authorship nos diz que o método posts não existe. Mas parando para pensar, faz todo sentido, já que nós ainda não definimos o relacionamento entre os modelos. Vamos tratar este erro apenas colocando o relacionamento no modelo User.

class User < ActiveRecord::Base
  has_many :posts, :dependent => :destroy
  
  #[...]
end

Note que apenas exibi o código relevante a esta alteração; as validações anteriores permanecem e são representadas aqui por #[...]. Após adicionar esta linha, você já tem o relacionamento entre posts e usuários e se você rodar os testes agora, apenas as falhas serão exibidas.

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
.FFFFFFF........
Finished in 0.142015 seconds.

  1) Failure:
test_should_be_invalid(PostTest)
    [./test/unit/../test_helper.rb:29:in `deny'
     ./test/unit/../test_helper.rb:33:in `assert_invalid'
     ./test/unit/post_test.rb:9:in `test_should_be_invalid']:
Post shouldn't be created.
<false> is not true.

  2) Failure:
test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:63]:
User doesn't exist, so it should be required.
<false> is not true.

  3) Failure:
test_should_deny_long_excerpt(PostTest) [./test/unit/post_test.rb:38]:
:excerpt should have had an error.
<false> is not true.

  4) Failure:
test_should_deny_non_number_user(PostTest) [./test/unit/post_test.rb:44]:
:user_id should have had an error.
<false> is not true.

  5) Failure:
test_should_require_body(PostTest) [./test/unit/post_test.rb:20]:
:body should be required.
<false> is not true.

  6) Failure:
test_should_require_title(PostTest) [./test/unit/post_test.rb:14]:
:title should be required.
<false> is not true.

  7) Failure:
test_should_require_user(PostTest) [./test/unit/post_test.rb:26]:
:user_id should be required.
<false> is not true.

16 tests, 25 assertions, 7 failures, 0 errors

Vamos às validações mais triviais utilizando o método validates_presence_of.

class Post < ActiveRecord::Base
  validates_presence_of :title
  validates_presence_of :body
  validates_presence_of :user_id
end
~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
..FFF...........
Finished in 0.133536 seconds.

  1) Failure:
test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:63]:
User doesn't exist, so it should be required.
<false> is not true.

  2) Failure:
test_should_deny_long_excerpt(PostTest) [./test/unit/post_test.rb:38]:
:excerpt should have had an error.
<false> is not true.

  3) Failure:
test_should_deny_non_number_user(PostTest) [./test/unit/post_test.rb:44]:
:user_id should have had an error.
<false> is not true.

16 tests, 28 assertions, 3 failures, 0 errors

A coisa já melhorou bastante. As três falhas restantes são relativamente simples de resolver. Primeiro vamos verificar se o user_id é um número.

validates_numericality_of :user_id, :only_integer => true

A falha relativa ao tamanho do resumo pode ser resolvido com uma validação como esta:

validates_length_of :excerpt, :maximum => 250, :if => :check_excerpt?
 
private
  def check_excerpt?
    !self.excerpt.blank?
  end

E agora, só mais uma falha para corrigir. Estamos ficando bons nisso!

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
..F.............
Finished in 0.149596 seconds.

  1) Failure:
test_should_check_post_authorship(PostTest) [./test/unit/post_test.rb:67]:
User doesn't exist, so it should be required.
<false> is not true.

16 tests, 32 assertions, 1 failures, 0 errors

Para corrigir esta falha, você deve primeiro definir que um post está associado a um usuário. Nós fizemos apenas o outro caminho, dizendo que um usuário possui diversos posts. Altere o seu modelo Post, adicionando o relacionamento belongs_to :user. Agora, você poderá adicionar as validações relativas a esta falha.

class Post < ActiveRecord::Base
  belongs_to :user
  
  validates_associated :user
  validates_presence_of :user
  #[..]
end

Perceba que estamos validando a presença do atributo/método user e não user_id. A mesma coisa está sendo feita na segunda parte do teste test_should_check_post_authorship. Isso deve ser feito para se validar a associação entre um post e um usuário, de modo que o usuário deve realmente existir; caso contrário, teriamos uma associação incorreta no teste, já que o usuário com id 100 não existe.

Parabéns! Mais um modelo foi devidamente testado.

~/blog$ rake test:units
Loaded suite /usr/lib/ruby/1.8/rake/rake_test_loader
Started
................
Finished in 0.141916 seconds.

16 tests, 32 assertions, 0 failures, 0 errors

Falta apenas mais um modelo para testarmos: são os comentários. Crie o modelo Comment e abra o arquivo de migração "db/migrate/003_create_comments.rb". Ele deve se parecer com isto:

class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :post_id, :integer, :null => false
      t.column :name, :string, :limit => 100, :null => false
      t.column :email, :string, :limit => 100, :null => false
      t.column :url, :string, :limit => 255, :null => true
      t.column :created_at, :datetime
      t.column :active, :boolean, :default => false, :null => false
      t.column :body, :text, :null => false
    end
  end
 
  def self.down
    drop_table :comments
  end
end

Os requisitos para este modelo são:

  • um comentário deve estar associado a um post
  • só é possível comentar em posts que estão com esta opção ativada
  • nome, comentário e email são obrigatórios; a URL é opcional.

Execute a migração e abra o arquivo "test/unit/comment_test.rb". Vamos criar nossos testes. Os testes são muito semelhantes aos criados anteriormente, por isso, irei apenas colocá-los aqui, sem explicações.

require File.dirname(__FILE__) + '/../test_helper'
 
class CommentTest < Test::Unit::TestCase
  fixtures :comments, :posts
  
  def test_should_be_created
    comment = create(:post_id => posts(:ruby_rules).id)
    assert_valid comment
  end
  
  def test_should_be_invalid
    comment = create(:email => nil, :name => nil, :url => nil, :body => nil)
    assert_invalid comment, "Comment shouldn't be created"
  end
  
  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
  
  def test_should_require_email
    comment = create(:email => nil)
    assert comment.errors.invalid?(:email), ":email should have had an error"
    assert_invalid comment, "Comment shouldn't be created"
  end
  
  def test_should_deny_bad_email
    comment = create(:email => 'bad@format')
    assert comment.errors.invalid?(:email), ":email should be in a valid format"
    assert_invalid comment, "Comment shouldn't be created"
  end
  
  def test_should_require_comment
    comment = create(:body => nil)
    assert comment.errors.invalid?(:body), ":body should have had an error"
    assert_invalid comment, "Comment shouldn't be created"
  end
  
  def test_should_require_post
    comment = create(:post_id => nil)
    assert comment.errors.invalid?(:post_id), ":post_id should have had an error"
    assert_invalid comment, "Comment shouldn't be created"
    
    comment = create(:post_id => 100)
    assert comment.errors.invalid?(:post), "Post doesn't exist so it should be required"
  end
  
  def test_cannot_comment_because_post_is_closed
    comment = create(:post_id => posts(:rails_rules).id)
    assert_invalid comment, "Comment shouldn't be created"
  end
  
  private
    def create(options={})
      Comment.create({
        :email => 'burns@simpsons.com',
        :name => 'Mr Burns',
        :url => 'http://thesimpsons.com/burns/',
        :body => "Get em', Smithers.",
        :post_id => 2
      }.merge(options))
    end
end

Abra também o arquivo "test/fixtures/comments.yml" e adicione as fixtures abaixo:

comment_on_ruby_post:
  id: 1
  name: Bart Simpson
  email: bart@thesimpsons.com
  url: http://thesimpsons.com/bart
  body: Heya!
  post_id: 2
  created_at: <%= Time.now %>
  active: 0

another_comment_on_ruby_post:
  id: 2
  name: Bart Simpson
  email: bart@thesimpsons.com
  url: http://thesimpsons.com/bart
  body: Heya!
  post_id: 2
  created_at: <%= Time.now %>
  active: 0

comment_on_rails_post:
  id: 3
  name: Principal Skinner
  email: skinner@thesimpsons.com
  url: http://thesimpsons.com/skinner
  body: Bart, you'll be in detention forever!
  post_id: 1
  created_at: <%= Time.now %>
  active: 1

Ao executar estes testes teremos muitas falhas e apenas 1 erro. Novamente, o erro está ligado ao relacionamento que não criamos. Para corrigí-lo, altere o modelo Post.

class Post < ActiveRecord::Base
  has_many :comments, :dependent => :destroy
  #[...]
end

Para validar nosso modelo, basta adicionar os validadores abaixo:

class Comment < ActiveRecord::Base
  belongs_to :post
  
  validates_associated :post
  validates_presence_of :post
  validates_presence_of :post_id
  validates_numericality_of :post_id, :only_integer => true
  
  validates_presence_of :name
  validates_presence_of :email
  validates_presence_of :body, :message => "Don't you wanna comment this post?"
  validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
  
  private
    def validate
      if post
        errors.add_to_base("Comments are closed") unless post.allow_comments
      end
    end
end

A única coisa que você pode estranhar é o método validate. Nele, verifico se um post foi passado para, então, checar se o post está aberto para comentário ou não.

Dicas e considerações

Autotest

Se você se irrita em ter que executar manualmente o rake test:units ou acha que testar é muito lento, dê uma olhada no ZenTest, especificamente no autotest. O autotest roda os testes para você automaticamente e o que é melhor, apenas os métodos que foram alterados. Dessa forma, você tem um feedback muito mais rápido.

Para instalá-lo, execute sudo gem install zentest --include-dependencies -y. Depois, basta executar autotest na raíz do seu aplicativo. No nuby on rails tem um screencast mostrando o autotest em funcionamento.

Usando o logger dentro dos métodos de teste

Se você, por alguma razão, quiser uilizar o logger dentro dos Testes de Unidade, adicione o seguinte método como helper de teste.

def logger
  RAILS_DEFAULT_LOGGER
end

Lembre-se que isso também pode ser feito no seu modelo.

Teste, codifique, teste, codifique

Você pode demorar algum tempo até se acostumar em testar antes de codificar, mas acredite, vale muito a pena! Você se torna mais produtivo e seu código terá menos bugs. Quanto mais completo seus testes forem, maior a certeza de que tudo está funcionando como deveria. O caminho contrário — codificar primeiro, testar depois — pode parecer mais fácil no começo, mas à medida que seu código cresce, você acabará esquecendo de testar alguma funcionalidade importante. Se isso acontecer, torça para que não aconteça nenhum bug.

Nossa, seu exemplo não é nada DRY

Não sei se você percebeu mas as mensagens de erro se repente em diversos testes. Fiz isso para deixar o exemplo o mais didático possível. Se isto te incomoda, remova todas as mensagens. Elas são opcionais.

Queima! Ele não criptografou as senhas....

No nosso exemplo, a senha foi armazenada sem nenhum tipo de criptografia. Nunca, jamais, em hipótese alguma, armazene informações importantes como a senha de maneira "raw". Não fiz isso aqui para não complicar.

E para finalizar...

Os seus testes podem ser mais completos que estes que fizemos. Se você não tem idéia do que testar, faça sempre uma lista dos requisitos para resolver o problema em questão. Do ponto de vista técnico, procure por projetos desenvolvidos em Rails e analise a suíte de testes. Com certeza você encontrará muito coisa legal.

A continuação deste artigo será sobre Testes Funcionais. Não tenho a menor idéia de quando irei escrevê-lo, mas espero que não demore. Dúvidas, críticas ou sugestões? Poste um comentário!

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/ [...]

#25 Flávio Alves disse:
22 Nov 09, 09:09AM

MUITO bom, Nando!
Parabéns e MUITO obrigado!
[]s

#26 Luiz disse:
17 Jul 10, 12:38AM

Nando, na criação das colunas não deveria ser " :null => false " ao invés de " :nil => false " ? Uso o MySQL e fez diferença, o :nil não fez cócegas nele.

Ao usar :null , logo no primeiro teste - !user.valid? - apareceu um erro e não uma falha como no seu resultado, já que o gerenciador do banco não permite a gravação, óbvio.

E as coisas não tomariam um caminho um pouco diferente da sua sequência a partir daí? Sou novato nessa área, estou aprendendo. Obrigado.

#27 Nando Vieira disse:
18 Jul 10, 09:39PM

Luiz, bem visto! Já atualizei o post.

#28 Luiz disse:
19 Jul 10, 12:55AM

Legal, Nando, só que a repercussão desse acerto, na prática do seu laboratório, criará a necessidade de outro(s) acerto(s) no seu post.

No segundo teste, quando se verifica se o usuário é "válido" executando o "User.create", com o banco configurado com aqueles campos para não aceitarem "null", a execução do teste acusaria um erro e não a falha que você mostrou. Com algum erro, o teste para. Acontece erro porque o gerenciador do banco não aceita salvar "null" nos campos em questão.

Seria muito legal se você refizesse a sequência do seu roteiro e atualizasse o post, pois esse seu material é um dos raros que encontrei na net que tenta ser o mais completo possível na demonstração da (boa) prática do TDD. Tenho encontrado só material incompleto, até mesmo o material do "Guides" nesse assunto é cheio de lacunas. Obrigado

#29 Mateus disse:
20 Ago 10, 10:11AM

Estou usando ubuntu, Rails 2.3.5, ruby 1.8.6 e ao seguir o tutorial ao chegar na criação do teste me foi criado o arquivo test/unit/user_test.rb com a seguinte classe:

"class UserTest < ActiveSupport::TestCase"

diferente da mostrada no tutorial que era class UserTest < Test::Unit::TestCase alguém pode me clarear? Thanks!

#30 Mateus disse:
20 Ago 10, 03:43PM

Também tive os mesmo problemas do amigo de cima, se você esta usando o mysql e colocando os campos como not null o desc do mysql fica assim:

http://pastebin.com/V3U8Cq9F

E ele não permite fazer uma inserção com 'nil' e da um Error não uma Failure no ActiveRecorder

Outra coisa que aconteceu aqui no metódo test_should_deny_duplicate_user é que ele não encontrou o metódo `assert_valid

#31 cjapes disse:
08 Set 11, 11:59AM

Acho que me está a faltar algum testa na parte em que fazemos 8 testes… alguém pode enunciar os 8 testes?

#32 Luiz disse:
12 Jan 12, 02:53PM

Muito bom o posto parabens, vai me ajudar muito no TCC =)

Deixe um comentário





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.

jQuery: Dominando o framework

Você quer aprender a usar jQuery de verdade? Então chegou a hora! Neste workshop você verá como funciona este framework de JavaScript, entendendo todos os aspectos que fazem do jQuery uma das melhores ferramentas para desenvolvimento de interfaces.

Saiba mais Fechar

Conheça também o HOWTO