Escrever testes unitários para seus modelos Propel ou Doctrine é bem fácil. Neste tutorial, você vai aprender alguns grandes dicas e melhores praticas para escrever melhores testes para seus modelos.
Configuração de Banco de Dados
Para testar classes modelo do Propel, você precisa de um banco de dados. Você ja tem o que você usa para seu desenvolvimento, mas é sempre bom ter o habito de criar um dedicado para testes.
Como todos os testes rodam no ambiente test, só precisamos editar o arquivo de configuração
config/databases.yml e sobrescrever os valores padrões para o ambiente test:
test:
propel:
param:
dsn: mysql:dbname=myproject_test;host=localhost
dev:
# dev configuration
all:
propel:
class: sfPropelDatabase
param:
dsn: mysql:dbname=myproject;host=localhost
username: someuser
password: somepa$$word
encoding: utf8
persistent: true
pooling: true
classname: PropelPDO
Nesse caso, nós mudamos apenas o nome do banco de dados, mas você pode também mudar o motor de banco de dados para usar por exemplo o SQLite.
Agora que temos o banco de dados configurado, podemos criar as tabelas, usando a task
propel:insert-sql:
$ php symfony propel:insert-sql --env=test
Dados de Teste
Agora que temos uma base dedicada para nossos testes, precisamos dar um jeito para carregar dados para o teste(fixtures) para cada vez que rodamos os testes unitários. Isso é porquê nós queremos colocar na base de dados o mesmo estado para todas as vezes que rodarmos nossos testes.
Isso é bem simples, graças a classe sfData:
$loader = new sfPropelData(); $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
O método loadData() recebe um diretório ou um arquivo como primeiro argumento.
Normalmente, um diretório fixtures é mais ou menos assim:
test/
fixtures/
10_categories.yml
20_articles.yml
30_comments.yml
Veja o prefixo numérico no nome de todos os arquivos. Essa é uma forma simples de
controlar a ordem de carregamento dos dados. Mais tarde se precisarmos inserir novos
arquivos fixture, vai ser fácil pois temos vários números livres entre os existentes:
test/
fixtures/
10_categories.yml
15_must_be_laoded_between_categories_and_articles.yml
20_articles.yml
30_comments.yml
Os leitores mais atentos irão notar que colocamos nossas fixtures no
diretório test/, enquanto o livro do symfony diz para colocarmos no diretório data/.
Essa é uma questão de gosto, mas eu gosto de organizar minhas fixtures nesses dois
diretórios porque assim posso categorizar as fixtures em dois grupos diferentes:
data/fixtures: com todos os dados iniciais para a aplicação funcionar.test/fixtures: com todos os dados necessários para os testes (unitarios e funcionais)
Esse jeito simples funciona muito bem para um pequeno agrupamento de dados de teste, mas
quando seu modelo cresce, você tem um monte de fixtures, e o tempo necessário para carregar
eles na base de dados torna-se importante. Então, temos que dar um jeito de carregar somente
um sub-grupo de dados de teste. E uma forma de fazer isso é sub-categorizando seus dados
de teste criando subdiretórios de acordo com os recursos principais do projeto:
test/
fixtures/
10_cms/
10_categories.yml
20_articles.yml
30_comments.yml
20_forum/
10_threads.yml
Agora, ao invés de carregar o diretório principal fixtures, podemos carregar somente um
dos subdiretórios, dependendo de quais classes de modelo queremos testar. Mas na maior parte do
tempo, também precisaremos carregar dados compartilhados, como users:
test/
fixtures/
00_common/
10_users.yml
10_cms/
10_categories.yml
20_articles.yml
30_comments.yml
20_forum/
10_threads.yml
Para facilitar esse tipo de uso, o método é capaz de receber um array de diretórios e/ou arquivos:
// load users and all the CMS data $loader = new sfPropelData(); $loader->loadData(array( sfConfig::get('sf_test_dir').'/fixtures/00_common/10_users.yml', sfConfig::get('sf_test_dir').'/fixtures/10_cms', ));
Assim, carregamos o arquivo fixture 10_users.yml e então todos os arquivos de
fixture no diretório 10_cms.
Escrevendo Testes Unitários
Temos uma base dedicada e uma forma de colocar nossa base de dados em um estado conhecido, vamos criar
algunas testes unitários para o modelo Article.
Aqui um tipico arquivo de bootstrapping de teste unitário para Propel:
// test/unit/model/ArticlePeerTest.php include(dirname(__FILE__).'/../../bootstrap/unit.php'); $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true); new sfDatabaseManager($configuration); $loader = new sfPropelData(); $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures'); $t = new lime_test(1, new lime_output_color()); $t->diag('::retrieveBySlug()'); $article = ArticlePeer::retrieveBySlug('the-best-framework-ever'); $t->is($article->getTitle(), 'The Best Framework Ever', '->retrieveBySlug() returns the article that matches the given slug');
Esse script é praticamente auto-explicável:
Como para todo teste unitário, incluímos o arquivo de bootstrapping.
include(dirname(__FILE__).'/../../bootstrap/unit.php');
Criamos um objeto de configuração para o ambiente
teste habilitamos o debug:$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true);
Isso também inicializa o
autloadingdas classes do Propel.Criamos um gerenciar de banco de dados. Ele inicializa a conexão Propel, carregando o arquivo de configuração
databases.yml:new sfDatabaseManager($configuration);
Carregamos nossos dados de teste, usando a classe
sfPropelData:$loader = new sfPropelData(); $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures');
Agora que esta tudo pronto, podemos começar a teste nossos objetos de modelo.
Se você não esta acostumado a escrever testes unitários, pode ser intimidante no começo.
Algumas dicas que uso o tempo todo para saber o que eu preciso testar:
- Teste um método de uma classe por vez
- Teste que para uma dada entrada, o retorno do método é o que você esperava
- Leia o código do método e teste todas as regras de negócio que você pode ter.
- Nunca teste coisas obvias ou coisas que são feitas por outros métodos
Meus arquivos de teste, sempre são estruturados com o mesmo padrão:
// output a message with the method you test (-> for instance methods, and :: for class methods) $t->diag('->methodName()'); // teste uma coisa de cada vez que pode ser expressada com uma única sentença // a sentença sempre começa com o nome do método // então um verbo expressando o que deve ser feito, como deve comporta-se, ...etc $t->is($object->methodName(), 1, '->methodName() returns 1 if you pass no argument');
Cobertura de Código
Quando você escreve teste, é fácil esquecer de testar uma condição em um código compexo.
O symfony 1.2 vem com uma nova task para testar a cobertura de código, test:coverage.
Após escrever meus testes para um classe qualquer, Eu sempre rodo a task test:coverage
para ter certeza que testei tudo:
$ php symfony test:coverage test/unit/model/ArticleTest.php lib/model/Article.php
O primeiro argumento é o arquivo de teste ou um diretório. O segundo é um arquivo ou diretório que eu quero saber a cobertura de código.
Se você quiser saber quais linhas não foram cobertas pelo teste, simplesmente coloque a opção --detailed:
$ php symfony test:coverage --detailed test/unit/model/ArticleTest.php lib/model/Article.php
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.