Começando com TDD
Vinícius Bail Alonso
Quem sou eu
- Desenvolvedor Web Full Stack
- Graduado em Sistemas para Internet (UTFPR)
- Membro do GURU/PR, PHPPR e Pato Livre
Nós somos Softfocus!

Adivinha de onde somos?

Sumário
- Introdução
- Alguns tipos de testes
- Dúvidas comuns
- Frameworks de Testes
- RSpec
- Nosso primeiro teste
- Baby Steps
- Entendendo o xUnit
- Conhecendo melhor o RSpec
- Carrinho de compras
- Mocks e Stubs
- Testando uma aplicação web
Test Driven Development
Um pouco de história
- TDD: by example

Kent Beck
- Em 1994 escreveu o SUnit (Smalltalk)
- 1995 apresenta o TDD na OOPSLA
- Em 2000 surge o JUnit (Java)
- Junto com Erich Gamma publicou "Test Infected"
Introdução
- Um pouco de história
- O que é na prática?
- Por que escrever testes?
Atenção!
TDD não é sobre testes
é sobre design
O que é TDD na prática?

Alguns tipos de testes
- Testes Unitários
- Testes de Integração
- Testes de Sistema
- Testes de Aceitação
Porque escrever testes?
- Foco no teste e não na implementação
- Código nasce testado
- Simplicidade
- Melhor reflexão sobre o design de classes
- Tornam os projetos mais simples de manter a longo prazo
Testes Unitários
- Testa um componente de forma isolada
- Em um sistema OO é comum que seja uma classe
- Testa apenas uma classe sem interação com componentes externos
- São os testes mais simples de serem escritos
Testes Unitários
class Person
def say_hello
"Hello!"
end
end
require 'spec_helper'
require_relative '../app/person'
describe Person do
it '#say_hello' do
person = Person.new
expect(person.say_hello).to eq('Hello!')
end
end
examplos/app/person.rb
examplos/spec/person_spec.rb
Testes de Integração
- Testam a integração entre duas partes do sistema
- Exemplo: escrita de arquivos, acesso a banco de dados, etc
Testes de Integração
class Field < ApplicationRecord
scope :get_ordened, -> { select(:name, :id, :area).order(:name) }
end
describe ".get_ordened" do
it 'return fields ordened' do
factories = %i(soy_field aba_field)
culture = FactoryGirl.create(:soy)
factories.each { |f| FactoryGirl.create(f, culture: culture) }
ordened = described_class.get_ordened
expect(ordened.first.name).to eq("Abacate Talhão")
expect(ordened.last.name).to eq("Soja Talhão")
end
end
Testes de Sistema
- Testes que simulam a interação do usuário com o sistema
- Por exemplo:
- Clica em links
- Preenche e envia formulários
- A vantagem desses testes é que testam a aplicação como um todo, encontrando problemas que só ocorrem no mundo real
Testes de Sistema
require 'rails_helper'
feature 'User signs in' do
given!(:user) { FactoryGirl.create(:user) }
scenario 'with valid credentials' do
visit root_path
fill_in 'Email', with: user.email
fill_in 'Password', with: user.password
check 'Remember me'
click_button 'Sign in'
expect(page).to have_content "Welcome back, #{user.first_name}!"
end
end
Testes de Aceitação
- São uma extensão dos testes de sistema
- Servem para aplicar critérios de aceitação em requisitos implementados
Testes de Aceitação
scenario "User pass his data and create a new account" do
visit "/users/sign_up"
fill_in "user_email", with: "test@hotmail.com"
fill_in "user_password", with: 12345678, match: :prefer_exact
fill_in "user_password_confirmation", with: 12345678, match: :prefer_exact
fill_in "user_name", with: "Juca"
fill_in "user_cpf", with: "357.548.846-05"
fill_in "user_phone", with: "(42) 99900-1122"
click_button "Registrar-se"
expect(current_path).to eq(root_path)
end
Modelo V

Pirâmide de Testes

