Testando aplicações Codeigniter com PHPUnit

Motivação

O Codeigniter já teve os seus dias de glória mas ultimamente tem sido cada vez menos utilizado. Ele evoluiu pouco nos últimos tempos em vista de grandes novidades que surgiram no ecossistema de PHP, foi ficando pra trás sem essas grandes novidades. Apesar disso, muitas aplicações foram construídas utilizando Codeigniter (inclusive por mim) e muitas dessas ainda estão em produção. Para ajudar algo em produção a continuar funcionando com uma base legada, nada melhor do que testes automatizados rodando à cada atualização no projeto ou a cada upgrade de versão (sim, apesar de raros eles ainda existem). Codeigniter tem o próprio engine de testes mas prefiro o PHPUnit.

Setup

Utilizei aqui Codeigniter versão 2.2.0, Composer, PHPUnit 4.1.* e PHP 5.4.4. Considero que você tenha o Composer instalado.

Passos

Com o Codeigniter descompactado e em funcionamento, crie na raiz do projeto um arquivo composer.json com o seguinte conteúdo:

{
  "require-dev": {
    "phpunit/phpunit": "4.1.*"
  }
}

Em seguida instale o PHPUnit na pasta do projeto com o comando:

composer.phar install

Será criada uma pasta chamada vendor aonde o composer irá colocar todos os pacotes necessários para executar o PHPUnit, inclusive seu binário dentro de /vendor/bin/. Se você rodar agora:

./vendor/bin/phpunit

poderá ver que o PHPUnit funciona e exibe o seu help padrão.

Aqui vamos criar o arquivo de configuração da execução do PHPUnit para o projeto. Então, dentro da pasta raiz deve ser criado um arquivo chamado phpunit.xml.dist com o seguinte conteúdo:

São diversas diretivas para o funcionamento do PHPUnit que podem ser descritas no site do projeto. Se você estiver usando Windows (com command, powershell ou cygwin) eu recomendo que você utilize

colors="false"

para ter menos sujeira no seu ouput de execução de testes.

O PHPUnit deixa você usar um arquivo de bootstrap para configurar o ambiente de execução antes dos testes. Para isso, vamos usar praticamente o mesmo conteúdo do arquivo index.php padrão do Codeigniter salvo num arquivo chamado Boostrap.php dentro da pasta /tests/.

O PHPUnit dá o resultado dos testes através de um output muito pragmático na saída padrão do terminal. Por isso, precisamos suprimir a saída de códigos do Codeigniter quando estivermos executando os testes para poder entender o que o PHPUnit irá nos dizer. Para isso, vamos utilizar hooks do sistema.

Primeiro altere o arquivo /application/config/config.php habilitando a execução de hooks na aplicação:

<?php
  // ...
  $config['enable_hooks'] = TRUE;
  // ...

Depois em /application/config/hooks.php referencie a execução do hook que iremos utilizar:

<?php
  // ...
  $hook['display_override'] = array(
    'class' => 'DisplayHook',
    'function' => 'captureOutput',
    'filename' => 'DisplayHook.php',
    'filepath' => 'hooks'
  );
  // ...

E finalmente vamos criar o hook /application/hooks/DisplayHook.php com o conteúdo:

O PHPUnit apresenta algumas incompatibilidades com o Codeigniter que precisaremos sanar. Alterar os arquivos do core do framework são uma má prática e, por isso, iremos sobrescrever algumas bibliotecas padrões do Codeigniter copiando as bibliotecas do core para a pasta /application/core/ com o prefixo _MY__ no nome do arquivo. A primeira será a classe Utf8.php.

cp /system/core/Utf8.php /application/core/MY_Utf8.php

Altere o nome da classe e a variável $CFG da seguinte forma:

<?php
  // ...
  class MY_Utf8 {
  // ...
    function __construct()
    {
      // ...
      // global $CFG;
      $CFG =& load_class('Config', 'core');
      // ...

O mesmo deve ser feito com a classe Output.php:

cp /system/core/Output.php /application/core/MY_Output.php

Altere o nome da classe, a variável $CFG e a $BM da seguinte forma:

<?php
  // ...
  class MY_Output {
    // ...
    function _display($output = '')
    {
      // ...
      // global $BM, $CFG;
      $CFG =& load_class('Config', 'core');
      $BM =& load_class('Benchmark', 'core');
      // ...

Tudo isso feito, você será capaz de executar o PHPUnit dentro do seu projeto sem erros:

taiar@guestxor:~/dev/ci$ ./vendor/bin/phpunit
PHPUnit 4.1.3 by Sebastian Bergmann.

Configuration read from /home/taiar/dev/ci/phpunit.xml.dist



Time: 100 ms, Memory: 2.25Mb

No tests executed!

Vamos então escrever um teste de exemplo utilizando o Codeigniter. Como configurado, o PHPUnit executará testes dentro de /tests/ por isso criamos /tests/CITest.php.

<?php

  class CITest extends PHPUnit_Framework_TestCase
  {
    private $CI;

    public function setUp()
    {
      // Carrega a instância do CI normalmente
      $this->CI = &get_instance();
    }

    public function testGetPost()
    {
      $_SERVER['REQUEST_METHOD'] = 'GET';
      $_GET['foo'] = 'bar';
      $this->assertEquals('bar', $this->CI->input->get_post('foo'));
    }
  }

Execute para obter um teste de sucesso:

taiar@guestxor:~/dev/ci$ ./vendor/bin/phpunit
PHPUnit 4.1.3 by Sebastian Bergmann.

Configuration read from /home/taiar/dev/ci/phpunit.xml.dist

.

Time: 130 ms, Memory: 3.50Mb

OK (1 test, 1 assertion)

Neste artigo não abordarei testes. Apenas a configuração desse ambiente dadas a motivações iniciais. Melhorias na execução dos testes poderiam ser feitas mapeando o fluxo de erros gerados pelo Codeigniter para gerar excessões nos testes ao invés de obter um output em html e outras coisas do gênero. Não tentei qualquer dessas sugestões (difícil motivação ao lidar com legados).

Referências

comments powered by Disqus