Testando Banco de Dados com phpunit

Introdução

Em artigo anterior vimos como dar o pontapé inicial no processo de testes para PHP usando o phpunit. Neste artigo vamos aprender como testar códigos que interagem com banco de dados afim de garantir que suas operações CRUD estão sendo realizadas corretamente. Para isso vamos usar a extensão de Database (banco de dados) do phpunit chamada DbUnit.

Sabemos que a maioria dos testes unitários devem ser simples e que não interajam com banco de dados. Porém algumas aplicações são muito acopladas ao banco, e possuem lógica de negócios que realizam uma mistura de SELECT, INSERT, UPDATE e DELETE. Portanto, em alguns casos se torna necessário testes com interação com banco de dados.

A extensão DbUnit simplifica o setup do banco para o propósito de testes permitindo você verificar o conteúdo do banco após realizar uma série de operações.

Criando o ambiente para os testes

Para exemplificar o uso da extensão DbUnit do phpunit vamos criar a seguinte estrutura de banco de dados para podermos testar:

phpunit-db

Neste exemplo estamos usando o MySQL. DbUnit também tem suporte para PostgreSQL, Oracle e SQLite.

O dump da estrutura deste banco pode ser obtida aqui: http://www.douglaspasqua.com/phpunit/music.sql

Instalando phpunit + DbUnit

Primeiramente faça a instalação do composer em sua aplicação:

curl -sS https://getcomposer.org/installer | php

Crie um arquivo composer.json com o seguinte conteúdo:

{
    "require-dev": {
            "phpunit/phpunit": "4.5.*",
            "phpunit/dbunit": ">=1.2"
    }
}

Execute a instalação do phpunit e a extensão DbUnit através de composer:

php composer.phar install

Criando a classe de teste

Normalmente, ao criar um teste com phpunit você deve extender da classe PHPUnit_Framework_TestCase. Porém para testar classes phpunit com a extensão de banco de dados é um pouco mais complicado, é necessário extender da classe abstrata PHPUnit_Extensions_Database_TestCase e implementar os métodos getConnection() e getDataSet():

<?php
 
class MyAlbumsTest extends PHPUnit_Extensions_Database_TestCase
{
    public function getConnection()
    {
        // implementacao getConnection
    }
 
    public function getDataSet()
    {
        // implementacao getDataSet()
    }

    // tests
}

Implementando getConnection()

A função desse método é dizer ao phpunit como ele deve fazer para se conectar com o banco de dados de teste para realizar o cleanup e fixture. A conexão deve ser construída usando PDO. Lembrando que isso não significa que sua aplicação deva ser baseada em PDO (Você pode usar o seu próprio DAO, Doctrine, etc..). É somente para o phpunit executar o cleanup e fixture necessário para os testes da sua aplicação.

private $conn = null;

public function getConnection()
{
    if(!$this->conn) {
            $db = new PDO(
                "mysql:host=127.0.0.1;dbname=music",
                "music", "music123");

            $this->conn = $this->createDefaultDBConnection($db, "music");
    }

    return $this->conn;
}

Implementando getDataSet()

Esta função informa quais os dados e como eles devem ser carregados para dentro do banco de dados de teste (fixture) – Fixture é uma solução de automação na preparação do banco de dados para que você tenha um estado inicial afim de realizar os testes – Existem 3 formas de fazer isso através do DbUnit: Flat XML DataSet, XML DataSet e MySQL XML DataSet. Em nosso exemplo vamos usar a opção XML DataSet onde criarmos a representação dos dados em formato XML. Para saber mais sobre as outras opções, consulte a documentação oficial.

Crie o arquivo music.xml dentro do diretório tests/ da sua aplicação com o seguinte conteúdo: (Estou levando em consideração que as classes de testes estão sendo criadas dentro do diretório tests/ na raiz de sua aplicação)