Dúvidas comuns
Dúvidas comuns
- Não vou demorar mais para entregar algo se estiver escrevendo testes?
- No início, provavelmente sim, pela falta de prática
- Porém, essa prática nos ajuda a encontrar bugs mais cedo, tornando sua correção mais barata
Dúvidas comuns
- Devo usar TDD o tempo todo?
- Cuidado com as palavras sempre e nunca em Engenharia de Software
Frameworks de Testes




RSpec
- Framework de testes escrito em Ruby
- Os testes são escritos de uma forma mais natural
- As saídas são bem formatadas facilitando a leitura
Nosso Primeiro Teste
- Vamos fazer um programa que verifica se uma string é um palíndromo
- Um palíndromo é uma palavra ou frase que pode ser lida em ambos os sentidos
Nosso Primeiro Teste
Iniciando a aplicação
# Criando um novo diretório para o programa
minicurso@tdd:~$ mkdir palindrome
# Acessando o diretório criado
minicurso@tdd:~$ cd palindrome/
# Iniciando a suite de testes RSpec
minicurso@tdd:~/palindrome$ rspec --init
create .rspec
create spec/spec_helper.rb
# Criando um diretório para o código do programa
minicurso@tdd:~/fizz-buzz$ mkdir app
# Listando os diretórios
minicurso@tdd:~/palindrome$ ls
app spec
Nosso Primeiro Teste
- Vamos começar pelo básico
- Criando uma classe responsável por verificar
- Se chamará Checker
- Deverá possuir um método chamado is_palindrome?
- Pelo qual receberá uma string retornando um boleano
Nosso Primeiro Teste
- Vamos começar testando o caso mais simples
- A classe deve retornar true para o palíndromo "arara"
Nosso Primeiro Teste
- Agora testamos um caso de uma palavra que não seja palíndromo
- A classe deve retornar false para a palavra "test"
Nosso Primeiro Teste
- Agora vamos testar nosso método com frases que são palíndromos
- A classe deve retornar true para a frase "roma me tem amor"
Nosso Primeiro Teste
- Até agora não tratamos palavras com letras maiúsculas e minúsculas
- A classe deve retornar true para a frase "Roma Me tEm amOr"
Nosso Primeiro Teste
- Agora testaremos nossa classe com uma frase que tenha caracteres especiais
- A classe deve retornar true para a frase "Socorram-me, subi no ônibus em Marrocos"
Nosso Primeiro Teste
- Por fim, deve quando o usuário enviar um String vazia devemos retornar false
Nosso Primeiro Teste
- Agora que já criamos alguns casos de testes vamos refatorar o código da classe Checker
- E o código do nosso arquivo de testes
Baby Steps
- Note que começamos a testar nossa funcionalidade pelo caso mais simples
- Essa prática é chamada baby steps
- Em seguida, fomos testando casos um pouco mais complicados
- Sempre escrevendo o código mínimo necessário para passar no teste
Conhecendo o xUnit
- As suites de testes que seguem padrão:
- Setup
- Exercise
- Verify
- Teardown
- Quando interagem com o SUT (System Under Test)
- Dizemos que eles fazem parte da família xUnit
Conhecendo o xUnit
- Setup
- Preparamos o ambiente para começar o teste
- Exercise
- Interagimos com o SUT a fim de gerar o comportamento à ser testado
- Verify
- Verificamos se o comportamento teve o resultado esperado
- Teardown
- O ambiente volta ao normal antes do teste
Conhecendo o xUnit
describe Person do
describe "#save" do
it "a new Person" do
#setup
person = Person.new params
#exercise
person.save
#verify
expect(person.new_record?).to be true
end
#teardown
after do
Person.destroy_all
end
end
end
Conhecendo melhor o RSpec
- O RSpec é uma ferramenta de Behaviour Driven Development (BDD) para Ruby
- Fornece vários métodos para facilitar a descrição do comportamento de uma classe
- describe
- context
- it
Conhecendo melhor o RSpec
- describe
- É utilizado para descrever uma classe ou método
- Exemplo:
- describe Account
- describe '#create'
Conhecendo melhor o RSpec
- context
- É utilizado para agrupar casos de teste em um contexto específico
- Melhorando a semântica de nossa bateria de testes
- Exemplo:
-
- context 'with valid params'
- context 'user is logged'
Conhecendo melhor o RSpec
- it
- É onde os casos de testes são executados
- E o comportamento descrito em um contexto é verificado
- Exemplo:
-
- it 'render to root page'
- it 'create a new tweet'
Carrinho de compras
- Foi solicitado para que seja desenvolvido um carrinho de compras
- O usuário deve ser capaz de
- Adicionar um produto ao seu carrinho
- Remover um produto do seu carrinho
- Somar o valor total do carrinho
Carrinho de compras
- Após estudar o domínio do problema decidimos que seriam necessárias 3 classes
- Product
- Representa o produto
- Item
- Representa um item no carrinho de compras
- Cart
- Representa o carrinho de compras
- Product
Carrinho de compras
- Vamos começar pela classe Product
- Ela deve ser capaz de ser instanciada e depois retornar os valores de seus atributos
- nome
- preço
Carrinho de compras
- Em seguida vamos desenvolver a classe Item
- Ela deve ser capaz de ser instanciada e depois retornar os valores de seus atributos
- produto
- quantidade
Carrinho de compras
- Agora devemos desenvolver a classe Cart
- Ela deve ser instanciada recebendo por parâmetro
- dono
- itens
- Ela deve ser capaz de
- Adicionar um produto
- Remover um produto
- Somar o valor total do carrinho
Bug encontrado
- Foi encontrado um bug em produção!
- Quando o valor de um produto é alterado os valores dos produtos que já estavam nos carrinhos dos clientes são alterados também
- Precisamos arrumar isso imediatamente!
Reflexão
- Os testes já escritos ajudaram a encontrar o problema com mais facilidade?
- Após resolver o problema, o fato de estar tudo testado lhe trouxe mais segurança?
Reflexão
- Mesmo utilizando TDD o software ainda apresenta erros. Devo abandonar a técnica?
- Não!
- Durante a escrita de testes prevemos apenas os fluxos óbvios
- Muitos fluxos de executação não podem ser previstos pelo desenvolvedor
Mocks e Stubs
- É muito comum precisarmos substituir algum colaborador do SUT em algumas situações:
- Instanciar o colaborador é muito complicado
- Instanciar o colaborador real pode trazer consequências ruins
- Enviar um e-mail durante os testes
- Você quer verificar o comportamento ao invés do estado
- Quando nossos testes precisam substituir colaboradores do SUT, chamamos isso de Test Doubles
Mocks e Stubs
- Existem muitos tipos de Test Doubles
- Mock Objects
- Stub
- Spy
- Fake Object
- Dummy
- Nesse minicurso estudaremos apenas dois: mock e stub
Mocks e Stubs
- Podemos testar nossas classes de duas formas
- Por estado
- Por comportamento
- Quando desejamos testar o estado utilizamos stub
- Quando desejamos testar o comportamento utilizamos mock
Mocks e Stubs
- Os stubs são utilizados na fase do setup do nosso padrão xUnit
- No pequeno projeto do carrinho de compras
- Já utilizamos stubs em um de nossos exemplos
Mocks e Stubs
describe "#total_value" do
context 'with 2 items' do
it 'the sum should be 3500' do
laptop = instance_double('Product', name: 'Notebook', price: 3000)
item_laptop = instance_double('Item', product: laptop, quantity: 1)
book = instance_double('Product', name: 'Livro', price: 50)
item_book = instance_double('Item', product: book, quantity: 10)
allow(item_laptop).to receive(:total_value).and_return(3000)
allow(item_book).to receive(:total_value).and_return(500)
cart.add_item(item_laptop)
cart.add_item(item_book)
expect(cart.total_value).to eq(3500)
end
end
end
Exemplo utilizando stub
Mocks e Stubs
describe "#total_value" do
context 'with 2 items' do
it 'the sum should be 3500' do
laptop = instance_double('Product', name: 'Notebook', price: 3000)
item_laptop = instance_double('Item', product: laptop, quantity: 1)
book = instance_double('Product', name: 'Livro', price: 50)
item_book = instance_double('Item', product: book, quantity: 10)
allow(item_laptop).to receive(:total_value).and_return(3000)
allow(item_book).to receive(:total_value).and_return(500)
cart.add_item(item_laptop)
cart.add_item(item_book)
expect(cart.total_value).to eq(3500)
end
end
end
Exemplo utilizando stub
Mocks e Stubs
- Já os mocks são utilizados na fase verify do nosso padrão xUnit
- Em seguida, um exemplo de teste utilizando mock
- Na classe Game que controla um jogo FizzBuzz, que é responsável por imprimir o resultado na tela
Mocks e Stubs
class Game
# ...
def play
@interval.each do |number|
puts @response.formatted number
end
end
end
describe '#play' do
it 'show a single Fizz' do
game = described_class.new (1..3)
expect(STDOUT).to receive(:puts).with('1')
expect(STDOUT).to receive(:puts).with('2')
expect(STDOUT).to receive(:puts).with('Fizz')
game.play
end
end
Testando uma aplicação web
Testando uma aplicação web
- Primeiro devemos clonar o projeto base
- Em seguida instalando as dependências
$ git clone https://github.com/viniciusalonso/minicurso-tdd-app.git
$ cd minicurso-tdd-app.git/
$ bundle install
Testando uma aplicação web
- Nossa primeira funcionalidade será um CRUD básico
- Create
- Read
- Update
- Delete
- De uma classe chamada Post com os atributos
- título (title)
- conteúdo (content)
Testando uma aplicação web
- Vamos começar gerando a classe Post e criando sua tabela no banco de dados
$ rails g model Post title content:text
Gerando a classe e a migration para a tabela
$ rails db:migrate
Criando a tabela no banco à partir da migration
Listagem
Testando uma aplicação web
- Vamos começar criando uma listagem para os posts
$ rails g controller Posts
Gerando o controlador e demais arquivos
- Gerando o controlador e testando sua action
Testando uma aplicação web
- Testando a action index do PostsController
- Devemos testar
- Retorno do status code 200 (ok)
- Buscar os posts no banco de dados
- Devemos testar
Testando uma aplicação web
- Returno do status code 200
describe "GET #index" do
it 'return http success' do
get :index
expect(response).to have_http_status(:success)
end
end
spec/controllers/posts_controller_spec.rb
Failures:
1) PostsController GET #index return http success
Failure/Error: get :index
ActionController::UrlGenerationError:
No route matches {:action=>"index", :controller=>"posts"}
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Returno do status code 200
resources :posts, only: :index
config/routes.rb
Failures:
1) PostsController GET #index return http success
Failure/Error: get :index
AbstractController::ActionNotFound:
The action 'index' could not be found for PostsController
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Returno do status code 200
resources :posts, only: :index
config/routes.rb
Failures:
1) PostsController GET #index return http success
Failure/Error: get :index
AbstractController::ActionNotFound:
The action 'index' could not be found for PostsController
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Returno do status code 200
def index
end
app/controllers/posts_controller.rb
Failures:
1) PostsController GET #index return http success
Failure/Error: get :index
ActionController::UnknownFormat:
PostsController#index is missing a template for this request format and variant.
request.formats: ["text/html"]
request.variant: []
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Returno do status code 200
$ rails spec/controllers/posts_controller_spec.rb
$ touch app/views/posts/index.html.erb
Testando uma aplicação web
- Buscar os posts no banco de dados
# Adicionar na linha 19
config.include FactoryGirl::Syntax::Methods
spec/rails_helper.rb
- Nesse teste será necessário criar registro no banco de dados, por isso vamos precisar da biblioteca factory_girl
Testando uma aplicação web
- Buscar os posts no banco de dados
let(:post) { FactoryGirl.create(:post) }
it 'assigns @posts' do
get :index
expect(assigns(:posts)).to include(post)
end
spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Buscar os posts no banco de dados
Failures:
1) PostsController GET #index assigns @posts
Failure/Error: expect(assigns(:posts)).to include(post)
expected nil to include
#<Post id: 1, title: "MyString",
content: "MyText",
created_at: "2017-09-24 23:15:56",
updated_at: "2017-09-24 23:15:56">,
but it does not respond to `include?`
$ rails spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Buscar os posts no banco de dados
def index
@posts = Post.all
end
app/controllers/posts_controller.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Testando a listagem de posts na view
require "rails_helper"
RSpec.describe "posts/index" do
it "displays all posts" do
assign(:posts, [
FactoryGirl.create(:post, title: 'My title 1'),
FactoryGirl.create(:post, title: 'My title 2'),
FactoryGirl.create(:post, title: 'My title 3')
])
render
expect(rendered).to match /My title 1/
expect(rendered).to match /My title 2/
expect(rendered).to match /My title 3/
end
end
spec/views/posts/index.html.erb_spec.rb
Create
Testando uma aplicação web
- Agora que possuimos uma listagem criada e testada, vamos implementar a criação de posts
- Essa funcionalidade deverá ser testada em duas partes
- Renderização do formulário
- Criação de um novo registro no banco de dados
Testando uma aplicação web
- Renderização do formulário
- Vamos usar uma biblioteca para gerar formulários de uma forma mais simples
gem 'simple_form'
Gemfile
$ bundle install
Comando para instalar as dependêndias no rails
$ rails g simple_form:install
Instalando a biblioteca no projeto
Testando uma aplicação web
- Renderização do formulário
describe "GET #new" do
it 'instance a new Post' do
get :new
expect(assigns(:post)).to be_a_new(Post)
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Renderização do formulário
Failures:
1) PostsController GET #new instance a new Post
Failure/Error: get :new
ActionController::UrlGenerationError:
No route matches
{:action=>"new", :controller=>"posts"}
Testando uma aplicação web
- Renderização do formulário
resources :posts, only: [:index, :new]
config/routes.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Renderização do formulário
Failures:
1) PostsController GET #new instance a new Post
Failure/Error: get :new
AbstractController::ActionNotFound:
The action 'new' could not be
found for PostsController
Testando uma aplicação web
- Renderização do formulário
def new
@post = Post.new
end
app/controllers/posts_controller.rb
Failures:
1) PostsController GET #new instance a new Post
Failure/Error: get :new
ActionController::UnknownFormat:
PostsController#new is missing a template for this request format and variant.
request.formats: ["text/html"]
request.variant: []
Testando uma aplicação web
- Testando a listagem de posts na view
<%= simple_form_for(@post) do |post| %>
<%= f.input :title %>
<%= f.input :content %>
<%= f.button :submit %>
<% end %>
spec/views/posts/index.html.erb_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Criação de um novo registro no banco de dados
describe "POST #create" do
it 'create new Post' do
expect do
post :create,
params: {post: {title: 'My post',
content: 'My content'}}
end.to change{ Post.count }.by(1)
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Criação de um novo registro no banco de dados
Failures:
1) PostsController POST #create create new Post
Failure/Error:
post :create,
params: {title: 'My post', content: 'My content'}
ActionController::UrlGenerationError:
No route matches {:action=>"create", :content=>"My content",
:controller=>"posts", :title=>"My post"}
Testando uma aplicação web
- Renderização do formulário
resources :posts, only: [:index, :new, :create]
config/routes.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Criação de um novo registro no banco de dados
Failures:
1) PostsController POST #create create new Post
Failure/Error:
expect do
post :create, params: {title: 'My post', content: 'My content'}
end.to change{ Post.count }.by(1)
expected `Post.count` to have changed by 1, but was changed by 0
Testando uma aplicação web
- Criação de um novo registro no banco de dados
def create
@post = Post.new post_params
@post.save
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :content)
end
app/controllers/posts_controller.rb
$ rspec spec/controllers/posts_controller_spec.rb
Update
Testando uma aplicação web
- Renderização do formulário
describe "GET #edit" do
let(:post) { FactoryGirl.create(:post) }
it 'assigns @post' do
get :edit, params: { id: post.id }
expect(assigns(:post)).to eq(post)
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Renderização do formulário
Failures:
1) PostsController GET #edit assigns @post
Failure/Error: get :edit, params: { id: post.id }
ActionController::UrlGenerationError:
No route matches
{:action=>"edit", :controller=>"posts", :id=>1}
Testando uma aplicação web
- Renderização do formulário
resources :posts, only: [:index, :new, :create, :edit]
config/routes.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Renderização do formulário
Failures:
1) PostsController GET #edit assigns @post
Failure/Error:
get :edit, params: { id: post.id }
AbstractController::ActionNotFound:
The action 'edit' could
not be found for PostsController
Testando uma aplicação web
- Renderização do formulário
def edit
@post = Post.find(params[:id])
end
app/controllers/posts_controller.rb
Failures:
1) PostsController GET #edit assigns @post
Failure/Error: get :edit, params: { id: post.id }
ActionController::UnknownFormat:
PostsController#edit is missing a template for this request format and variant.
request.formats: ["text/html"]
request.variant: []
Testando uma aplicação web
- Renderização do formulário
Failures:
1) PostsController GET #edit assigns @post
Failure/Error: get :edit, params: { id: post.id }
ActionController::UnknownFormat:
PostsController#edit is missing a
template for this request format and variant.
request.formats: ["text/html"]
request.variant: []
Testando uma aplicação web
- Atualização do registro no banco de dados
describe "PATCH #update" do
let(:post) { FactoryGirl.create(:post) }
it 'update post' do
params = { id: post.id, post: { title: 'Meu novo título' } }
patch :update, params: params
post.reload.title
expect(post.title).to eq('Meu novo título')
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
Failures:
1) PostsController PATCH #update update post
Failure/Error: patch :update, params: params
ActionController::UrlGenerationError:
No route matches
{:action=>"update", :controller=>"posts",
:post=>{:id=>1, :title=>"Meu novo título"}}
- Atualização do registro no banco de dados
Testando uma aplicação web
resources :posts, except: :destroy
config/routes.rb
$ rspec spec/controllers/posts_controller_spec.rb
- Atualização do registro no banco de dados
Testando uma aplicação web
Failures:
1) PostsController PATCH #update update post
Failure/Error: put :update, params: params
AbstractController::ActionNotFound:
The action 'update' could not be found for PostsController
- Atualização do registro no banco de dados
Testando uma aplicação web
def update
@post = Post.find(params[:id])
@post.update(post_params)
redirect_to posts_path
end
app/controllers/posts_controller.rb
$ rspec spec/controllers/posts_controller_spec.rb
- Atualização do registro no banco de dados
Delete
Testando uma aplicação web
- Deletando um registro do banco de dados
describe "DELETE #destroy" do
it 'delete post' do
attrs = FactoryGirl.attributes_for(:post)
post = Post.create(attrs)
expect do
delete :destroy, params: { id: post.id }
end.to change{ Post.count }.by(-1)
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
Failures:
1) PostsController DELETE #destroy delete post
Failure/Error: delete :destroy, params: { id: post.id }
ActionController::UrlGenerationError:
No route matches {:action=>"destroy", :controller=>"posts", :id=>1}
- Deletando um registro do banco de dados
Testando uma aplicação web
resources :posts
config/routes.rb
$ rspec spec/controllers/posts_controller_spec.rb
- Deletando um registro do banco de dados
Testando uma aplicação web
Failures:
1) PostsController DELETE #destroy delete post
Failure/Error: delete :destroy, params: { id: post.id }
AbstractController::ActionNotFound:
The action 'destroy' could not be found for PostsController
- Deletando um registro do banco de dados
Testando uma aplicação web
def destroy
@post = Post.find(params[:id])
@post.destroy
redirect_to posts_path
end
app/controllers/posts_controller.rb
$ rspec spec/controllers/posts_controller_spec.rb
- Atualização do registro no banco de dados
Read
Testando uma aplicação web
- Lendo um registro do banco de dados
describe "GET #show" do
let(:post) { FactoryGirl.create(:post) }
it 'assigns @post' do
get :show, params: { id: post.id }
expect(assigns(:post)).to eq(post)
end
end
spec/controllers/posts_controller_spec.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Lendo um registro do banco de dados
def show
@post = Post.find(params[:id])
end
app/controllers/posts_controller.rb
$ rspec spec/controllers/posts_controller_spec.rb
Testando uma aplicação web
- Lendo um registro do banco de dados
<strong>
<%= @post.title %>
</strong>
<p>
<%= @post.content %>
</p>
<%= link_to 'Voltar', posts_path %>
app/views/posts/show.html.erb
$ rspec spec/controllers/posts_controller_spec.rb
Cobertura de testes
Testando uma aplicação web
- Outro conceito importante é a cobertura de testes de sua aplicação
- As ferramentas que fazem esse cálculo nos mostram qual a porcentagem do código que escrevemos que tem cobertura de testes
Testando uma aplicação web
- Após rodar nossa suite podemos oberservar a mensagem abaixo
Coverage report generated for RSpec to
/home/vinicius/Projects/minicurso-app/coverage. 39 / 39 LOC (100.0%) covered.
$ rspec
- Para um relatório mais detalhado basta abrir o arquivo coverage/index.html
Testando uma aplicação web

coverage/index.html
Validações
Testando uma aplicação web
- Por hora, nossa classe está aceitando os dados sem validá-los
- É muito importante aceitar apenas dados confiáveis
- Por essa razão vamos adicionar algumas validações
- title
- Não pode ser vazio
- Deve ser único
- content
- Não pode ser vazio
- title
Testando uma aplicação web
- Para testar as validações vamos utilizar uma biblioteca
#Adicionar nas linhas 15 e 16
config.include(Shoulda::Matchers::ActiveModel, type: :model)
config.include(Shoulda::Matchers::ActiveRecord, type: :model)
spec/rails_helper.rb
Testando uma aplicação web
- Escrevendo testes para as validações na classe
require 'rails_helper'
RSpec.describe Post, type: :model do
context 'validations' do
it { should validate_presence_of(:title) }
it { should validate_uniqueness_of(:title) }
it { should validate_presence_of(:content) }
end
end
spec/models/post_spec.rb
$ rspec spec/models/post_spec.rb
Testando uma aplicação web
- Adicionando as validações na classe
class Post < ApplicationRecord
validates_presence_of :title, :content
validates_uniqueness_of :title
end
app/models/post.rb
$ rspec spec/models/post_spec.rb
Adicionando uma home page
Testando uma aplicação web
- Precisamos adicionar uma página inicial com uma mensagem de boas vindas para o usuário
require 'rails_helper'
RSpec.feature "HomePages", type: :feature do
scenario "Visit the home page and see messages" do
visit '/'
expect(page).to have_title "Seja Bem Vindo (a)!"
expect(page).to have_text("Bem Vindo(a)!")
end
end
spec/features/home_pages_spec.rb
$ rspec spec/features/home_pages_spec.rb
Testando uma aplicação web
- Precisamos adicionar uma página inicial com uma mensagem de boas vindas para o usuário
class WelcomeController < ApplicationController
def index
end
end
app/controllers/welcome_controller.rb
$ rspec spec/features/home_pages_spec.rb
root 'welcome#index'
config/routes.rb
Testando uma aplicação web
- Precisamos adicionar uma página inicial com uma mensagem de boas vindas para o usuário
<!-- Linha 4 -->
<title>Seja Bem Vindo (a)!</title>
app/views/layouts/application.html.erb
$ rspec spec/features/home_pages_spec.rb
<h1>Bem Vindo(a)!</h1>
app/views/welcome/index.html.erb
Adicionando um link para a página dos posts
Testando uma aplicação web
- Precisamos adicionar uma página inicial com uma mensagem de boas vindas para o usuário
scenario "Go from home page to posts page" do
visit '/'
click_on 'Posts'
expect(page.current_path).to eq(posts_path)
end
spec/features/home_pages_spec.rb
$ rspec spec/features/home_pages_spec.rb
Testando uma aplicação web
- Precisamos adicionar uma página inicial com uma mensagem de boas vindas para o usuário
<%= link_to 'Posts', posts_path %>
app/views/welcome.html.erb
$ rspec spec/features/home_pages_spec.rb
Adicionando autenticação
Testando uma aplicação web
- Para adicionar autenticação vamos utilizar uma gem chamada devise
gem 'devise'
Gemfile
$ bundle install
$ rails generate devise:install
Instalando dependência
Instalando devise no projeto
Testando uma aplicação web
- Criando model para representar o usuário
$ rails generate devise User
$ rails db:migrate
Gerando model User
Criando tabela users no banco de dados
Testando uma aplicação web
- Configurando ambiente de testes
#Linha 7
require 'devise'
#Linhas 15 e 16
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::IntegrationHelpers, type: :feature
spec/rails_helper.rb
Testando uma aplicação web
- Faremos com que seja necessário estar logado para acessar qualquer página
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
# Adicionar linha abaixo
before_action :authenticate_user!
end
app/controllers/application_controller.rb
$ rspec
Testando uma aplicação web
- É necessário simular um login de usuário nos testes
FactoryGirl.define do
factory :user do
email { Faker::Internet.email }
password "password"
password_confirmation "password"
end
end
spec/factories/users.rb
- Primeiro devemos configurar a fábrica de usuários
Testando uma aplicação web
- Depois simulamos o login em nossos testes
before(:each) do
sign_in FactoryGirl.create(:user)
end
spec/controllers/posts_controller_spec.rb
$ rspec
spec/controllers/welcome_controller_spec.rb
spec/features/home_pages_spec.rb
Cadastro de usuário
Testando uma aplicação web
- Vamos começar gerando nosso arquivo de teste
$ rails g rspec:feature user_register_himself
scenario "user pass the correct data" do
visit '/users/sign_up'
fill_in "user_email", with: "test@hotmail.com"
fill_in "user_password", with: 12345678, match: :prefer_exact
fill_in "user_password_confirmation", with: 12345678, match: :prefer_exact
click_on "Sign up"
expect(page).to have_text('Bem Vindo(a)!')
end
spec/features/user_register_himselves_spec.rb
Testando uma aplicação web
- Caso o usuário deixe um campo vazio
scenario "user submit form with a blank email" do
visit '/users/sign_up'
fill_in "user_email", with: ""
fill_in "user_password", with: 12345678, match: :prefer_exact
fill_in "user_password_confirmation", with: 12345678, match: :prefer_exact
click_on "Sign up"
expect(page).to have_text("Email can't be blank")
end
spec/features/user_register_himselves_spec.rb
Reflexão
- De que vale um teste que não quebra?
- Quais garantias temos com os últimos testes que escrevemos?
E agora, por onde devo começar?
Cursos




Livros





Códigos utilizados
- Palíndromo e Carrinho de compras
- https://github.com/viniciusalonso/minicurso-tdd
- Aplicação
- https://github.com/viniciusalonso/minicurso-tdd-app
Entre em contato



vba321@hotmail.com


Referências
- Test Driven Development: Testes no mundo real com ruby
- http://blog.caelum.com.br/unidade-integracao-ou-sistema-qual-teste-fazer/
- http://blog.chrisdpeters.com/introduction-feature-specs-rspec/
- Cucumber e RSpec: Construa aplicações Ruby com testes e especificações
- http://www.mauricioaniche.com/2010/11/cuidado-com-seus-baby-steps/
- https://martinfowler.com/articles/mocksArentStubs.html
Começando com TDD
By Vinícius Alonso
Começando com TDD
Minicurso apresentado no FTSL(Fórum de Tecnologia em Software Livre) em Curitiba 2017.
- 1,457