E depois que seu código funciona, o que mais ele pode fazer?

Conhecer um idioma e se comunicar bem nele são a mesma coisa? Nem sempre, né? Você pode saber vocabulário, gramática, e até gírias, mas se não souber cativar as pessoas, se conectar com elas, talvez acabe não se comunicando tão bem quanto alguém que consegue criar rapport sem nem mesmo falar a língua.

Venho me convencendo que algo parecido acontece com programação. Você pode conhecer linguagens profundamente e ainda assim deixar a desejar nas entregas. Porque codar se trata não só da linguagem técnica, mas da comunicação entre você e a máquina. Para seu código funcionar bem, você precisa se comunicar com a máquina.

E depois que seu código funciona, o que mais ele pode fazer?

Ele pode se comunicar com seres humanos!

Uma vez que o software funcione como precisa, e tenha uma performance legal, dê uma olhada no que ele pode dizer a mais para o próximo ser humano que precisar trabalhar com ele.

Por exemplo, estes tempos me deparei com um código (meu) mais ou menos assim:

O que ficou me chamando a atenção é que este código não explica nada do contexto, né? Ele funciona e performa bem, mas o próximo que vier mexer nele, vai precisar buscar e perguntar um monte de coisa antes de conseguir se situar.

Duas coisas gritavam na minha cabeça:

  1. Se a preferência do usuário não for "App", o que pode ser?
  2. Como deixar mais claro que a principal decisão tomada aqui é sobre o número de dígitos?

Bora refatorar e comunicar essas duas coisas aí?

Nesta próxima iteração, trocamos o "if" por um switch. O switch nos permite expôr as outras possibilidades, deixando o código mais claro para as pessoas que precisarem ler. Aproveitamos a abordagem para incluir também a opção extra, que melhora nosso código no sentido de tratar casos estranhos de maneira específica. Fica assim:

Pronto! Agora quem bater o olho nestas linhas de código já vai saber de pelo menos 3 possibilidades de preferências e ter uma noção de que qualquer outra provavelmente é algo inesperado.

Vamos iterar mais uma vez para trazer a decisão dos dígitos a tona? Uma forma de fazer isso é deixar o switch retornando apenas este número. Fica mais claro que esta é a função dele ali:

Maravilha! Agora está bem explícito que a decisão tomada é sobre a quantidade de dígitos!

Epa, mas peraí! Tem algo mais que podemos fazer!

Como esta decisão só precisa ser tomada uma vez, não precisamos armazenar isso numa variável. (Estamos codando em Ruby!) Podemos simplesmente colocar isso num outro método!

Prontinho! Agora nosso código está um pouco mais explicativo para o ser humano. A próxima pessoa que chegar ali, vai ter menos dúvidas sobre o que está acontecendo, provavelmente terá que fazer menos perguntas, buscar menos informações em outro lugar, e conseguirá trabalhar até mais leve! Haha

Cada time tem seu jeito e seu estilo de codar. Talvez este exemplo não seja positivo para todos os contextos, mas fica a sugestão. Qualquer comentário vale a reflexão! O que mais você faria aqui? Ou prefere como estava e nem teria mexido?

sampope 11: Como acrescentar um novo campo num Model do Rails?

Acrescentando um campo num model do Rails

Quando a gente desenvolve uma aplicação, ela cresce e evolui conforme as necessidades dos usuários, da equipe de suporte, e diversos outros fatores. Neste contexto, é comum precisar acrescentar campos aos models que já existem nela.

Neste post vamos acrescentar um campo no model que criamos no post sobre callbacks. Se trata do model que controla os roteiros da operadora de turismo. Tínhamos lhe dado o nome Itinerary. Neste post vamos acrescentar um campo para podermos guardar o endereço de e-mail do cliente que estiver comprando aquele roteiro. O nome do campo novo será customer_email.

Bora?

1 - Escreva o teste (ou atualize um existente)

Para mantermos nossa aplicação saudável, vamos sempre começar pelos testes. Neste momento ainda não temos nenhum teste que simule um usuário criando um roteiro novo na nossa aplicação. Então vamos começar por ele.

Algo assim:

Prontinho! Agora temos um teste para a criação de novos roteiros! Vamos roda-lo para termos certeza de que está tudo em ordem.

Chamamos o comando rspec somente para o arquivo que acabamos de criar, e temos:

Beleza! O teste passou, conforme o previsto.

Agora vamos atualizar o teste, para incluir a nova funcionalidade, que é o email do cliente. Vamos chamar nosso campo de customer_email, então nosso teste deverá ficar desta forma:


Quando rodamos ele novamente, desta vez temos um problema:


Era isso mesmo que a gente queria! Nosso teste está falhando por não encontrar o campo para inserir o e-mail do cliente. Agora é hora de inserir este campo!

Vamos lá!

2 - Crie e rode a migration

Sempre que precisamos alterar qualquer coisa no banco de dados de uma aplicação Rails, nós usamos migrations. Se olharmos o post sobre o scaffold, veremos que um dos passos do scaffold é a criação da migration que nós rodamos para criar a tabela no banco de dados.

Agora que vamos acrescentar um campo numa tabela existente, vamos criar a migration nós mesmos. Fazemos isso rodando o seguinte comando dentro do diretório da aplicação no terminal:

rails g migration AddCustomerEmailToItinerary customer_email

Neste comando estamos chamando o Rails, usando o "g"- que é abreviação de "generate", pedindo uma migration com o nome AddCustomerEmailToItinerary, e dizendo que o campo a ser adicionado se chama customer_email. Como não especificamos nenhum tipo de dado para customer_email, o Rails vai colocar seu padrão, que é string.

Quando rodamos o comando, vemos que o Rails chama o ActiveRecord e gera o arquivo 20230103171959_add_customer_email_to_itinerary.rb no diretório db/migrate.


Vamos ver o que tem neste arquivo?


Vemos que o Rails usou suas magias para criar um método change que acrescenta uma coluna na tabela itineraries. O nome da nova coluna é customer_email e o tipo de dado é string. Está tudo em ordem, conforme queríamos! Podemos rodar a migration.

Usamos o comando rails db:migrate e temos:


Vemos que deu tudo certo e o Rails criou a coluna.

O que acontece se rodarmos nosso teste agora? Ele continua falhando, porque precisamos colocar este novo campo no formulário.

Bora fazer isso?

3 - Insira o campo no formulário

Depois que o Rails criou a coluna no banco de dados, ainda precisamos colocar o campo no formulário que o usuário preenche. Para fazer isso, vamos até o arquivo app/views/itineraries/_form.html.erb:


Nele vemos que na linha 3 o método form_with abre a criação de um formulário. Os campos subtotaldiscount são inseridos nas linhas 18 e 23, respectivamente. Na linha 27 temos o método que gera o botão submit, que para o usuário aparece como "Create Itinerary".

Agora vamos criar o nosso campo, customer_email. Acrescentamos as seguintes linhas:

Veja que o nosso campo é bem semelhante aos outros. Temos uma div com classe "field", um método label com o nome do nosso campo, e um método text_field também com o nome. A única diferença entre este e os outros dois, é que os outros são números, então eles têm number_field ao invés de text_field. Isso é uma mescla de HTML tradicional e o que chamamos de "Rails helpers". Falaremos mais sobre Rails helpers em outros posts.

Colocamos estas linhas antes das linhas do subtotal e nosso arquivo do formulário fica assim:

Vamos rodar nosso teste agora? Será que temos novidades?


Sim! Temos novidades!

Ele ainda está falhando, mas não mais no formulário. Lendo a mensagem da falha, vemos que agora se trata da parte em que o e-mail não aparece na tela que nos diz que o roteiro foi criado.

Bora codar isso?

4 - Acrescente o novo dado na tela de show

Depois que o Rails cria um novo registro, ele mostra os dados deste registro para o usuário. A tela onde isso acontece é chamada de show pelo Rails. Precisamos então da show de itineraries.

Vamos encontra-la em app/views/itineraries/show.html.erb:


Usando o subtotal como exemplo, vemos que ele é construído nas linhas 5 a 8. O Rails cria um p na linha 5, dentro dele coloca a palavra "Subtotal" na linha 6, e o valor do subtotal do roteiro em questão na linha 7.

Para acrescentar nosso novo dado, vamos fazer algo parecido com customer_email:

Inserindo estas linhas logo acima de subtotal no arquivo, temos:

Será que agora está tudo pronto? Vamos rodar nosso teste?


Iiihhh, continua falhando. Mas mudou alguma coisa?

Se olharmos rápido, parece que não, porém mudou sim! Veja que apesar do RSpec não estar encontrando o endereço de e-mail inserido, ele está vendo que a tela agora tem o texto "Customer email:".

O que está acontecendo é que apesar de estar recebendo o valor do campo e-mail, o Rails não está salvando isso no banco de dados. Precisamos do passo final para corrigir isso e finalizar.

Bora?

5 - Permita que o controller receba o novo campo

Quando o usuário clica no botão "Create Itinerary", o Rails manda os dados do formulário para o controller dos roteiros, que é chamado de ItinerariesController.

Podemos encontra-lo em app/controllers/itineraries_controller.rb:

Ele foi criado automaticamente pelas mágicas do Rails na hora do scaffold. Este arquivo é responsável por diversas coisas que podemos aprender em outro post. Neste momento, nosso foco será nas linhas 73 a 75, o método itinerary_params:


Para te ajudar a manter a sua aplicação de forma segura na internet, o Rails te dá esta ajudinha de permitir que somente dados previstos entrem na próxima camada. Neste método o Rails diz que para um dado entrar, ele precisa fazer parte de um itinerary e se chamar subtotal ou discount.

Até agora isso era suficiente, mas neste momento estamos implementando uma nova funcionalidade que é um novo dado. Precisamos que o controller aceite nosso dado novo.

Para isso, vamos acrescenta-lo à lista do método:


O que deixa nosso controller assim:

Será que agora vai?

Bora rodar o teste:

Pronto!

Maravilha! Nosso teste agora passou!
Se rodarmos nossa aplicação, conseguiremos criar roteiros incluindo o endereço de e-mail do cliente!

sampope 10: Como usar callbacks para armazenar dados calculados numa aplicação Rails?

Usando callbacks para calcular e armazenar dados numa aplicação Rails.

Considerando que grande parte da motivação para ter rotinas de trabalho num sistema é diminuição da margem de erros, é comum que a gente queira calcular certas informações dentro do sistema, ao invés de permitir que o usuário às insira.

No nosso caso onde a aplicação Rails será responsável por rotinas de uma operadora de turismo, um exemplo deste tipo de campo seria o total de um pacote ou roteiro após o desconto ser aplicado. Ao invés do usuário inserir o subtotal, o desconto, e depois o total (tendo calculado este manualmente), vamos fazer com que ele possa simplesmente colocar o subtotal e o desconto, deixando que o sistema calcule o total final.

Vamos lá?

1 - Crie a classe Itinerary

rails generate scaffold itinerary subtotal:integer discount:integer total:integer

Vamos usar scaffold nesta criação, pois neste momento não estamos preocupados com os detalhes do CRUD. Queremos somente focar neste dado calculado.

Lembre-se de rodar a migração na sequência:
rails db:migrate

(Se quiser mais detalhes sobre esta parte, veja este post, onde focamos nas mágicas do scaffold do Rails.)

2 - Crie um teste para a classe

# spec/models/itinerary_spec.rb

require 'rails_helper'

describe Itinerary do
  it 'saves total' do
    itinerary = Itinerary.create(subtotal: 5, discount: 3, total: 2)

    expect(itinerary.total).to eq 2
  end
end

Podemos falar mais sobre testes de classes em outro post. O mais importante aqui é que ele esteja no diretório certo e especifique valores para subtotal, desconto, e total.

O teste acima foi criado para que ele passe agora, do jeito que a classe está. Como já criamos a classe e uma série de coisas no scaffold do primeiro passo, é importante fazer isso antes e nos certificarmos de que está tudo como esperamos. Veja na minha tela abaixo que está tudo ok, o teste desta forma passou:


Agora que temos certeza de que está tudo em ordem com a classe e o teste neste momento, vamos alterar o teste para o cenário que buscamos. Queremos criar uma situação onde o total não seja inserido, mas sim calculado pela própria classe antes de salvar, certo? Então, o ajuste no nosso teste é retirar o total: 2 dali dos nossos parâmetros e continuar com a expectativa de que o total seja 2, deixando o teste assim:

# spec/models/itinerary_spec.rb

require 'rails_helper'

describe Itinerary do
  it 'saves total' do
    itinerary = Itinerary.create(subtotal: 5, discount: 3)

    expect(itinerary.total).to eq 2
  end
end

E desta vez, quando rodamos o teste, temos problemas:


Tudo certo. Este era o problema que queríamos. O teste está nos dizendo que ao procurar pelo valor 2, ele acabou não encontrando nada. Faz sentido, pois não estamos colocando nada lá.

Agora começa a parte boa!

3 - Crie o método do cálculo

Dentro da nossa classe que no momento está assim:

# app/models/itinerary.rb

class Itinerary < ApplicationRecord
end

Precisamos inserir este método:

def assign_total
  self.total = subtotal - discount
end

Ele pede para que o Rails subtraia o valor do desconto do valor subtotal e armazene esta diferença no total do objeto em questão.

4 - Escolha o callback

O Rails coloca vários callbacks à nossa disposição. Este link nos mostra todos na ordem em que acontecem. Para escolher o que vamos usar, precisamos pensar em duas coisas:
  1. Porque nosso callback vai existir?
  2. Quando ele deve agir?
A resposta da primeira pergunta é que nosso callback vai existir porque queremos tirar do usuário a responsabilidade de calcular o total. Sobre a segunda, ele deve agir quando o objeto for salvo, independente dele estar sendo criado ou atualizado.

Com estas questões esclarecidas, fica claro que o nosso callback vai ser o before_save, afinal ele roda logo antes do objeto ser salvo, não importante se ele já existia ou não.

5 - Configure o callback para chamar o método 

Agora que já temos o método e sabemos qual callback vamos usar, podemos configurá-lo com a seguinte linha:
before_save :assign_total

Nossa classe completa fica assim:

# app/models/itinerary.rb

class Itinerary < ApplicationRecord

  before_save :assign_total

  def assign_total
    self.total = subtotal - discount
  end

end

E quando rodamos novamente o teste, temos:


Passou! Tudo certo!

Pronto!

Agora nossa aplicação Rails não salva mais o valor total vindo do usuário, mas sim o resultado do cálculo feito pela classe antes de salvar os dados no banco!


Um passo além

Agora que a nossa classe não usa mais o valor total passado pelo usuário, não seria melhor impedir que ele insira um no formulário?


Fazemos isso retirando o campo no arquivo app/views/itineraries/_form.html.erb.

Depois de retirar o campo do formulário, é indicado também retirar ele da lista de dados aceitos no arquivo app/controllers/itineraries_controller.rb.

sampope 9: Como testar uma feature com RSpec e Capybara?

Usando RSpec e Capybara para testar uma feature numa aplicação Rails

Depois de criar nossa aplicação Rails, nós instalamos RSpec e Capybara, e até já escrevemos um teste bem simples para testar a nossa home. Agora vamos criar um teste de feature, ainda simples, mas com algumas novidades. Vamos testar a feature de edição da classe Item, que foi criada no post anterior.

Vamos lá?

1 - Crie o arquivo 'user_edits_item_spec.rb' no diretório spec/features

Como nosso outro teste, este é um teste de feature, então ele deve ser criado dentro do diretório features no diretório spec.

O nome segue algumas convenções no sentido de ser descritivo da ação que será testada. O mais importante no nome é o final _spec.rb, pois é este sufixo que faz o Rails e o RSpec saberem que se trata de um arquivo que tem um teste a ser rodado.

2 - Inicie o arquivo com um require do rails_helper

Esta linha é necessária para que as configurações pré estabelecidas sejam carregadas.

# spec/features/user_edits_item_spec.rb

