Test-Driven Development no Rails: Unit Tests



Documentos relacionados
Test-driven Development no Rails Começando seu projeto com o pé direito. 2007, Nando Vieira

Como incluir artigos:

MANUAL DO ANIMAIL Terti Software

Listando itens em ComboBox e gravando os dados no Banco de Dados MySQL.

Validando dados de páginas WEB

Versão 0.1. Manual FoRc. Última Atualização: Maio/2007. Daniel Schmitz

Lazarus pelo SVN Linux/Windows

4 O Workflow e a Máquina de Regras

Programação Web Aula 10 - Testes, Javascript, Ajax

1º passo: Instalando a rvm.

Tutorial para envio de comunicados e SMS

No Fedora, instalamos os pacotes "mysql" e "mysql-server", usando o yum:

Memória Flash. PdP. Autor: Tiago Lone Nível: Básico Criação: 11/12/2005 Última versão: 18/12/2006. Pesquisa e Desenvolvimento de Produtos

Utilizando a ferramenta de criação de aulas

Banco de Dados Microsoft Access: Criar tabelas. Vitor Valerio de Souza Campos

EXEMPLO DE COMO FAZER UMA MALA DIRETA

Criando Banco de Dados, Tabelas e Campos através do HeidiSQL. Prof. Vitor H. Migoto de Gouvêa Colégio IDESA 2011

Bem- Vindo ao manual de instruções do ECO Editor de COnteúdo.

Banco de Dados Microsoft Access: Criar tabelas

WF Processos. Manual de Instruções

Trabalhando com conexão ao banco de dados MySQL no Lazarus. Prof. Vitor H. Migoto de Gouvêa Colégio IDESA 2011

Dicas para usar melhor o Word 2007

Aula 01 - Formatações prontas e condicionais. Aula 01 - Formatações prontas e condicionais. Sumário. Formatar como Tabela

Segurança de Acesso a Banco de Dados no MS SQL Server

2 echo "PHP e outros.";

MANUAL DE UTILIZAÇÃO SISTEMA DE CADASTRO INTRANET

Parte I. Demoiselle Mail

Parte I Tutorial Wi Fi 2.0 Arduino Exemplo usado: Webserver

Prefeitura Municipal de São Luís Manual de uso dos serviços da SEMFAZ. Prefeitura Municipal de São Luís Manual de uso dos serviços da SEMFAZ

FERRAMENTAS DE COLABORAÇÃO CORPORATIVA

Trilha Agile TDD e 20 coisas que você precisa saber

Vamos criar uma nova Página chamada Serviços. Clique em Adicionar Nova.

CONSTRUÇÃO DE BLOG COM O BLOGGER

Manual Administrador - Mídia System

Novell. Novell Teaming 1.0. novdocx (pt-br) 6 April 2007 EXPLORAR O PORTLET BEM-VINDO DESCUBRA SEU CAMINHO USANDO O NOVELL TEAMING NAVIGATOR

CRIANDO UM BANCO DE DADOS

Nesse artigo abordaremos os principais aspectos de instalação e uso do NTOP no Fedora Core 4.

Guia do Usuário. Introdução

ETEC DR. EMÍLIO HENRNANDEZ AGUILAR PROGRAMAÇÃO DE COMPUTADORES II PROFESSOR RAFAEL BARRETO DELPHI FORMULÁRIO COM ABAS E BUSCAS DE REGISTROS

Scriptlets e Formulários

Como criar e gerir um blog?

II Semana de Tecnologia da Informação. Rails Framework

Guia de instalação para ambiente de Desenvolvimento LINUX

Vamos criar uma nova Página chamada Serviços. Clique em Adicionar Nova.

O Googlebot não consegue aceder a ficheiros CSS e JS em seu blog

Manual de Instalação e Configuração para Revendedores e Assinantes Virtual Server.

Sistema de Recursos Humanos

Manual SAGe Versão 1.2 (a partir da versão )

Instalando servidor Apache com MySQL e as linguagens ColdFusion e PHP. XAMPP (xampp-win installer.exe), veja aqui.

UNIVERSIDADE FEDERAL DO AMAPÁ PRÓ REITORIA DE ADMINISTRAÇÃO E PLANEJAMENTO DEPARTAMENTO DE INFORMÁTICA. Manual do Moodle- Sala virtual

PAINEL GERENCIADOR DE S

Inventario de produtos

Barra de ferramentas padrão. Barra de formatação. Barra de desenho Painel de Tarefas

"Manual de Acesso ao Moodle - Discente" 2014

Desenvolvendo Websites com PHP

Versão Manual FoRc. Última Atualização: Maio/2007. Daniel Schmitz

Manual do Google agenda. criação e compartilhamento de agendas

Conexão rápida entre dois computadores em uma plataforma Linux

CRIANDO BANCOS DE DADOS NO SQL SERVER 2008 R2 COM O SQL SERVER MANAGEMENT STUDIO

AMBIENTE. FORMULÁRIO: é a janela do aplicativo apresentada ao usuário. Considere o formulário como a sua prancheta de trabalho.

Tutorial Básico. Instalando e testando o Ruby on Rails no Windows

Manual do Ambiente Moodle para Professores

Manual de Publicaça o no Blog da Aça o TRIBOS nas Trilhas da Cidadania

3. No painel da direita, dê um clique com o botão direito do mouse em qualquer espaço livre (área em branco).

NOVIDADES DO JAVA PARA PROGRAMADORES C

COMO FAZER SEUS PEDIDOS ONLINE PASSO-A-PASSO

Segmentação de Lista de Contatos

Manual de Utilização

Esse manual é um conjunto de perguntas e respostas para usuários(as) do Joomla! 1.5.

INSTALANDO E CONFIGURANDO O MY SQL

Manual de configuração do sistema

Cadastrar Categorias e Produtos

Tutorial para envio de comunicados

Fixture-Factory. Criando objetos para seus testes. Como criar objetos através de templates para serem utilizados como massa de dados em seus testes.

Omega Tecnologia Manual Omega Hosting

MICROSOFT EXCEL AVANÇADO

OFICINA BLOG DAS ESCOLAS

Índice: CMS 3 O que é Content Management System? Clientes 4 O que é o Cliente? 4 Configurando o i-menu/i-view para trabalhar. com o CMS.

GUIA SISTEMA ONLINE. Anote aqui: Acesso: 1- Navegadores: google chrome, firefox, internet explorer, etc.. 2- Digite: web.aeschool.com.

COMO INICIAR O RELACIONAMENTO COM OS LEADS? 8 passos para TER UMA SEQUÊNCIA DE S BEM SUCEDIDA.

W o r d p r e s s 1- TELA DE LOGIN

BEM VINDOS AO DHL WEB SHIPPING GUIA DE USO

Docas do Pará - Guia de Instalação

8. Outros tipos de Transação (Modo de Transação de Autoconfirmação e Modo Implícito)

Série ArcGIS Online I. Aprenda em 20. Módulo 4. Configure pop-ups.

Microsoft Access: Criar relações para um novo banco de dados. Vitor Valerio de Souza Campos

Desenvolvendo plugins WordPress usando Orientação a Objetos

OCOMON PRIMEIROS PASSOS

Manual Sistema de Autorização Online GW

Perguntas frequentes do Samsung Drive Manager

Como escrever melhor em 5 passos simples

Revisando sintaxes SQL e criando programa de pesquisa. Prof. Vitor H. Migoto de Gouvêa Colégio IDESA 2011

Como instalar o Ocomon passo a passo.

Instalando o Debian em modo texto

Em 2 minutos de leitura você aprenderá facilmente a: Montar seu perfil Buscar colaboradores e comunidades Utilizar recursos

Para criar uma animação precisamos de uma imagem e que ela contenha alguns frames. O número de frames é uma escolha sua.

- Versão 1.0 Página 1

Introdução ao Processamento de Imagens com o IRIS Por Rodolfo Barros

Transcrição:

Test-Driven Development no Rails: Unit Tests 11/05/07 Tags:, Ruby, Ruby on Rails, TDD, Teste Unitário, Unit Tests 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 apri 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 ate 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 fazo 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, :nil => false t.column :email, :string, :nil => false t.column :password, :string, :nil => false def self.down drop_table :users 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 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 enter. 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"

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. 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. Lembrese 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 enter 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 def assert_invalid(record, message='') deny record.valid?, message 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" 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á prevo 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" private def create(options={}) User.create({ :name => "Homer Simpson", :email => "homer@simpsons.com", :password => "test" }.merge(options)) 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" 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"

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" 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" 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" 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" 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" def test_should_deny_duplicate_user user = create assert_valid user user = create assert_invalid user, "User shouldn't be created" private def create(options={}) User.create({ :name => "Homer Simpson", :email => "homer@simpsons.com", :password => "test" }.merge(options)) 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. 2) Failure: test_should_deny_bad_email(usertest) [./test/unit/user_test.rb:25]: :email should be in a valid format. 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. 4) Failure: test_should_require_email(usertest) [./test/unit/user_test.rb:19]: :email should be required. 5) Failure: test_should_require_longer_password(usertest) [./test/unit/user_test.rb:37]: :password should be 4 characters or longer. 6) Failure: test_should_require_name(usertest) [./test/unit/user_test.rb:13]: :name should be required. 7) Failure: test_should_require_password(usertest) [./test/unit/user_test.rb:31]: :password should be required. 8 tests, 9 assertions, 7 failures, 0 errors Foram executados 8 testes, com 9 asserções, so 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 depê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 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. 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. 3) Failure: test_should_require_longer_password(usertest) [./test/unit/user_test.rb:37]: :password should be 4 characters or longer. 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]+\.)+[az]{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, :nil => false t.column :excerpt, :string, :limit => 250, :nil => true t.column :body, :text, :nil => false t.column :created_at, :datetime t.column :updated_at, :datetime t.column :allow_comments, :boolean, :default => true, :nil => false t.column :user_id, :integer, :nil => false def self.down drop_table :posts 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 depem 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 private def create(options={}) Post.create({ :title => 'Title', :excerpt => 'Excerpt', :body => 'Body', :allow_comments => true, :user_id => 1 }.merge(options)) 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" 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" 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" 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"

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 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" 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" 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" 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. 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. 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. 5) Failure: test_should_require_author(posttest) [./test/unit/post_test.rb:26]: :user_id should be required.

6) Failure: test_should_require_body(posttest) [./test/unit/post_test.rb:20]: :body should be required. 7) Failure: test_should_require_title(posttest) [./test/unit/post_test.rb:14]: :title should be required. 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, :depent => :destroy #[...] 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. 2) Failure: test_should_check_post_authorship(posttest) [./test/unit/post_test.rb:63]: User doesn't exist, so it should be required. 3) Failure: test_should_deny_long_excerpt(posttest) [./test/unit/post_test.rb:38]: :excerpt should have had an error. 4) Failure:

test_should_deny_non_number_user(posttest) [./test/unit/post_test.rb:44]: :user_id should have had an error. 5) Failure: test_should_require_body(posttest) [./test/unit/post_test.rb:20]: :body should be required. 6) Failure: test_should_require_title(posttest) [./test/unit/post_test.rb:14]: :title should be required. 7) Failure: test_should_require_user(posttest) [./test/unit/post_test.rb:26]: :user_id should be required. 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 ~/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. 2) Failure: test_should_deny_long_excerpt(posttest) [./test/unit/post_test.rb:38]: :excerpt should have had an error. 3) Failure: test_should_deny_non_number_user(posttest) [./test/unit/post_test.rb:44]: :user_id should have had an error. 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? 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. 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, dizo 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 #[..] Perceba que estamos validando a presença do atributo/método user e não user_id. A mesma coisa está so 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, :nil => false t.column :name, :string, :limit => 100, :nil => false t.column :email, :string, :limit => 100, :nil => false t.column :url, :string, :limit => 255, :nil => true t.column :created_at, :datetime t.column :active, :boolean, :default => false, :nil => false t.column :body, :text, :nil => false def self.down drop_table :comments 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 def test_should_be_invalid comment = create(:email => nil, :name => nil, :url => nil, :body => nil) assert_invalid comment, "Comment shouldn't be created" 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" 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" 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" 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" 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" def test_cannot_comment_because_post_is_closed comment = create(:post_id => posts(:rails_rules).id) assert_invalid comment, "Comment shouldn't be created" 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)) 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, :depent => :destroy #[...] 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 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