Documentação do Symfony2
Renderizada do repositório symfony-docs-pt-BR no Github

Testes

Sempre que você escrever uma nova linha de código, você também adiciona potenciais novos bugs. Para construir aplicações melhores e mais confiáveis, você deve testar seu código usando testes funcionais e unitários.

O Framework de testes PHPUnit

O Symfony2 se integra com uma biblioteca independente - chamada PHPUnit - para dar a você um rico framework de testes. Esse capitulo não vai abranger o PHPUnit propriamente dito, mas ele tem a sua excelente documentação documentation.

Note

O Symfony2 funciona com o PHPUnit 3.5.11 ou posterior, embora a versão 3.6.4 é necessária para testar o código do núcleo do Symfony.

Cada teste - quer seja teste unitário ou teste funcional - é uma classe PHP que deve residir no sub-diretório Tests/ de seus bundles. Se você seguir essa regra, você pode executar todos os testes da sua aplicação com o seguinte comando:

# espefifique o diretório de configuração na linha de comando
$ phpunit -c app/

A opção -c diz para o PHPUnit procurar no diretório app/ por um arquivo de configuração. Se você está curioso sobre as opções do PHPUnit, dê uma olhada no arquivo app/phpunit.xml.dist.

Tip

O Code coverage pode ser gerado com a opção --coverage-html.

Testes Unitários

Um teste unitário é geralmente um teste de uma classe PHP especifica. Se você quer testar o comportamento global da sua aplicação, veja a seção sobre Testes Funcionais.

Escrever testes unitários no Symfony2 não é nada diferente do que escrever um teste unitário padrão do PHPUnit. Vamos supor que, por exemplo, você tem uma classe incrivelmente simples chamada Calculator no diretório Utility/ do seu bundle:

// src/Acme/DemoBundle/Utility/Calculator.php
namespace Acme\DemoBundle\Utility;

class Calculator
{
    public function add($a, $b)
    {
        return $a + $b;
    }
}

Para testar isso, crie um arquivo chamado CalculatorTest no diretório Tests/Utility do seu bundle:

// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Acme\DemoBundle\Tests\Utility;

use Acme\DemoBundle\Utility\Calculator;

class CalculatorTest extends \PHPUnit_Framework_TestCase
{
    public function testAdd()
    {
        $calc = new Calculator();
        $result = $calc->add(30, 12);

        // assert that our calculator added the numbers correctly!
        $this->assertEquals(42, $result);
    }
}

Note

Por convenção, o sub-diretório Tests/ deve replicar o diretório do seu bundle. Então, se você estiver testando uma classe no diretório Utility/ do seu bundle, coloque o teste no diretório Tests/Utility/.

Assim como na rua aplicação verdadeira - o autoloading é automaticamente habilitado via o arquivo bootstrap.php.cache (como configurado por padrão no arquivo phpunit.xml.dist).

Executar os testes para um determinado arquivo ou diretório também é muito fácil:

# executa todos os testes no diretório Utility
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/

# executa os testes para a classe Article
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php

# executa todos os testes para todo o Bundle
$ phpunit -c app src/Acme/DemoBundle/

Testes Funcionais

Testes funcionais verificam a integração das diferentes camadas de uma aplicação (do roteamento as views). Eles não são diferentes dos testes unitários levando em consideração o PHPUnit, mas eles tem um fluxo bem especifico:

  • Fazer uma requisição;
  • Testar a resposta;
  • Clicar em um link ou submeter um formulário;
  • Testar a resposta;
  • Repetir a operação.

Seu Primeiro Teste Funcional

Testes funcionais são arquivos PHP simples que estão tipicamente no diretório Tests/Controller do seu bundle. Se você quer testar as páginas controladas pela sua classe DemoController, inicie criando um novo arquivo DemoControllerTest.php que extende a classe especial WebTestCase.

Por exemplo, o Symfony2 Standard Edition fornece um teste funcional simples para o DemoController (DemoControllerTest) descrito assim:

// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DemoControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/demo/hello/Fabien');

        $this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);
    }
}

Tip

Para executar seus testes funcionais, a classe WebTestCase class inicializa o kernel da sua aplicação. Na maioria dos casos, isso acontece automaticamente. Entretando, se o seu kernel está em um diretório diferente do padrão, você vai precisar modificar seu arquivo phpunit.xml.dist para alterar a variável de ambiente KERNEL_DIR para o diretório do seu kernel:

<phpunit
    <!-- ... -->
    <php>
        <server name="KERNEL_DIR" value="/path/to/your/app/" />
    </php>
    <!-- ... -->
</phpunit>

O método createClient() retorna um cliente, que é como um navegador que você vai usar para navegar no seu site:

$crawler = $client->request('GET', '/demo/hello/Fabien');

O método request() (veja mais sobre o método request) retorna um objeto Symfony\Component\DomCrawler\Crawler que pode ser usado para selecionar um elemento na Response, clicar em links, e submeter formulários.

Tip

O Crawler só funciona se a resposta é um documento XML ou HTML. Para pegar a resposta bruta, use $client->getResponse()->getContent().

Clique em um link primeiramente selecionando-o com o Crawler usando uma expressão XPath ou um seletor CSS, então use o Client para clicar nele. Por exemplo, o segunte código acha todos os links com o texto Greet, então seleciona o segundo, e então clica nele:

$link = $crawler->filter('a:contains("Greet")')->eq(1)->link();

$crawler = $client->click($link);

Submeter um formulário é muito parecido, selecione um botão do formulário, opcionalmente sobrescreva alguns valores do formulário, então submeta-o:

$form = $crawler->selectButton('submit')->form();

// pega alguns valores
$form['name'] = 'Lucas';
$form['form_name[subject]'] = 'Hey there!';

// submete o formulário
$crawler = $client->submit($form);

Tip

O formulário também pode manipular uploads e tem métodos para preencher diferentes tipos de campos (ex. select() e tick()). Para mais detalhers, veja a seção `Forms`_ abaixo.

Agora que você pode facilmente navegar pela sua aplicação, use as afirmações para testar que ela realmente faz o que você espera que ela faça. Use o Crawler para fazer afirmações no DOM:

// Afirma que a resposta casa com um seletor informado
$this->assertTrue($crawler->filter('h1')->count() > 0);

Ou, teste contra o conteúdo do Response diretamente se você só quer afirmar que o conteudo contém algum texto ou se o Response não é um documento XML/HTML:

$this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent());

Trabalhando com o Teste Client

O teste Client simula um cliente HTTP como um navegador e faz requisições na sua aplicação Symfony2:

$crawler = $client->request('GET', '/hello/Fabien');

O método request() pega o método HTTP e a URL como argumentos e retorna uma instancia de Crawler.

Utilize o Crawler para encontrar elementos DOM no Response. Esses elementos podem então ser usados para clicar em links e submeter formulários:

$link = $crawler->selectLink('Go elsewhere...')->link();
$crawler = $client->click($link);

$form = $crawler->selectButton('validate')->form();
$crawler = $client->submit($form, array('name' => 'Fabien'));

Os métodos click() e submit() retornam um objeto Crawler. Esses métodos são a melhor maneira de navegar na sua aplicação por tomarem conta de várias coisas para você, como detectar o método HTTP de um formulário e dar para você uma ótima API para upload de arquivos.

Tip

Você vai aprende rmais sobre os objetos Link e Form na seção Crawler abaixo.

O método request pode também ser usado para simular submissões de formulários diretamente ou fazer requisições mais complexas:

// Submeter diretamente um formuário (mas utilizando o Crawler é mais fácil!)
$client->request('POST', '/submit', array('name' => 'Fabien'));

// Submissão de formulário com um upload de arquivo
use Symfony\Component\HttpFoundation\File\UploadedFile;

$photo = new UploadedFile(
    '/path/to/photo.jpg',
    'photo.jpg',
    'image/jpeg',
    123
);
// ou
$photo = array(
    'tmp_name' => '/path/to/photo.jpg',
    'name' => 'photo.jpg',
    'type' => 'image/jpeg',
    'size' => 123,
    'error' => UPLOAD_ERR_OK
);
$client->request(
    'POST',
    '/submit',
    array('name' => 'Fabien'),
    array('photo' => $photo)
);

// Executa uma requisição de DELETE e passa os cabeçalhos HTTP
$client->request(
    'DELETE',
    '/post/12',
    array(),
    array(),
    array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word')
);

Por último mas não menos importante, você pode forçar cara requisição para ser executada em seu pŕoprio processo PHP para evitar qualquer efeito colateral quando estiver trabalhando com vários clientes no mesmo script:

$client->insulate();

Acessando Objetos Internos

Se você usa o cliente para testar sua aplicação, você pode querer acessar os objetos internos do cliente:

$history   = $client->getHistory();
$cookieJar = $client->getCookieJar();

Você também pode pegar os objetos relacionados a requisição mais recente:

$request  = $client->getRequest();
$response = $client->getResponse();
$crawler  = $client->getCrawler();

Se as suas requisição não são isoladas, você pode também acessar o Container e o Kernel:

$container = $client->getContainer();
$kernel    = $client->getKernel();

Acessando o Container

É altamente recomendado que um teste funcional teste somente o Response. Mas em circunstancias extremamente raras, você pode querer acessar algum objeto interno para escrever afirmações. Nestes casos, você pode acessar o dependency injection container:

$container = $client->getContainer();

Esteja ciente que isso não funciona se você isolar o cliente ou se você usar uma camada HTTP. Para ver a lista de serviços disponíves na sua aplicação, utilize a task container:debug.

Tip

Se a informação que você precisa verificar está disponível no profiler, uso-o então

Acessando dados do Profiler

En cada requisição, o profiler do Symfony coleta e guarda uma grande quantidade de dados sobre a manipulação interna de cada request. Por exemplo, o profiler pode ser usado para verificar se uma determinada página executa menos consultas no banco quando estiver carregando.

Para acessar o Profiler da última requisição, faço o seguinte:

$profile = $client->getProfile();

Para detalhes especificos de como usar o profiler dentro de um teste, seja o artigo /cookbook/testing/profiling do cookbook.

Redirecionamento

Quando uma requisição retornar uma redirecionamento como resposta, o cliente automaticamente segue o redirecionamento. Se você quer examinar o Response antes do redirecionamento use o método followRedirects():

$client->followRedirects(false);

Quando o cliente não segue os redirecionamentos, você pode forçar o redirecionamento com o método followRedirect():

$crawler = $client->followRedirect();

O Crawler

Uma instancia do Crawler é retornada cada vez que você faz uma requisição com o Client. Ele permite que você examinar documentos HTML, selecionar nós, encontrar links e formulários.

Examinando

Como o jQuery, o Crawler tem metodos para examinar o DOM de um documento HTML/XML. Por exemplo, isso encontra todos os elementos input[type=submit], seleciona o último da página, e então seleciona o elemento imediatamente acima dele:

$newCrawler = $crawler->filter('input[type=submit]')
    ->last()
    ->parents()
    ->first()
;

Muitos outros métodos também estão disponíveis:

Metodos Descrição
filter('h1.title') Nós que casam com o seletor CSS
filterXpath('h1') Nós que casam com a expressão XPath
eq(1) Nó para a posição especifica
first() Primeiro nó
last() Último nó
siblings() Irmãos
nextAll() Todos os irmãos posteriores
previousAll() Todos os irmãos anteriores
parents() Nós de um nivel superior
children() Filhos
reduce($lambda) Nós que a função não retorne false

Como cada um desses métodos retorna uma nova instância de Crawler, você pode restringir os nós selecionados encadeando a chamada de métodos:

$crawler
    ->filter('h1')
    ->reduce(function ($node, $i)
    {
        if (!$node->getAttribute('class')) {
            return false;
        }
    })
    ->first();

Tip

Utilize a função count() para pegar o número de nós armazenados no Crawler: count($crawler)

Extraindo Informações

O Crawler pode extrair informações dos nós:

// Retornar o valor do atributo para o primeiro nó
$crawler->attr('class');

// Retorna o valor do nó para o primeiro nó
$crawler->text();

// Extrai um array de atributos para todos os nós (_text retorna o valor do nó)
// retorna um array para cara elemento no crawler, cara um com o valor e href
$info = $crawler->extract(array('_text', 'href'));

// Executa a lambda para cada nó e retorna um array de resultados
$data = $crawler->each(function ($node, $i)
{
    return $node->attr('href');
});

Formulários

Assim como nos links, você seleciona o form com o método selectButton():

$buttonCrawlerNode = $crawler->selectButton('submit');

Note

Note que selecionamos os botões do formulário e não os forms, pois o form pode ter vários botões; se você usar a API para examinar, tenha em mente que você deve procurar por um botão.

O método selectButton() pode selecionar tags button e submit tags input. Ele usa diversas partes diferentes do botão para encontrá-los:

  • O atributo value;
  • O atributo id ou alt para imagens;
  • O valor do atributo id ou name para tags button.

Uma vez que você tenha o Crawler representanto um botão, chame o método form() para pegar a instancia de Form do form que envolve o nó do botão:

$form = $buttonCrawlerNode->form();

Quando chamar o método form(), você pode também passar uma array com valores dos campos para sobreescrever os valores padrões:

$form = $buttonCrawlerNode->form(array(
    'name'              => 'Fabien',
    'my_form[subject]'  => 'Symfony rocks!',
));

E se você quiser simular algum método HTTP especifico para o form, passe-o como um segundo argumento:

$form = $crawler->form(array(), 'DELETE');

O Client pode submeter instancias de Form:

$client->submit($form);

Os valores dos campos também posem ser passsados como um segundo argumento do método submit():

$client->submit($form, array(
    'name'              => 'Fabien',
    'my_form[subject]'  => 'Symfony rocks!',
));

Para situações mais complexas, use a instancia de Form como um array para setar o valor de cada campo individualmente:

// Muda o valor do campo
$form['name'] = 'Fabien';
$form['my_form[subject]'] = 'Symfony rocks!';

Também existe uma API para manipular os valores do campo de acordo com o seu tipo:

// Seleciona um option ou um radio
$form['country']->select('France');

// Marca um checkbox
$form['like_symfony']->tick();

// Faz o upload de um arquivo
$form['photo']->upload('/path/to/lucas.jpg');

Tip

Você pode pegar os valores que serão submetidos chamando o método getValues() no objeto Form. Os arquivos do upload estão disponiveis em um array separado retornado por getFiles(). Os métodos getPhpValues() e getPhpFiles() também retorna valores submetidos, mas no formato PHP (ele converte as chaves para a notação de colchetes - ex. my_form[subject] - para PHP arrays).

Configuração de Testes

O Client usado pelos testes funcionais cria um Kernel que roda em um ambiente especial chamado test. Uma vez que o Symfony carrega o app/config/config_test.yml no ambiente test, você pode ajustar qualquer configuração de sua aplicação especificamente para testes.

Por exemplo, por padrão, o swiftmailer é configurado para não enviar realmente os e-mails no ambiente test. Você pode ver isso na opção de configuração swiftmailer:

Você também pode usar um ambiente completamente diferente, ou sobrescrever o modo de debug (true) passando cada um como uma opção para o método createClient():

$client = static::createClient(array(
    'environment' => 'my_test_env',
    'debug'       => false,
));

Se a sua aplicação se comporta de acordo com alguns cabeçalhos HTTP, passe eles como o segundo argumento de createClient():

$client = static::createClient(array(), array(
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));

Você também pode sobrescrever cabeçalhos HTTP numa base por requisições:

$client->request('GET', '/', array(), array(), array(
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));

Tip

O cliente de testes está disponível como um serviço no container no ambiente teste (ou em qualquer lugar que a opção framework.test esteja habilitada). Isso significa que você pode sobrescrever o serviço inteiramente se você precisar.

Configuração do PHPUnit

Cada aplicação tem a sua própria configuração do PHPUnit, armazenada no arquivo phpunit.xml.dist. Você pode editar o arquivo para mudar os valores padrões ou criar um arquivo phpunit.xml` para ajustar a configuração para sua máquina local.

Tip

Armazene o arquivo phpunit.xml.dist no seu repositório de códigos e ignore o arquivo phpunit.xml.

Por padrão, somente os testes armazenados nos bundles “standard” são rodados pelo comando phpunit (standard sendo os testes nos diretórios src/*/Bundle/Tests ou src/*/Bundle/*Bundle/Tests) Mas você pode facilmente adicionar mais diretórios. Por exemplo, a seguinte configuração adiciona os testes de um bundle de terceiros instalado:

<!-- hello/phpunit.xml.dist -->
<testsuites>
    <testsuite name="Project Test Suite">
        <directory>../src/*/*Bundle/Tests</directory>
        <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
    </testsuite>
</testsuites>

Para incluir outros diretórios no code coverage, edite também a sessção <filter>:

<filter>
    <whitelist>
        <directory>../src</directory>
        <exclude>
            <directory>../src/*/*Bundle/Resources</directory>
            <directory>../src/*/*Bundle/Tests</directory>
            <directory>../src/Acme/Bundle/*Bundle/Resources</directory>
            <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
        </exclude>
    </whitelist>
</filter>

Aprenda mais no Cookbook

  • /cookbook/testing/http_authentication
  • /cookbook/testing/insulating_clients
  • /cookbook/testing/profiling