Caution: You are browsing the legacy symfony 1.x part of this website.

Teste de Unidade no seus Modelos

1.2
Symfony version
1.1
Language

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 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');

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