require 'rails_helper'

3 - Acrescente o bloco para a feature

Eu geralmente nomeio o arquivo de teste e a feature dentro do arquivo da mesma forma. Aprendi assim quando aprendi o B+A=BA do Rails e adoro o quanto isso facilita na hora de encontrar testes que precisam ser analisados e/ou atualizados numa alteração qualquer.

# spec/features/user_edits_item_spec.rb

require 'rails_helper'

feature 'user edits item' do
end

4 - Acrescente o bloco do cenário

Por padrão, o primeiro cenário de um teste de feature é aquele onde tudo está perfeito e dá certo. O nosso ideal ao criar a feature.

# spec/features/user_edits_item_spec.rb

require 'rails_helper'

feature 'user edits item' do
  scenario 'sucessfully' do
  end
end

Agora a coisa vai ficar legal! :-) Antes de proceder, precisamos lembrar que um bom teste de feature tem 3 partes:
  1. Configuração: Preparamos o ambiente e a situação
  2. Ação: Fazemos o que queremos testar
  3. Expectativa: Esperamos um determinado resultado

5 - Monte a configuração da situação

No nosso caso, queremos testar a edição de um item. Para que o item seja editado, ele precisa primeiramente existir, então vamos criar um item qualquer.

# spec/features/user_edits_item_spec.rb

require 'rails_helper'

feature 'user edits item' do
  scenario 'sucessfully' do
    Item.create(name: 'example', price: 1)
  end
end

6 - Descreva as ações que devem ser testadas

A ação de editar um item, começa quando o usuário clica num link ou botão de editar, certo? Onde o usuário está neste momento? É lá que precisamos começar.

No nosso caso, o usuário estaria na index dos itens, então vamos começar com ele visitando esta página. Uma vez que ele esteja lá, queremos que ele clique em editar, altere algumas informações, e salve estas alterações.

No ambiente do RSpec com o Capybara, estes passos são simulados assim:

visit items_path
click_on 'Edit'
fill_in 'Name', with: 'changed'
fill_in 'Price', with: 2
click_on 'Update Item'

São várias linhas, mas graças as magias do RSpec e do Capybara, são bem simples e descritiva do que está acontecendo.

7 - Declare o resultado esperado

A terceira parte, conforme vimos a pouco, é a expectativa. Precisamos declarar o que queremos que aconteça quando as ações do passo acima forem executadas.

Neste caso queremos que a aplicação nos diga se deu tudo certo e nos mostre o item com seus dados atualizados. Fazemos isso da seguinte forma:

expect(page).to have_content 'Item was successfully updated.'
expect(page).to have_content 'Name: changed'
expect(page).to have_content 'Price: 2'

Pronto!

Nosso arquivo completo ficou assim:
# spec/features/user_edits_item_spec.rb

require 'rails_helper'

feature 'user edits item' do
  scenario 'sucessfully' do
    Item.create(name: 'example', price: 1)

    visit items_path
    click_on 'Edit'
    fill_in 'Name', with: 'changed'
    fill_in 'Price', with: 2
    click_on 'Update Item'

    expect(page).to have_content 'Item was successfully updated.'
    expect(page).to have_content 'Name: changed'
    expect(page).to have_content 'Price: 2'
  end
end

Vamos rodar o rspec no nosso terminal para ver o que acontece?


Perfeito! Agora temos 2* testes e os dois estão passando**! :-)



* Se você está seguindo a série sampope como um tutorial e fazendo cada passo, possivelmente terá mais de 2 testes, porque no sampope 8 usamos Scaffold, que geralmente cria vários arquivos de teste automaticamente.

** Geralmente um teste que acaba de ser criado não passa, porque a prática correta é primeiro escrever o teste para depois desenvolver a feature. No nosso caso, já tínhamos feito a feature no post anterior. Este post é apenas para mostrar como escrever estes tipos de teste, por isso ele foi criado já passando.

sampope 8: Como usar a mágica do Scaffold para criar um CRUD numa aplicação Rails?

Criando CRUD com a mágica do Scaffold no Rails

