log

taiar

Testing Codeigniter Applications With PHPUnit

Motivation

Codeigniter had its glory days but becomes useless with time. On the other hand, there are some legacy systems it would be really nice if I could test'em (avoiding errors in production with upgrades and all). Codeigniter has its own unit testing library but I prefer PHPUnit.

Setup

Using Codeigniter version 2.2.0, Composer, PHPUnit 4.1.* e PHP 5.4.4. I’m considering you’ve installed Composer.

Steps

With the unpacked and working Codeigniter, create a new composer.json file with the following content:

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

Install PHPUnit on the project with the command:

composer.phar install

It’ll create a vendor folder with all the necessary packages for PHPUnit, including its binary under /vendor/bin/. Now if you run:

./vendor/bin/phpunit

you’ll see that PHPUnit works and shows its default help message.

Here we’ll create the PHPUnit config file for the project. Under the main folder, create a new phpunit.xml.dist file containing:

With PHPUnit you can write a boostrap file to set the test execution environment. For that, we’ll have almost the very same Codeigniter’s index.php saved in /tests/Bootstrap.php:

PHPUnit’s result comes os the terminal with a very pragmatic output. With that in mind, we’ll have to suppress the Codeigniter’s code output so when tests run we can understand what PHPUnit tests will tell us. To achieve it, we’ll use Codeigniter hooks.

First, change the /application/config/config.php file turning hooks on for the application:

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

Later in /application/config/hooks.php refer to the hook we’ll use:

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

Lastly we’ll create the /application/hooks/DisplayHook.php hook:

PHPUnit has some incompatibilities with Codeigniter that we will have to handle. Change the framework’s core libraries is a very bad practice, so we’ll need to overwrite some Codeigniter libraries by copying to the /application/core/ folder with the _MY__ prefix on the file name. The first class will be the Utf8.php one.

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

Change the class name and the $CFG variable as follows:

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

The same thing must be done with the Output.php class:

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

Change the class name, the $CFG and $BM variables as follows:

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

Now you’ll be able to run PHPUnit inside your project without errors:

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!

So let’s write a test using Codeigniter! As stated before, PHPUnit will run tests from /tests/ folder. So we create the /tests/CITest.php file.

<?php

  class CITest extends PHPUnit_Framework_TestCase
  {
    private $CI;

    public function setUp()
    {
      // Load CI instance normally
      $this->CI = &get_instance();
    }

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

Run for a successful test:

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)

In this article I’ll not look into tests. Only the configuration of this environment given the initial motivation. Improvements on the tests execution would be done by mapping the Codeigniter error output to exceptions instead of the default html output and other issues. I don’t try any of this suggestions yet (it’s a little hard to be motivated when dealing with legacies).

References

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

Gerando Subconjuntos

Implementação minha de um algoritmo gerar subconjuntos. O artifício aqui foi aplicar uma máscara de números binários sequencial sobre um vetor contendo o conjunto. Incrementando a máscara de 1 em 1 até que ela ocupe todo o vetor, podemos gerar todos os subconjuntos dos elementos desse vetor.

Algoritmo De Regressão Linear Em Fortran

Veja abaixo:

E No último Dia Do Ano...

… 31 de dezembro. Último dia do ano.

Eu gosto de balanços de final de ano! É divertido fazer uma retrospectiva do que aconteceu e do que não… E eu juro que eu farei um dia desses. Uma grande. Mas vamos às contas:

Sem contar hoje, faltam:

  • 7 dias pro vestibular

  • 12 dias pra eu entrar de férias

  • 13 dias pra eu começar a trabalhar

  • 7 dias pra eu aprender um monte de coisa, fazer exercício e escrever pra caramba

  • 7 dias pra passar nessa porra…

Será que vai dar? 7 É o número da sorte!

Às vezes penso que sim, às vezes que não… Mas só vai dar pra saber depois…

Faltam 32 dias (no máximo) pra eu saber……… Shit…

Felicidades pra todos que merecem em 2007! Que seja um ano fantástico (pra quem merece), e para mim também!

Go On!