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

quarta-feira, 6 de janeiro de 2010

Para evitar o tédio, fixtures

Duas coisas aconteceriam se você fizesse testes apenas como ensinei no último post: tédio seria a primeira, porque você precisaria criar novas instâncias dos Modelos o tempo todo. A segunda seria a falta de instâncias de Modelos das tabelas que não fazem parte do teste (o que também geraria tédio, já que você precisaria criar cada uma dessas instâncias).

Para resolver isso, existem as fixtures.

Mas o que são fixtures, afinal?

São arquivos YML (Yet another Markup Language, não confundir com XML) que definem instâncias dos modelos. Os arquivos são criados quando você gera os modelos (exatamente como os testes de unidade), mas ficam "vazios", apenas com comentários ensinando a usá-los.

Criar instâncias de uma classe com uma fixture é fácil. Digamos que você queira criar uma instância que você acesse com "programador" (você verá como acessar fixtures mais tarde, então não se preocupe com isso, por enquanto). Tudo o que você precisaria fazer é abrir o arquivo pessoa.yml na pasta test/fixtures, apague tudo o que estiver no arquivo, e digite:



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



Atente para a identação, ela deve ser dois espaços, e não TAB.

As fixtures são uma maneira bastante concisa de se armazenar dados de testes. Elas são intuitivas para quem vê, são carregadas sempre que os testes são inicializados, e não apresentam muitas dificuldades de acesso.

Porém têm algumas desvantagens. Elas tornam as suítes (conjuntos) de testes lentas; permitem dados inválidos; é complicado manter um grande número de dados em fixtures; e elas são frágeis, no sentido de que uma alteração numa fixture pode afetar o resultado dos seus testes (desvantagens tiradas do livro Programando Rails, "A Bíblia", de David H. Hansson).

Todavia, você pode evitar praticamente tudo com um certo cuidado. Para evitar lentidão, crie apenas fixtures estritamente necessárias (obviamente, isso também evita um grande número de dados). Para evitar dados inválidos, evitem escrever dados inválidos (afinal, você tem total controle sobre o que está numa fixture). Quanto à fragilidade, tome cuidado redobrado com todos os seus testes, o seria uma atitude boa mesmo que você não usasse fixtures.

Finalmente, explicando o código anterior. Boa parte é lógico e intuitivo. A primeira linha indica que toda a identação seguinte será armazenada numa instância "programador". Cada uma das linhas seguintes define um campo da instância.

Mas... o que quer dizer dizer aquele <%= Date.civil(1989, 11, 9) %> ali no meio? É algo bem simples, na verdade. Chama-se expressão. Quando você inicializar as fixtures (o que é feito automaticamente quando você inicia seus testes com rake), o Ruby se encarregará de substituir todo o código entre <%= %> pela string equivalente.

Nesse caso, <%= Date.civil(1989, 11, 9) %> vai ser lido como 1989-11-09.

Mas... por que escrever expressões, quando você pode inserir o dado que você quer?

Lembrem-se: a fixture não filtra dados inválidos. O uso de expressões é uma das muitas formas de evitar a inserção de dados inválidos. Além do quê, muitas vezes o uso de uma expressão é imprescindível, como verá a seguir.

Você deve estar pensando agora "se o Ruby reconhece o código dentro da expressão, eu posso usá-la para fazer um loop e criar várias instâncias de uma vez?". Não. Não pode. A expressão SEMPRE vai ser convertida em string, então tentar executar um loop vai resultar em nil. "Que pena", você deve estar pensando. "Eu gostaria muito de poder gerar dados automaticamente, em vez de digitá-los à exaustão".

E você pode. Só não vai usar expressões para isso, e sim scriplets.

Scriplets são muito parecidos com expressões, inclusive na sintaxe. A principal diferença é que eles NÃO são convertidos em strings, então você pode usá-los para gerenciar códigos e variáveis diretamente no arquivo, e inclusive criar loops.

Um scriplet é definido entre <% %>. Sim, é só tirar o sinal de igual. Vamos dizer que você queira definir cinco fixtures quaisquer de Pessoa. Você pode muito bem, em vez de digitar de uma por uma, fazer isso:


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


Como tudo no Ruby, há mais de uma maneira de escrever a mesma coisa. Minha forma preferida é substituir <% for i in (1..5) %> por <% 1.upto 5 do |i| %>, mas isso não vem ao caso. O que importa é como o Ruby vai enxergar sua fixture depois do processamento, e será assim:


pessoa_1:
  id: 1
  nome: pes_1
  salario: 100
  data_nascimento: 1980-01-01

pessoa_2:
  id: 2
  nome: pes_2
  salario: 200
  data_nascimento: 1980-02-02

pessoa_3:
  id: 3
  nome: pes_3
  salario: 300
  data_nascimento: 1980-03-03

pessoa_4:
  id: 4
  nome: pes_4
  salario: 400
  data_nascimento: 1980-04-04

pessoa_5:
  id: 5
  nome: pes_5
  salario: 500
  data_nascimento: 1980-05-05


Claro que a quantidade de fixtures vai contra a ideia de fazer apenas a quantidade necessária, mas é só um exemplo didático, então não há problemas.

E mais uma dica: lembre-se sempre das expressões e dos scriplets. Eles vão ser SEMPRE necessários, mesmo quando você não estiver fazendo testes

