Mostrando postagens com marcador erros. Mostrar todas as postagens
Mostrando postagens com marcador erros. Mostrar todas as postagens

quinta-feira, 7 de janeiro de 2010

Como realmente testar: asserções

Falei bastante sobre testes nos dois últimos posts, conceituei testes funcionais e de unidade, falei sobre como criar fixtures, mas faltou a prática de verdade: como usar tudo isso para testar tudo?

Como já disse, testes são conjuntos de asserções. Uma asserção verifica se os parâmetros indicados nela estão válidos, se não estiverem ela retorna uma Falha. O teste executa asserção por asserção, se alguma falhar ele é interrompido, aponta a falha e diz qual das asserções falharam. Se o teste encontrar qualquer erro de sintaxe ou de execução, em vez de retornar uma falha, ele retorna um Erro. Isso já faz ter uma noção simples: caso haja uma Falha, houve erro de lógica, caso haja um Erro, houve erro de codificação.

O exemplo mais básico de asserção é o assert, que verifica se o parâmetro passado é true (lembrem-se que, em Ruby, tudo o que não for nil ou false é verdadeiro.

Porém, apenas com assert não dá para fazer tanta coisa. Sim, podemos ver se uma validação está funcionando corretamente (sim, eu prometo explicar validações), podemos ver se um objeto é igual ao outro usando operadores lógicos, podemos ver se um objeto é instância de determinada classe. Mas fazer tudo isso com assert torna as coisas mais ilegíveis, e é um tanto mais difícil identificar para que serve uma certa asserção. Por isso, existem vários tipos de asserções utilizáveis.

Para aprender outros tipos de asserções, vamos ter alguma ideia de outras funcionalidades para o modelo Pessoa. Digamos que você queira um método que diga o salário líquido da pessoa. Digamos que, caso a pessoa receba menos de 200 reais ela não pague imposto algum. Se o salário for maior ou igual a 200 e menor que 300 ela pague 10% de imposto, se e for 300 ou maior ela pague 20% (impostos tão baixos? Sonho de todo brasileiro...).

Antes de TUDO, vamos abrir aquela fixture para termos dados para testar. Apague tudo o que estiver na fixture e digite isso:

<% for i in (1..4) %>
pessoa_<%= i %>:
  id: <%= i %>
  nome: pes_<%= i %>
  salario: <%= i * 100 %>
  data_nascimento: <%= Date.civil(1980, i, i) %>
<% end %>


Sim, bem parecido com o último exemplo, mas dessa vez há apenas quatro dados.

Agora, ao teste.

Abra o arquivo test/unit/pessoa_test.rb

Insira, abaixo do método definido anteriormente, o seguinte código:

def test_salario_liquido_correto
  assert_equal 100, pessoas(:pessoa_1).salario_liquido
  assert_equal 180, pessoas(:pessoa_2).salario_liquido
  assert_equal 240, pessoas(:pessoa_3).salario_liquido
  assert_equal 320, pessoas(:pessoa_4).salario_liquido
end


Lembram que eu ensinaria como acessar dados da fixture? Aí está. O método tem o mesmo nome do arquivo que guarda os dados (se o arquivo for pessoas.yml, o método é pessoas), e para recuperar um registro específico, você passa como parâmetro o símbolo equivalente à definição do registro.

Por exemplo, no velho exemplo do "programador" (não mude o código da fixture, ok?):

programador:
  id: 1
  nome: Yuri Albuquerque
  salario: 100.00
  data_nascimento: <%= Date.civil(1989, 11, 9) %>

Você acessaria o registro com pessoas(:programador). Sim, simples assim.

Ao fazer isso, você tem uma instância do modelo, e pode chamar qualquer método dele. Por isso o pessoas(:pessoa_1).salario_liquido, eu estou acessando um método da instância do modelo Pessoa.

E atente para a ordem dos parâmetros dada. Ela não é imprescindível, mas, por padrão, o valor esperado vem antes do valor real.

Agora abra o console, vá até o diretório da aplicação e digite

rake


Depois de um tempo você verá que o teste indicou um Erro. E era isso que você deveria esperar, afinal, você tentou acessar um método que não existe (salario_liquido).

Se ele não existe, vamos criá-lo.

Abra o arquivo app/models/pessoa.rb

Lembre-se que esse arquivo define tudo do modelo, seus métodos de instância, seus métodos de classe, suas validações, seus atributos. Se você for um bom programador de Rails, vai fazer boa parte das implementações no Modelo e deixar o controlador cuidar apenas dos redirecionamentos. Não é o meu caso, infelizmente.

Acrescente esse método à classe:

def salario_liquido
  return(salario) if salario < 200
  return(salario * 0.9) if salario >= 200 and salario < 300
  return(salario * 0.8) if salario >= 300
end


Creio que eu não precise explicar o que esse método faz. Rode o rake novamente e veja seu teste apontar um sucesso.

É óbvio que assert e assert_equal não são os únicos métodos dos testes de unidade do Rails. Existem métodos para as mais diversas situações. Vamos a alguns deles:

  • assert_not_equal(obj1, obj2): é o oposto de assert_equal, verifica se os parâmetros dados são diferentes
  • assert_same(obj1, obj2): levemente diferente de assert_equal. Verifica se ambos os objetos têm o mesmo endereço de memória
  • assert_not_same(obj1, obj2): verifica se os objetos não são os mesmos
  • assert_nil(obj): verifica se o objeto está vazio, ou seja, nil
  • assert_not_nil(obj): verifica se o objeto não está vazio
  • assert_match(regex, str): verifica se a string passada combina com a Expressão Regular passada (Expressões Regulares são um assunto comprido, complexo, que enchem livros e livros. Não é o objetivo desse blog detalhá-las).
  • assert_no_match(regex, str): verifica se a string passada não combina com a Expressão Regular passada
  • assert_in_delta(expect, actual, delta): verifica se os números passados estão equivalentes dentro da faixa de tolerância delta dada.
  • assert_throw(symbol) { block }: verifica se o bloco de código "lança" (throw) o símbolo. Blocos de código são uma característica da linguagem Ruby, caso não conheça, sugiro pesquisar sobre eles imediatamente. Mas funciona como passagem de código por "parâmetro".
  • assert_raise(exception1, exception2, ...) { block }: verifica se o bloco de código retorna os erros (exceptions) dados
  • assert_nothing_raised(exception1, exception2, ...) { block }: verifica se o bloco não retorna nenhum dos erros dados
  • assert_instance_of(classe, objeto): verifica se o objeto percence à classe dada.
  • assert_kind_of(classe, objeto): verifica se o objeto percence a alguma classe filha da classe dada.
  • assert_respond_to(objeto, symbol): verifica se o objeto possui o método com o mesmo nome do símbolo.
  • assert_operator(obj1, operator, obj2): verifica se é possível executar obj1.operator(obj2)
  • assert_send(array): verifica se o método listado na array[1], executado no objeto na array[0], com os parâmetros na array[2] em diante retornam true. Sim, bem confuso. Mas não se preocupem muito com esse, por enquanto, particularmente nunca me foi necessário. Tentarei dedicar um post a ele, assim que aprendê-lo exatamente.
  • flunk: sempre retorna uma falha. Bom para indicar testes incompletos.
Lembre-se de testar tudo o que for possível no seu código. Os guias do Ruby on Rails (guias.rubyonrails.pro.br) sugerem fazer pelo menos um teste para cada validação e para cada método. David H. Hansson (de novo o autor de Programando Rails "A Bíblia", vocês vão me ver fazendo várias referências a esse livro) vai ainda mais longe, sugerindo fazer um teste para cada asserção, e pelo menos uma asserção por método. Ele sugere dessa forma porque você saberá exatamente o que está testando, e saberá o que deve corrigir caso algo dê errado.

Particularmente, não uso essa aproximação. Já me basta saber qual asserção falhou quando elas estão agrupadas em testes um pouco mais gerais. Testo mais por situações.

Um bom exemplo é quando lidei com um sistema de autenticação. Fiz um teste para verificar se o usuário logava quando os dados estavam corretos, e outro para verificar se ele NÃO logava quando algum dado estava incorreto. Claro que poderia ter ido um pouco mais longe, e feito um teste para verificar se ele não logava caso o e-mail estivesse incorreto, outro caso a senha estivesse incorreta, outro caso ambos estivessem incorretos, e assim por diante.

Mas agrupar as asserções de testes mais parecidos num único teste não me trouxeram muitos problemas. Com o tempo, você (e sua equipe, claro) vão desenvolver sua própria técnica de testes, e é importante que todos concordem com a forma como tudo está sendo testado.

O importante é programar satisfeito.

sexta-feira, 4 de dezembro de 2009

Testando seus modelos no Rails - parte 1

Vou começar com uma citação do livro Programando Rails "A Bíblia", de Obie Fernandes. A citação é de "Wilson":
Escrever aplicações sem testes faz de você uma pessoa má, incapaz de amar

Exagero, certo? É... talvez não. Rails foi feito com testes em pensamento. Os desenvolvedores testam incansavelmente todas as funcionalidades, você não pode submeter um código novo sem um respectivo teste funcional ou de unidade. Por isso o framework conta com uma biblioteca de testes poderosa, a Test::Unit, da família xUnit, muito usada em várias linguagens, incluindo .NET, Java e C++.

Mas testar pra quê?

Verdade seja dita, a pergunta deveria ser "por que não testar?". Hoje, com a ascenção dos métodos ágeis de programação, testes automatizados têm se tornado importantíssimos. Algumas metodologias, como XP (eXtreme Programming) obrigam você a programar os testes ANTES de programar a aplicação, em si.

Agora você deve estar pensando "mas você não respondeu à pergunta. Para quê testar? Eu programo muito bem, confio no meu código, ele não vai dar erro".

Vai. Mesmo com todos os testes automatizados e profissionais da qualidade de software, o usuário final VAI encontrar erros no programa, e vai se irritar com isso ou se aproveitar disso.

Por que programar testes antes de programar a aplicação? Não faz sentido, o que vai ser testado?

Na verdade, adotar essa ideia de programar primeiro os testes já facilitou a vida de muita gente. Programando os testes, você saberá o que o programa deverá fazer (pois você terá nos testes os resultados que devem ser retornados); o teste funcionará como uma documentação rápida das capacidades do programa/ acúmulo de erros no decorrer do projeto costumam ocorrer (o que torna as coisas mais difíceis de serem rastreadas e corrigidas), mas se você fizer testes serão detectados mais cedo; e, além de tudo isso, há de se lembrar que Ruby é uma linguagem interpretada. Se você não executar testes, como saber se você não cometeu nenhum erro de sintaxe?

Serei sincero. Eu não costumo programar testes antes de programar a aplicação. Mas isso já me fez recomeçar projetos inteiros do zero por excesso de bugs. Então, acredite quando eu digo que testes automatizados são muito úteis para tornar as coisas menos complexas.

Ok, agora vamos começar com a mão na massa. Abra a pasta /test do seu projeto. Você verá as seguintes pastas e arquivo:





Para quem está acostumado com nomenclaturas de testes automatizados, os nomes mudam um pouco. Por partes:

  • Fixtures - Essas são as vilãs heróicas. Muita gente não gosta de fixtures, mas a utilidade delas é imensa. Fixtures são dados escritos como um arquivo YML (que explicarei na próxima parte) que serão carregados pelo banco de dados de teste para executar qualquer teste que você queira. Cuidado ao referenciar uma fixture dentro da outra: nunca se sabe qual é a fixture que será lida pelo banco de dados primeiro.

  • Functional - Functional tests, no Rails, são bem semelhantes a functional tests normais, mas são focados em controladores. Esse tipo de teste deve capturar os requerimentos do usuário e deve cuidar para que esses requerimentos sejam preenchidos. Imagine-se fazendo casos de uso, muitos e muitos casos de uso. Quando você finalmente termina... não acontece nada, só tem um monte de bolinhas. No caso de um functional test, você cria testes semelhantes a caso de uso, e faz a aplicação de forma que passe nos testes feitos! Enquanto unit tests testam sob um ponto de vista da programação, functional tests testam sob ponto de vista do usuário. O que quer dizer que você deve ver se o sistema está rodando conforme as necessidades do usuário. Sim, isso quer dizer que eles são essenciais.

  • Integration - Integration tests têm um significado parecido com o tradicional. Permitem a você testar grandes cadeias de código e ver se cada uma das partes do código trabalham bem em conjunto. Provavelmente serão pouco usados, por serem necessários em casos bem específicos e lentos.

  • Performance - É um nome bem sugestivo. Performance tests testam o quanto sua aplicação está demorando para atender aos requisitos, e onde elas mais estão demorando. Bem útil, não?

  • Unit - Unit tests são bem diferentes no Rails com relação à nomenclatura normal. Enquanto normalmente representam testes de funcionalidades isoladas, sob ponto de vista do programador (no sentido de que o programador imagina determinada função ou objeto e vai testá-la), no Rails representam Functional Tests de Modelos. Apenas isso. Você pode referenciar um Modelo num teste de outro sem problemas. É meio confuso usar o termo "Unit test" para um teste que, na verdade, é funcional. Mas se você ignorar isso vai viver bem. :)


Os testes de Rails são executados com o banco de dados site_test (ou comercio_test, ou hotel_test, ou qualquer nome que você tenha colocado na aplicação). É bem difícil fazer um teste que não envolva banco de dados, então não é recomendável.

No próximo post eu ensinarei a programar os testes e a executá-los.