CRUD (create, read, update, delete) é o esqueleto básico de qualquer aplicação, praticamente a razão que a maioria existe. Afinal, criar registros que depois são "puxados" para relatórios e/ou cálculos é o que a maioria das pessoas querem quando usam e/ou buscam sistemas.

Para quem já desenvolve em outra linguagem isso não é novidade. E nem deve ser a parte da qual mais gosta. Vamos jogar aberto?

CRUD, apesar de necessário, é meio chato, né? É uma tarefa que podemos chamar de repetitiva. Claro que em geral as classes e os objetos ficam mais complexos e nos desafiam a buscar lógicas e soluções criativas, porém, na essência, todo CRUD é igual:
  • Create = Criação, pegue estas informações e crie dados no banco
  • Read = Consulta, traga estes dados e mostre-os de alguma forma
  • Update = Atualização, mude alguma informação nos dados
  • Delete = Destruição: pode acabar com tudo, não preciso mais destes dados
Para fugirmos da parte repetitiva, e podermos focar nossos esforços nas questões peculiares e/ou de fato inovadoras da nossa aplicação, o Rails nos oferece a magia do Scaffold! Este motor do Rails faz todo o básico do CRUD para nós! Depois a gente só incrementa e customiza conforme a necessidade da aplicação!

Vamos lá?

Para começar temos que ter uma aplicação Rails criada. Nesta série nós fizemos isso no primeiro post.

1 - Rode o comando 'rails generate scaffold'

Este é o comando que vai mandar o Rails criar tudo no nosso CRUD, mas precisamos acrescentar os detalhes como o nome da classe e os campos que ela deverá ter.

No meu caso, a aplicação sampope é um pet project com foco em soluções para turismo. Esta primeira coisa a ser criada será simplesmente chamada de Item.

Um item desta aplicação vai se referir a algo que será vendido. Poderá ser um passeio, um serviço de transporte, ou qualquer outra coisa que fará parte dos roteiros e das vendas. Neste primeiro momento, ele terá apenas nome (name) e preço (price).

O comando completo ficará assim:
rails generate scaffold Item name price:integer

Este comando diz: "Rails, por favor crie todo o CRUD para uma classe chamada Item que tem os atributos nome e preço, sendo que preço é um número."

Note que para price, nós especificamos integer e para name não especificamos nada. Isso é porque na ausência do tipo de dado, o Rails usará seu padrão, que é string, resultando numa coluna do tipo VARCHAR sendo criada no banco de dados. 

Quando a gente rodar o comando, o Rails vai nos avisar das várias coisas que está fazendo. Veja abaixo o que apareceu para mim. Ao longo do desenvolvimento da nossa aplicação vamos aprender mais sobre cada um destes passos.

vivipoit ~/Projects/sampope (master)
> rails generate scaffold Item name price:integer
Running via Spring preloader in process 2540
      invoke  active_record
      create    db/migrate/20200519100714_create_items.rb
      create    app/models/item.rb
      invoke    rspec
      create      spec/models/item_spec.rb
      invoke  resource_route
       route    resources :items
      invoke  scaffold_controller
      create    app/controllers/items_controller.rb
      invoke    erb
      create      app/views/items
      create      app/views/items/index.html.erb
      create      app/views/items/edit.html.erb
      create      app/views/items/show.html.erb
      create      app/views/items/new.html.erb
      create      app/views/items/_form.html.erb
      invoke    rspec
      create      spec/requests/items_spec.rb
      create      spec/views/items/edit.html.erb_spec.rb
      create      spec/views/items/index.html.erb_spec.rb
      create      spec/views/items/new.html.erb_spec.rb
      create      spec/views/items/show.html.erb_spec.rb
      create      spec/routing/items_routing_spec.rb
      invoke    helper
      create      app/helpers/items_helper.rb
      invoke      rspec
      create        spec/helpers/items_helper_spec.rb
      invoke    jbuilder
      create      app/views/items/index.json.jbuilder
      create      app/views/items/show.json.jbuilder
      create      app/views/items/_item.json.jbuilder
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/items.scss
      invoke  scss
      create    app/assets/stylesheets/scaffolds.scss