<dataset>
	<table name="albums">
		<column>id</column>
		<column>artista</column>
		<column>titulo</column>
		<row>
			<value>1</value>
			<value>Dream Theater</value>
			<value>Images and Words</value>
		</row>
	</table>
	<table name="tracks">
		<column>id</column>
		<column>numero</column>
		<column>nome</column>
		<column>albums_id</column>
		<row>
			<value>1</value>
			<value>1</value>
			<value>Pull Me Under</value>
			<value>1</value>
		</row>
		<row>
			<value>2</value>
			<value>2</value>
			<value>Another Day</value>
			<value>1</value>
		</row>
		<row>
			<value>3</value>
			<value>3</value>
			<value>Take the Time</value>
			<value>1</value>
		</row>
	</table>
</dataset>

Esse modelo de carregar os dados para o banco de testes pode ser facilmente entendido e adaptado para a sua realidade.

Finalmente implemente o método getDataSet() informando o caminho para o arquivo music.xml, criado anteriormente, usando o método createXMLDataSet():

public function getDataSet()
{
        return $this->createXMLDataSet("tests/music.xml");
}

Dica: É possível usar o método createMySQLXMLDataSet que aceita o formato de xml gerado pelo comando mysqldump. Mais exemplos na documentação oficial.

Criando Testes

Com os métodos getConnection() e getDataSet() implementados, podemos criar os testes para as funções de nossa aplicação que interagem com o banco de dados. Para cada teste, o phpunit irá fazer o cleanup e fixutre no banco de dados. Segue teste de exemplo:

public function testLendo()
{
    $conn = $this->getConnection()->getConnection();

    // realiza operacoes com banco de dados
    // .....

    // verificando o status do banco.
    $query = $conn->query('SELECT * FROM albums');
    $results = $query->fetchAll(PDO::FETCH_ASSOC);

    $this->assertCount(1, $results);
    $this->assertEquals('Dream Theater', $results[0]['artista']);
    $this->assertEquals('Images and Words', $results[0]['titulo']);

    // lendo tracks
    $query = $conn->query('SELECT count(*) as total_tracks FROM tracks');
    $results = $query->fetchAll(PDO::FETCH_ASSOC);
    $this->assertEquals(3, $results[0]['total_tracks']);
}

Colocando tudo junto

O script final para este artigo:

<?php

class MyAlbumsTest extends PHPUnit_Extensions_Database_TestCase
{
    private $conn = null;

    public function getConnection()
    {
        if(!$this->conn) {
            $db = new PDO(
                "mysql:host=127.0.0.1;dbname=music",
                "music", "music123");

            $this->conn = $this->createDefaultDBConnection($db, "music");
        }

        return $this->conn;
    }

    public function getDataSet()
    {
        return $this->createXMLDataSet("tests/music.xml");
    }

    public function testLendo()
    { 
        $conn = $this->getConnection()->getConnection();
  
        // lendo albums
        $query = $conn->query('SELECT * FROM albums');
        $results = $query->fetchAll(PDO::FETCH_ASSOC);
        
        $this->assertCount(1, $results);
        $this->assertEquals('Dream Theater', $results[0]['artista']);
        $this->assertEquals('Images and Words', $results[0]['titulo']);

        // lendo tracks
        $query = $conn->query('SELECT count(*) as total_tracks FROM tracks');
        $results = $query->fetchAll(PDO::FETCH_ASSOC);
        $this->assertEquals(3, $results[0]['total_tracks']);
    }
}   

Executando os testes:

$ ./vendor/bin/phpunit tests/

Troubleshooting

Gostaria de deixar uma dica, caso precise usar a função setUp() em sua classe,
para realizar algum tipo de inicialização para seus testes, lembre-se de chamar o método pai, pois senão o DbUnit não irá realizar o cleanup e fixture corretamente.

public function setUp()
{
    // inicializacoes
    // ....

    parent::setUp();
}

Referências

https://phpunit.de/manual/current/en/database.html
http://someguyjeremy.com/2013/01/database-testing-with-phpunit.html
http://www.sitepoint.com/bulletproofing-database-interactions/

Deixe um comentário

Follow

Get every new post on this blog delivered to your Inbox.

Join other followers: