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: database: myproject_test dev: # dev configuration all: propel: class: sfPropelDatabase param: datasource: propel phptype: mysql hostspec: localhost database: myproject username: someuser password: somepa$$word
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.
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 (unitários 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 carregar vários diretórios e arquivos de teste, podemos chamar o método loadData()
quantas vezes precisarmos:
// load users and all the CMS data $loader = new sfPropelData(); $loader->loadData(sfConfig::get('sf_test_dir').'/fixtures/00_common/10_users.yml'); $loader->loadData(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
test
e habilitamos o debug:$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'test', true);
Isso também inicializa o
autloading
das 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');
This work is licensed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License license.