2 - Rode o comando 'rails db:migrate'

Um dos arquivos criados na ação acima foi o que chamamos de migration, que guarda instruções sobre o que deve ser criado no banco de dados. Rodamos o comando rails db:migrate para executar estas instruções e nos certificar de que está tudo em ordem para os nossos dados serem criados e manipulados lá.

vivipoit ~/Projects/sampope (master)
> rails db:migrate
== 20200519100714 CreateItems: migrating ======================================
-- create_table(:items)
   -> 0.0118s
== 20200519100714 CreateItems: migrated (0.0119s) =============================

Pronto!

É só isso! Agora nós podemos rodar rails s e navegar a nossa aplicação para ver que temos uma index para itens, que podemos criar itens, atualizar informações dos itens, e até excluir alguns! Tudo isso agora existe por causa de apenas 2 comandos!


sampope 7: Como instalar SimpleCov numa aplicação Rails?

Instalando SimpleCov na aplicação Rails

Tínhamos criado a home da nossa aplicação, mas ainda não tínhamos o teste. Depois de instalar o RSpec e o Capybara no post retrasado, conseguimos fazer isso no post passado. Agora, todo o código que criamos está 100% coberto por testes!

Será?

Como podemos ter certeza e como podemos acompanhar isso ao longo do desenvolvimento da nossa aplicação?

São perguntas como estas que a gem SimpleCov responde para nós! Se trata de uma ferramenta que coleta os dados de cobertura dos testes de uma aplicação Rails depois compila e apresenta tudo de uma maneira simples. Com o SimpleCov fica muito mais fácil monitorar o quanto do nosso código está coberto - e protegido - por testes!

Vamos lá?

1 - Acrescente a gem 'simplecov' ao seu Gemfile

Vamos colocar ela no bloco :development, :test do Gemfile, junto com as do RSpec e do Capybara.

gem 'simplecov'

2 - Rode o comando 'bundle install'

Essa parte deve estar ficando repetitiva, mas sempre que inserimos uma nova gem numa aplicação Rails, precisamos rodar bundle install depois.

3 - Acrescente a linha do require e do start do SimpleCov ao arquivo spec_helper.rb

No topo, bem no topo, antes de tudo do spec_helper.rb, vamos colocar as seguintes linhas:

require 'simplecov'
SimpleCov.start

A primeira linha orienta o Rails e o RSpec sobre o fato de que o SimpleCov deve ser carregado, e a segunda diz para o próprio SimpleCov começar a observar o que vai acontecer.

Eu coloco bem no topo mesmo, antes dos próprios comentários. O começo do meu arquivo fica assim:


Pronto!

É só isso! Agora o nosso projeto nos dará o feedback da cobertura de testes toda vez que rodarmos os testes. Veja só:

vivipoit ~/Projects/sampope (master)
> rspec
.

Finished in 0.13024 seconds (files took 3.25 seconds to load)
1 example, 0 failures

Coverage report generated for RSpec to /Users/vivipoit/Projects/sampope/coverage. 67 / 69 LOC (97.1%) covered.

A novidade é a linha que começa com "Coverage report generated blá, blá, blá". Veja que ao final temos uma porcentagem: 97,1%. Isso quer dizer que 97,1% do nosso código foi coberto pelos testes rodados.

Nessa linha também vemos em qual diretório está o relatório de cobertura. Abrimos ele num navegador e clicamos em index.html para ver as informações de forma bem visual e simples de absorver e analisar:


É simples, não é? Com um relatório claro assim, podemos sempre nos certificar de que estamos testando tudo que desenvolvemos!

Dica - Evite comitar os arquivos gerados pelo SimpleCov

