Ir para o conteúdo
ou

Software livre Brasil

 Voltar a Gustavo Dutr...
Tela cheia

Como fazer testes unitários em Models no CakePHP

29 de Julho de 2010, 0:00 , por Software Livre Brasil - 0sem comentários ainda | Ninguém está seguindo este artigo ainda.
Visualizado 712 vezes

Olá! Dando continuidade ao post anterior Introdução a testes unitários no CakePHP e SimpleTest, este post tenta explicar como testar models.

A camada de modelo (model) geralmente é conhecida pela sua capacidade de abstrair as fontes dos dados, tornando o sistema independente de banco de dados, isto é, independente se é utilizado MySQL, Postgres ou até mesmo arquivos CSV ou XML.

Pelo fato desta camada ser responsável pela manutenção dos dados do sistema, é de extrema importância testá-la. Os testes unitários devem garantir que esta esteja funcionando de acordo, para evitar incosistências.

Vamos criar os testes unitários desde o início, desde a criação das tabelas do banco de dados, passando pelos testes e a criação do model em si.

Vamos criar no banco de dados, que já suponho estar configurado no CakePHP, a tabela com a qual vamos trabalhar. Vamos criar um Model simples para produtos.

CREATE TABLE `products` (
   `id` INT(15) UNSIGNED NOT NULL AUTO_INCREMENT,
   `name` VARCHAR(200) NOT NULL,
   `description` TEXT,
   `price` DOUBLE(10,2) UNSIGNED NOT NULL,
   PRIMARY KEY (`id`)
)
comment = 'Produtos'
engine = InnoDB

Tendo esta tabela no banco, podemos utilizar o próprio CakePHP para gerar automaticamente o fixture e o model. Para isto vamos executar o seguinte:

cd meu_projeto/app
../cake/console/cake bake model Product

Interagindo com o Shell do cake, você pode criar seu model de forma simples e prática. Observe que ele criou 2 arquivos:

app/tests/fixtures/product_fixture.php
app/tests/cases/models/product.test.php

Em app/tests/fixtures/product_fixture.php vamos definir nossos dados de teste, os dados que utilizaremos nos testes. Para isto, basta ajustar a propriedade $records:

   public $fields = array(
      'id' => array(
         'type' => 'integer',
         'null' => false,
         'default' => NULL,
         'length' => 15,
         'key' => 'primary'
      ),
      'name' => array(
         'type' => 'string',
         'null' => false,
         'default' => NULL,
         'length' => 200
      ),
      'description' => array(
         'type' => 'text',
         'null' => true,
         'default' => NULL
      ),
      'price' => array(
         'type' => 'float',
         'null' => false,
         'default' => NULL,
         'length' => '10,2'
      ),
      'indexes' => array(
	     'PRIMARY' => array('column' => 'id', 'unique' => 1)
      ),
      'tableParameters' => array(
         'charset' => 'utf8',
         'collate' => 'utf8_general_ci',
         'engine' => 'InnoDB'
      )
   );
   public $records = Array(
      Array(
         'id' => 1,
         'name' => 'Nome do Produto',
         'description' => 'Descrição longa',
         'price' => 15.23
      ),
      Array(
         'id' => 2,
         'name' => 'Nome do Segundo Produto',
         'description' => 'Descrição super longa',
         'price' => 12
      )
   );

Pronto, nosso fixture se encontra com a estrutura da tabela na propriedade $fields e os dados na propriedade $records.

Em app/tests/cases/models/product.test.php ficarão nossos testes. O CakePHP já escreve algumas coisas no arquivo:

public $fixtures = array('app.product');

É essencial que todos os models relacionados devam ter seus fixtures adicionados a esta propriedade, pois senão ele não é capaz de criar as tabelas com os dados de teste.

public function startTest) {
   $this->Product = ClassRegistry::init('Product');
}

Este método é executado sempre antes de cada teste (de ser executado o método que inicia com test). É interessante ter o objeto sempre “reiniciado” ao se fazer cada teste, pois senão um teste pode influenciar no valor do outro. Por exemplo, se o campo name tivesse um índice unique, poderia ter erro caso dois métodos de teste tivessem o mesmo valor sendo inserido, sendo que não era isto que estava sendo testado.

Para ter certeza que o objeto está sendo reinicializado, eu sugiro adicionar (somente necessário nos testes de 1.2.X):

public function endTest() {
   unset($this->Product);
   ClassRegistry::flush();
}

Assim temos mais garantia de que tudo vai funcionar como o esperado.

O teste que o CakePHP 1.2.X insere automaticamente não deixa de ser importante, porém, na versão 1.3.X do CakePHP este teste não é mais inserido automaticamente.

public function testIsA...() {
   $this->asssertIsA($this->Product, 'Product');
}

Quando o ClassRegistry::init() não acha o arquivo com o model sendo inicializado, porém consegue encontrar uma tabela que satisfaça o nome deste model, o CakePHP cria automaticamente um model com a classe GenericModel. Assim, será possível saber se o CakePHP está encontrando o model na estrutura de diretórios.

Bom, a primeira coisa que quero testar neste model, é garantir que não será possível a inserção de dados vazios. Os campos são obrigatórios e devem ser preenchidos com valores não vazios.

public function testNoPassedData() {
   $data = Array()
   $this->assertFalse($this->Product->save($data));
   $error_fields = array_keys($this->Product->validationErrors);
   // Verifica se o campo deu erro na validação
   $this->assertTrue(in_array('name', $error_fields);
   $this->assertTrue(in_array('description', $error_fields);
   $this->assertTrue(in_array('price', $error_fields);
}
public function testEmptyData() {
   $data = Array(
      'name' => '',
      'description' => '',
      'price' => ''
   )
   $this->assertFalse($this->Product->save($data));
   $error_fields = array_keys($this->Product->validationErrors);
   // Verifica se o campo deu erro na validação
   $this->assertTrue(in_array('name', $error_fields);
   $this->assertTrue(in_array('description', $error_fields);
   $this->assertTrue(in_array('price', $error_fields);
}

Bom, o nome e a descrição podem conter o que quiser, desde que contenham alguma coisa. Então, a princípio não há necessidade de mais testes. Mas e o preço do produto?

Bom, vamos pensar no preço: um preço é um valor numérico acima de 0. Então podemos por os seguintes testes:

public function testValidPriceFormat() {
   $valid_prices = Array(3, 5.04, 124.3, 12000, 0.01, 1.0000);
   $data = Array(
      'name' => 'nome válido',
      'description' => 'descrição válida',
      'price' => null // será substituido
   );
   foreach ($valid_prices as $counter => $price) {
      $data['price'] = $price;
      $data['name'] = $data['name'];
      $this->assertTrue($this->Product->save($data));
   }
}
public function testInvalidPriceFormat() {
   $invalid_prices = Array('', -14,0,'zero','1,00', 0);
   $data = Array(
      'name' => 'nome válido',
      'description' => 'descrição válida',
      'price' => null // será substituido
   );
   foreach ($invalid_prices as $price) {
      $data['price'] = $price;
      $this->assertFalse($this->Product->save($data));
      $error_fields = array_keys($this->Product->validationErrors);
      // verifica se está no array de erros
      $this->assertTrue(in_array('price', $error_fields));
   }
}

Vejam que não vale a pena misturar as coisas: quando eu testo valores válidos ou inválidos de preço, somente o preço eu modifico. O restante das informações permancem imutáveis e devem ser válidas. O que queremos testar agora é a validação do preço e não de outras partes.

Uma outra coisa importante é considerar strings e valores com vírgulas para estes casos, são potenciais problemas! Aqui vai uma dica: sempre que trabalhar com campos inteiros, verifique o comportamento do sistema com valores 0 e negativos também!

Bom, temos nossos testes. Precisamos fazer eles passarem. Para isto, vamos adicionar o array de validação ao model.

No arquivo app/tests/cases/model/product.test.php vamos por o seguinte:

public $validate = Array(
   'name' => Array(
      'required' => Array(
         'rule' => '/\S/',
         'message' => 'Deve ser especificado um nome para o produto',
         'required' => true
      )
   ),
   'description' => Array(
      'required' => Array(
         'rule' => '/\S/',
         'message' => 'Deve ser especificado uma descrição para o produto',
         'required' => true
      )
   ),
   'price' => Array(
      'valid' => Array(
         'rule' => array('comparison', '>', 0),
         'message' => 'O preço deve ser maior que zero'
      ),
      'format' => Array(
         'rule' => array('numeric'),
         'message' => 'O preço deve ser um valor numérico válido'
      ),
      'required' => Array(
         'rule' => '/\S/',
         'message' => 'Deve ser especificado um preço para o produto',
         'required' => true
      )
   )
);

Bom, agora é só rodar os testes e ver a barra verde. Simples, huh?


Fonte: http://gustavodutra.com/post/412/como-fazer-testes-unitarios-em-models-no-cakephp/

0sem comentários ainda

Enviar um comentário

Os campos são obrigatórios.

Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.