.. index::
single: Testes
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:
.. code-block:: bash
# 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``.
.. index::
single: Testes; Testes Unitários
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:
.. code-block:: bash
# 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/
.. index::
single: Testes; Testes Funcionais
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::
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 :ref:`mais sobre o método request`)
retorna um objeto :class:`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());
.. _book-testing-request-method-sidebar:
.. sidebar:: Mais sobre o método ``request()``:
A assinatura completa do método ``request()`` é::
request(
$method,
$uri,
array $parameters = array(),
array $files = array(),
array $server = array(),
$content = null,
$changeHistory = true
)
O array ``server`` são valores brutos que você espera encontrar normalmente na
variável superglobal do PHP `$_SERVER`_. Por exemplo, para setar os cabeçalhos
HTTP `Content-Type` e `Referer`, você passará o seguinte::
$client->request(
'GET',
'/demo/hello/Fabien',
array(),
array(),
array(
'CONTENT_TYPE' => 'application/json',
'HTTP_REFERER' => '/foo/bar',
)
);
.. index::
single: Testes; Assertions
.. sidebar: Afirmações Úteis
Para você começar mais rápido, aqui está uma lista com as afirmações
mais comuns e úteis::
// Afirma que tem exatamente uma tag h2 com a classe "subtitle"
$this->assertTrue($crawler->filter('h2.subtitle')->count() > 0);
// Afirma que tem 4 tags h2 na página
$this->assertEquals(4, $crawler->filter('h2')->count());
// Afirma que o cabeçalho "Content-Type" é "application/json"
$this->assertTrue($client->getResponse()->headers->contains('Content-Type', 'application/json'));
// Afirma que o conteúdo da resposta casa com a regexp.
$this->assertRegExp('/foo/', $client->getResponse()->getContent());
// Afirma que o código do status da resposta é 2xx
$this->assertTrue($client->getResponse()->isSuccessful());
// Afirma que o código do status da resposta é 404
$this->assertTrue($client->getResponse()->isNotFound());
// Afirma um especifico código 200
$this->assertEquals(200, $client->getResponse()->getStatusCode());
// Afirma que a resposta é um redirecionamento para /demo/contact
$this->assertTrue($client->getResponse()->isRedirect('/demo/contact'));
// ou simplesmente checa que a resposta redireciona para qualquer URL
$this->assertTrue($client->getResponse()->isRedirect());
.. index::
single: Testes; Client
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
:ref:`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();
Navegando
~~~~~~~~~
O Cliente suporta muitas operação que podem ser realizadas em um navegador real::
$client->back();
$client->forward();
$client->reload();
// Limpa todos os cookies e histórico
$client->restart();
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
:doc:`/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();
.. index::
single: Testes; Crawler
.. _book-testing-crawler:
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');
});
Links
~~~~~
Para selecionar links, você pode usar os métodos acima ou o conveniente atalho
``selectLink()``::
$crawler->selectLink('Click here');
Isso seleciona todos os links que contém o texto, ou imagens que o atributo ``alt``
contém o determinado texto. Como outros métodos de filtragem, esse retorna outro
objeto ``Crawler``.
Uma vez selecionado um link, você pode ter acesso a um objeto especial ``Link``,
que tem métodos especificos muito úties para links (como ``getMethod()`` e
``getUri()``). Para clicar no link, use o método do Client ``click()`` e passe
um objeto do tipo ``Link``::
$link = $crawler->selectLink('Click here')->link();
$client->click($link);
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).
.. index::
pair: Testes; Configuração
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``:
.. configuration-block::
.. code-block:: yaml
# app/config/config_test.yml
# ...
swiftmailer:
disable_delivery: true
.. code-block:: xml
.. code-block:: php
// app/config/config_test.php
// ...
$container->loadFromExtension('swiftmailer', array(
'disable_delivery' => true
));
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 :ref:`framework.test`
esteja habilitada). Isso significa que você pode sobrescrever o serviço inteiramente
se você precisar.
.. index::
pair: PHPUnit; Configuração
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:
.. code-block:: xml
../src/*/*Bundle/Tests
../src/Acme/Bundle/*Bundle/Tests
Para incluir outros diretórios no code coverage, edite também a sessção ````:
.. code-block:: xml
../src
../src/*/*Bundle/Resources
../src/*/*Bundle/Tests
../src/Acme/Bundle/*Bundle/Resources
../src/Acme/Bundle/*Bundle/Tests
Aprenda mais no Cookbook
------------------------
* :doc:`/cookbook/testing/http_authentication`
* :doc:`/cookbook/testing/insulating_clients`
* :doc:`/cookbook/testing/profiling`
.. _`DemoControllerTest`: https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
.. _`$_SERVER`: http://php.net/manual/en/reserved.variables.server.php
.. _`documentation`: http://www.phpunit.de/manual/3.5/en/