O SimpleCov cria estes relatórios e os arquivos dos quais eles dependem num diretório chamado coverage, que acaba sendo monitorado pelo git. Você não precisa comitar nem monitorar estes arquivos no repositório da aplicação. Para que eles não apareçam, acrescente a linha /coverage/* ao arquivo .gitignore.

# .gitignore

# Ignore SimpleCov files
/coverage/*

sampope 6: Como escrever testes numa aplicação Rails?

Escrevendo testes numa aplicação Rails

Agora que já instalamos o RSpec e o Capybara, vamos escrever nosso primeiro teste!

O ideal é sempre começar pelo teste. Nós já quebramos esta regra quando fizemos nossa home, afinal ainda não tínhamos testes na nossa aplicação. Vamos corrigir isso agora e daqui para frente trabalhar com testes sempre!

1 - Crie o arquivo person_sees_home_spec.rb

Ele deve ser criado dentro do diretório spec/features. A parte final (_spec.rb) é a mais importante, afinal é esta informação que o RSpec vai usar para decidir se o arquivo é ou não um teste.

2 - Comece o código com o require para o rails_helper

Antes que o teste possa rodar, o Rails precisa montar e configurar o servidor e o ambiente. O teste em si também vai precisar saber as regras e configurações do seu escopo. Tudo isso é tratado pelo rails_helper ou pelos arquivos que ele carrega. Ao colocar o require no topo do nosso teste, nós nos certificamos de que tudo isso será levando em consideração.

# spec/features/person_sees_home_spec.rb

require 'rails_helper'

3 - Acrescente o bloco que nomeia a feature

Nosso teste precisa especificar a feature que será testada. É importante que a informação usada aqui seja descritiva da situação, pois quando este teste falhar, será ela que nos ajudará a identificar por onde começar nosso trabalho.

# spec/features/person_sees_home_spec.rb

require 'rails_helper'

feature 'person sees home' do
end

4 - Dentro do bloco feature, acrescente o bloco scenario

O bloco cenário é usado para simularmos os diversos possíveis casos e comportamentos. Num teste para uma plataforma de pagamento, por exemplo, devemos testar um pagamento realizado com sucesso e também devemos testar um pagamento com um cartão de crédito vencido, por exemplo.

Geralmente começamos pelo teste de sucesso, afinal é isso que precisamos primeiramente: Que a coisa funcione! Então, o nome do cenário é simplesmente 'sucessfully'.

# spec/features/person_sees_home_spec.rb

require 'rails_helper'

feature 'person sees home' do
  scenario 'sucessfully' do
  end
end

Agora que temos o arquivo criado e organizado, vamos de fato escrever o que o teste precisa fazer!

5 - Mande o teste visitar a home da aplicação

Se queremos testar nossa home, aquela que criamos antes, precisamos que nosso teste navegue até ela. Fazemos isso com os métodos visit e root_path.

O método visit é do Capybara. Ele instrui que queremos navegar para alguma URL.

root_path é do Rails mesmo. Ele gera a URL da raiz da aplicação, conforme configurada no arquivo config/routes.rb. Nós apontamos a nossa raiz à home, então root_path vai gerar uma URL que mostrará ela.

# spec/features/person_sees_home_spec.rb

require 'rails_helper'

feature 'person sees home' do
  scenario 'sucessfully' do
    visit root_path
  end
end

6 - Acrescente o comportamento esperado

Ao contrário das outras áreas das nossas vidas, quando escrevemos testes, precisamos criar expectativas! É assim que saberemos se nossa aplicação está, ou não, fazendo o que gostaríamos que ela fizesse.

Neste caso, estamos testando a nossa home e no momento temos muito pouco nela. Então, nossa expectativa pode ser simplesmente que a palavra "sampope", nome da aplicação, apareça lá. Fazemos isso com acrescentando a linha de código expect(page).to have_content 'sampope'.

# spec/features/person_sees_home_spec.rb

require 'rails_helper'

feature 'person sees home' do
  scenario 'sucessfully' do
    visit root_path
    expect(page).to have_content 'sampope'
  end
end

Pronto!

Nosso teste está completo. Para ter certeza, você pode rodar o comando rspec na sua tela de comando ou terminal. Verá que onde antes dizia 0 examples, agora vai dizer 1 example:

E depois que seu código funciona, o que mais ele pode fazer?

Conhecer um idioma e se comunicar bem nele são a mesma coisa? Nem sempre, né? Você pode saber vocabulário, gramática, e até gírias, mas se n...