sábado, 12 de dezembro de 2009

Unit tests para testar modelos

Agora aprofundarei mais no assunto Unit Tests. Na verdade, o uso desse termo no Rails é incorreto, porque "testes de unidade" fazem testes de mecanismos e/ou objetos de forma independente do restante do sistema.

Em outras palavras, quando você faz um teste de unidade, testa apenas aquela unidade do sistema. Caso você necessite de interação com outros objetos nesse tipo de teste, você recorre a objetos mock, que seriam simulações ou simplificações dos objetos reais.

Esse isolamento é necessário para manter as interfaces do objeto simples e o código limpo, com um design simplificado.

No Rails, por outro lado, esse isolamento não ocorre. Por quê? Não tenho certeza. Mas, na prática, o funcionamento dos testes de unidade no Rails não diferem tanto quanto um teste de unidade comum, já que você provavelmente não vai chamar nenhuma função de um modelo diferente do testado. A diferença acaba sendo que você não se utiliza de mocks (o que pode até ser algo bom).

No Rails, os testes de unidade testam os modelos (lembra deles?). Vamos criar um modelo para fazer um teste simples do Rails.

Primeiro, abra o console (ou prompt) e vá ao diretório raíz do seu projeto. Depois, digite:
ruby script/generate model Pessoa nome:string data_nascimento:date salario:decimal

Explicando: a pasta script contém várias utilidades para ajudá-lo na criação da sua aplicação. O script "generate" gera o que você mandar ele gerar, seja um modelo, um controlador, migração ou scaffold. Explicarei tudo isso em posts futuros.

O parâmetro model serve para especificar que o que você está gerando é um modelo. Pessoa diz qual é o nome do modelo. Os outros definem os atributos do mesmo (e da respectiva tabela).

Agora, digite
rake db:migrate


Quando você gera um modelo, também gera uma migração (na pasta db/migrate) respectiva àquele modelo. A migração gera a tabela no banco de dados, com o comando acima. Bem mais simples que digitar todo o SQL, não?

Junto com o modelo gerado, também é criado um teste de unidade na pasta test/unit. Abra-o. Ele já possui uma asserção, que sempre retornará verdadeiro. Digite
rake

No console e você verá um sinal de sucesso. Não é o que queremos, por enquanto.

Abra o teste (em test/unit/pessoa_test.rb). Apague as linhas


test "the truth" do
  assert true
end

Agora vamos começar a testar, mesmo. Por enquanto, não temos nenhuma funcionalidade especial em mente, então vamos começar com uma interessante. O usuário só pode se cadastrar se ele digita um nome.

Onde havia o código anterior, digite.


def test_usuario_cadastrou_um_nome
  pessoa = Pessoa.new
  pessoa.salario = 1000.00
  pessoa.data_nascimento = Date.civil(1989,11,9)
  assert !pessoa.save
end

Esse código é bem simples. Se vocês não sabem o que é "def", voltem ao Ruby antes de estudar Rails, é simplesmente a definição de um método. Todos os métodos da classe que começam com "test" são o teste, em si. Todas as asserções dentro do teste serão analizadas, e caso todas sejam válidas, o teste contará como um sucesso.

As três linhas seguintes são óbvias: estamos declarando uma pessoa e definindo um salário e uma data de nascimento para ela.

A linha "assert !pessoa.save" é a chave do teste. Assert é uma asserção (que eu tanto falei há algumas linhas e ainda não expliquei). Asserções validam o que há dentro delas. Se realmente for algo válido, a asserção retorna um sucesso. Senão, ela pára o teste e avisa que houve uma falha.

A asserção mais simples é "assert", que verifica se o que está dentro dela é true.

O que nos leva à questão: o método pessoa.save retorna um boolean?

A resposta é sim. Sempre que o Rails tenta salvar um registro no banco de dados, ele retorna true caso ele tenha conseguido e false caso não tenha conseguido. Nesse caso, nós queremos que retorne false, porque não queremos que ele consiga salvar o registro, já que o nome da pessoa não foi definido. Por isso o "!" antes de pessoa.save, ele simboliza "não" em muitas linguagens de programação, inclusive Ruby.

Vá no console e digite rake novamente. Você vai ver o teste falhar.

"Tudo isso para um teste falho? Mas não queríamos fazer um programa, afinal de contas?"

Novamente, estamos testando ANTES de fazer o código que vai validar o teste. Isso é o que chamamos de Test-Driven Development, ou Desenvolvimento Orientado a Testes. Sempre que você for programar, primeiro faz os testes automatizados, roda-os, vê falharem, e faz os códigos o mais simples possíveis para passarem nos testes.

Dessa forma, você terá um sistema simples, legível e com o mínimo de bugs.

Agora vamos fazer o teste retornar um sucesso.

Abra o arquivo app/models/pessoa.rb. Insira dentro da classe a linha


validates_presence_of :nome


Essa linha é bem óbvia. É um validador, e vai verificar se o usuário lembrou de digitar seu próprio nome. Não se preocupe com isso, por enquanto, também vou explicar em posts futuros.

Rode o rake de novo e veja o teste retornar um sucesso. Parabéns! Você fez um teste de unidade!

No próximo post, explicarei fixtures e como elas podem melhorar seus testes.