Testing
O CakePHP vem com suporte interno para testes e integração para o PHPUnit. Em adição aos recursos oferecidos pelo PHPUnit, o CakePHP
oferece alguns recursos adicionais para fazer testes mais facilmente. Esta seção
abordará a instalação do PHPUnit, começando com testes unitários e como você
pode usar as extensões que o CakePHP oferece.
Instalando o PHPUnit
O CakePHP usa o PHPUnit como framework de teste básico. O PHPUnit é um padrão
para testes unitários em PHP. Ele oferece um profundo e poderoso conjunto de
recursos para você ter certeza que o seu código faz o que você acha que ele faz.
O PHPUnit pode ser instalado usando o PHAR package ou Composer.
Usando o arquivo PHAR
Depois de ter baixado o arquivo phpunit.phar , você pode usar ele para
executar seus testes:
Dica
Como conveniência você pode deixar phpunit.phar disponivel globalmente em sistemas
Unix ou Linux com os comandos:
chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit
phpunit --version
Por favor, consulte a documentação do PHPUnit para instruções sobre como
instalar globalmente o PHPUnit PHAR em sistemas Windows.
Configuração do banco de dados de teste
Lembre-se de ter o debug habilitado em seu arquivo config/app.php antes de
executar qualquer teste. Antes de executar quaisquer testes você deve adicionar
um datasourse test
para o arquivo config/app.php. Esta configuração é
usada pelo CakePHP para fixar tabelas e dados:
'Datasources' => [
'test' => [
'datasource' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'dbhost',
'username' => 'dblogin',
'password' => 'dbpassword',
'database' => 'test_database'
],
],
Nota
É uma boa idéia tornar o banco de dados de teste e o banco de dados
real diferentes. Isso evitará erros embaraçosos mais tarde.
Verificando a Configuração de Teste
Depois de instalar o PHPUnit e definir a configuração da fonte de dados test
,
você pode se certificar de que está pronto para escrever e executar seus próprios
testes executando os testes do aplicativo:
# Para phpunit.phar
$ php phpunit.phar
# Para phpunit instalado com Composer
$ vendor/bin/phpunit
O exemplo acima deve executar todos os testes que você possui ou informar que nenhum
teste foi executado. Para executar um teste específico, você pode fornecer o caminho
para o teste como um parâmetro para o PHPUnit. Por exemplo, se você tiver um caso de
teste para a classe ArticlesTable, poderá executá-lo com
$ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest
Você deve ver uma barra verde com algumas informações adicionais sobre os testes
executados e o número passado.
Nota
Se você estiver em um sistema Windows, provavelmente não verá cores.
Convenções de Casos de Teste
Como a maioria das coisas no CakePHP, os casos de teste têm algumas convenções. No que diz
respeito aos testes:
Os arquivos PHP que contêm testes devem estar nos seus diretórios tests/TestCase/[Type]
.
Os nomes desses arquivos devem terminar em Test.php em vez de apenas em .php.
As classes que contêm testes devem se estender das classes Cake\TestSuite\TestCase
,
Cake\TestSuite\IntegrationTestCase
ou \PHPUnit\Framework\TestCase
.
Como outros nomes de classe, os nomes de classe do caso de teste devem corresponder
ao nome do arquivo. RouterTest.php deve conter classe RouterTest extends TestCase
.
O nome de qualquer método que contenha um teste (ou seja, que contenha uma asserção) deve
começar com test
, como em testPublished()
. Você também pode usar a anotação
@test
para marcar métodos como métodos de teste.
Novo na versão 3.4.1: Suporte para PHPUnit 6 foi adicionado. Se você estiver usando uma versão do PHPUnit menor que
5.7.0, suas classes de teste devem estender as classes CakePHP ou PHPUnit_Framework_TestCase
.
Criando seu Primeiro Caso de Teste
No exemplo a seguir, criaremos um caso de teste para um método auxiliar muito simples. O auxiliar
que vamos testar estará formatando a barra de progresso HTML. Nosso ajudante se parece com:
namespace App\View\Helper;
use Cake\View\Helper;
class ProgressHelper extends Helper
{
public function bar($value)
{
$width = round($value / 100, 2) * 100;
return sprintf(
'<div class="progress-container">
<div class="progress-bar" style="width: %s%%"></div>
</div>', $width);
}
}
Este é um exemplo muito simples, mas será útil mostrar como você pode criar um caso
de teste simples. Após criar e salvar nosso auxiliar, criaremos o arquivo de caso de
teste em tests/TestCase/View/Helper/ProgressHelperTest.php. Nesse arquivo
começaremos com o seguinte:
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\ProgressHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
class ProgressHelperTest extends TestCase
{
public function setUp()
{
}
public function testBar()
{
}
}
Vamos preparar esse esqueleto em um minuto. Adicionamos dois métodos para começar. Primeiro é setUp()
.
Este método é chamado antes de cada método test em uma classe de caso de teste. Os métodos de instalação
devem inicializar os objetos necessários para o teste e fazer qualquer configuração necessária. No nosso método
de configuração, adicionaremos o seguinte:
public function setUp()
{
parent::setUp();
$View = new View();
$this->Progress = new ProgressHelper($View);
}
A chamada do método pai é importante nos casos de teste, pois TestCase::setUp()
faz várias coisas, como fazer backup dos valores em Core\Configure
e,
armazenar os caminhos em Core\App
.
Em seguida, preencheremos o método de teste. Usaremos algumas asserções para garantir que
nosso código crie a saída que esperamos:
public function testBar()
{
$result = $this->Progress->bar(90);
$this->assertContains('width: 90%', $result);
$this->assertContains('progress-bar', $result);
$result = $this->Progress->bar(33.3333333);
$this->assertContains('width: 33%', $result);
}
O teste acima é simples, mas mostra o benefício potencial do uso de casos de teste.
Usamos assertContains()
para garantir que nosso assistente retorne uma string
que contenha o conteúdo que esperamos. Se o resultado não contiver o conteúdo esperado,
o teste falhará e saberemos que nosso código está incorreto.
Usando casos de teste, você pode descrever o relacionamento entre um conjunto de entradas
conhecidas e sua saída esperada. Isso ajuda você a ter mais confiança no código que está
escrevendo, pois pode garantir que o código que você escreveu atenda às expectativas e
afirmações feitas pelos seus testes. Além disso, como os testes são de código, eles são
fáceis de executar novamente sempre que você faz uma alteração. Isso ajuda a impedir a
criação de novos bugs.
Nota
O EventManager é atualizado para cada método de teste. Isso significa que,
ao executar vários testes ao mesmo tempo, você perderá seus ouvintes de
eventos que foram registrados no config/bootstrap.php, pois o bootstrap
é executado apenas uma vez.
Executando Testes
Depois de instalar o PHPUnit e escrever alguns casos de teste, você deverá executá-los com muita
frequência. É uma boa ideia executar testes antes de confirmar quaisquer alterações para ajudar
a garantir que você não tenha quebrado nada.
Ao usar o phpunit
, você pode executar os testes do aplicativo. Para executar os testes do seu
aplicativo, você pode simplesmente executar:
# instalado pelo Composer
$ vendor/bin/phpunit
# arquivo phar
php phpunit.phar
Se você clonou o código-fonte CakePHP do GitHub <https://github.com/cakephp/cakephp> __ e deseja
executar os testes de unidade do CakePHP, não se esqueça de executar o seguinte comando Composer
antes de executar phpunit
para que todas as dependências sejam instaladas:
No diretório raiz do seu aplicativo. Para executar testes para um plug-in que faz parte da fonte do
aplicativo, primeiro execute cd
para o diretório do plug-in, depois use o comando phpunit
que
corresponde à maneira como você instalou o phpunit:
cd plugins
# Usando o phpunit instalado pelo compositor
../vendor/bin/phpunit
# Usando o arquivo phar
php ../phpunit.phar
Para executar testes em um plug-in independente, você deve primeiro instalar o projeto
em um diretório separado e instalar suas dependências:
git clone git://github.com/cakephp/debug_kit.git
cd debug_kit
php ~/composer.phar install
php ~/phpunit.phar
Filtrando Casos de Teste
Quando você tem casos de teste maiores, geralmente deseja executar um subconjunto
dos métodos de teste ao tentar trabalhar em um único caso com falha. Com o corredor
da CLI, você pode usar uma opção para filtrar os métodos de teste:
$ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest
O parâmetro filter é usado como uma expressão regular com distinção entre maiúsculas e minúsculas para
filtrar quais métodos de teste executar.
Gerando Cobertura de Código
Você pode gerar relatórios de amostras de código a partir da linha de comando usando as
ferramentas internas de cobertura de código do PHPUnit. O PHPUnit irá gerar um conjunto de
arquivos HTML estáticos contendo os resultados da cobertura. Você pode gerar cobertura para
um caso de teste, fazendo o seguinte:
$ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest
Isso colocará os resultados da cobertura no diretório webroot do seu aplicativo.
Você deve conseguir visualizar os resultados acessando http://localhost/your_app/coverage
.
Se você estiver usando o PHP 5.6.0 ou superior, poderá usar o phpdbg
para gerar cobertura
em vez do xdebug. O phpdbg
geralmente é mais rápido na geração de cobertura:
$ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest
Combinando Conjuntos de Testes para Plug-ins
Muitas vezes, seu aplicativo será composto de vários plugins. Nessas situações,
pode ser bastante entediante executar testes para cada plug-in. Você pode fazer
testes em execução para cada um dos plugins que compõem seu aplicativo adicionando
seções adicionais <testsuite>
ao arquivo phpunit.xml.dist do seu aplicativo:
<testsuites>
<testsuite name="app">
<directory>./tests/TestCase/</directory>
</testsuite>
<!-- Adicione seus pacotes de plugins -->
<testsuite name="forum">
<directory>./plugins/Forum/tests/TestCase/</directory>
</testsuite>
</testsuites>
Quaisquer suítes de teste adicionais vinculados ao elemento <testsuites>
serão
executados automaticamente quando você usar phpunit
.
Se você estiver usando <testsuites>
para usar acessórios de plug-ins que você
instalou com o composer, o arquivo composer.json
do plugin deve adicionar o
espaço para nome do fixture à seção de carregamento automático. Exemplo:
"autoload-dev": {
"psr-4": {
"PluginName\\Test\\Fixture\\": "tests/Fixture/"
}
},
Retornos de Chamada do Ciclo de Cida do Caso de Teste
Os casos de teste têm vários retornos de chamada do ciclo de vida que você pode usar ao fazer o teste:
setUp
é chamado antes de cada método de teste. Deve ser usado para criar os objetos que serão
testados e inicializar quaisquer dados para o teste. Lembre-se sempre de chamar parent::setUp()
tearDown
é chamado após cada método de teste. Deve ser usado para limpeza após a conclusão do
teste. Lembre-se sempre de chamar parent::tearDown()
.
setupBeforeClass
é chamado uma vez antes do início dos métodos de teste em um caso.
Este método deve ser estático.
tearDownAfterClass
é chamado uma vez depois que os métodos de teste em um caso são iniciados.
Este método deve ser estático.
Fixtures
Ao testar o código que depende dos modelos e do banco de dados, pode-se usar fixtures
como uma maneira de gerar tabelas de dados temporárias carregadas com dados de amostra que
podem ser usados pelo teste. O benefício do uso de fixtures é que seu teste não tem chance
de interromper os dados do aplicativo ao vivo. Além disso, você pode começar a testar seu
código antes de realmente desenvolver conteúdo ao vivo para um aplicativo.
O CakePHP usa a conexão chamada test
no seu arquivo de configuração config/app.php.
Se essa conexão não for utilizável, uma exceção será gerada e você não poderá usar fixtures
de banco de dados.
O CakePHP executa o seguinte durante o curso de uma fixture no caso de teste:
Cria tabelas para cada um dos fixtures necessários.
Preenche tabelas com dados, se os dados forem fornecidos no fixture.
Executa métodos de teste.
Esvazia as tabelas de fixtures.
Remove tabelas de fixtures do banco de dados.
Conexões de Teste
Por padrão, o CakePHP fará o pseudônimo de cada conexão em sua aplicação. Cada
conexão definida no bootstrap do seu aplicativo que não inicia com test
terá
um alias prefixado criado com test
. As conexões com aliasing garantem que você
não use acidentalmente a conexão errada nos casos de teste. O alias de conexão é
transparente para o restante do seu aplicativo. Por exemplo, se você usar a conexão
‘padrão’, receberá a conexão test
nos casos de teste. Se você usar a conexão ‘replica’,
o conjunto de testes tentará usar ‘test_replica’.
Criando Fixtures
Ao criar um dispositivo elétrico, você definirá principalmente duas coisas: como
a tabela é criada (quais campos fazem parte da tabela) e quais registros serão
preenchidos inicialmente na tabela. Vamos criar nosso primeiro fixture, que
será usado para testar nosso próprio modelo de artigo. Crie um arquivo chamado
ArticlesFixture.php no seu diretório tests/Fixture, com o seguinte
conteúdo:
namespace AppTestFixture;
use CakeTestSuiteFixtureTestFixture;
class ArticlesFixture extends TestFixture
{
// Opcional. Configure esta propriedade para carregar fixtures
// em uma fonte de dados de teste diferente
public $connection = ‘test’;
- public $fields = [
‘id’ => [‘type’ => ‘integer’],
‘title’ => [‘type’ => ‘string’, ‘length’ => 255, ‘null’ => false],
‘body’ => ‘text’,
‘published’ => [‘type’ => ‘integer’, ‘default’ => ‘0’, ‘null’ => false],
‘created’ => ‘datetime’,
‘modified’ => ‘datetime’,
‘_constraints’ => [
‘primary’ => [‘type’ => ‘primary’, ‘columns’ => [‘id’]]
]
];
public $records = [
- [
‘title’ => ‘First Article’,
‘body’ => ‘First Article Body’,
‘published’ => ‘1’,
‘created’ => ‘2007-03-18 10:39:23’,
‘modified’ => ‘2007-03-18 10:41:31’
],
[
‘title’ => ‘Second Article’,
‘body’ => ‘Second Article Body’,
‘published’ => ‘1’,
‘created’ => ‘2007-03-18 10:41:23’,
‘modified’ => ‘2007-03-18 10:43:31’
],
[
‘title’ => ‘Third Article’,
‘body’ => ‘Third Article Body’,
‘published’ => ‘1’,
‘created’ => ‘2007-03-18 10:43:23’,
‘modified’ => ‘2007-03-18 10:45:31’
]
];
}
Nota
Recomenda-se não adicionar valores manualmente a colunas incrementais automáticas,
pois isso interfere na geração de sequência no PostgreSQL e SQLServer.
A propriedade $connection
define a fonte de dados que a fixture usará. Se seu
aplicativo usa várias fontes de dados, você deve fazer com que as fixtures
correspondam às fontes de dados do modelo, mas prefixados com test
. Por exemplo,
se o seu modelo usa a fonte de dados mydb
, sua fixture deve usar a fonte de dados
test_mydb
. Se a conexão test_mydb
não existir, seus modelos usarão a fonte de
dados test
padrão. As fontes de dados da fixture devem ser prefixadas com test
para reduzir a possibilidade de truncar acidentalmente todos os dados do seu aplicativo
ao executar testes.
Usamos $fields
para especificar quais campos farão parte desta tabela e como eles são
definidos. O formato usado para definir esses campos é o mesmo usado com Cake\Database\Schema\Table
.
As chaves disponíveis para definição da tabela são:
- type
Tipo de dados interno do CakePHP. Atualmente suportado:
string
: mapeia para VARCHAR
ou CHAR
uuid
: mapeia para UUID
text
: mapeia para TEXT
integer
: mapeia para INT
biginteger
: mapeia para BIGINTEGER
decimal
: mapeia para DECIMAL
float
: mapeia para FLOAT
datetime
: mapeia para DATETIME
timestamp
: mapeia para TIMESTAMP
time
: mapeia para TIME
date
: mapeia para DATE
binary
: mapeia para BLOB
- fixed
Usado com tipos de sequência para criar colunas CHAR em plataformas que as
suportam.
- length
Defina para o comprimento específico que o campo deve ter.
- precision
Defina o número de casas decimais usadas nos campos flutuante e decimal.
- null
Defina como true
(para permitir NULLs) ou false
(para desabilitar NULLs).
- default
Valor padrão que o campo assume.
Podemos definir um conjunto de registros que serão preenchidos após a criação da
tabela de fixtures. O formato é bastante simples, $records
é uma matriz de
registros. Cada item em $records
deve ser uma única linha. Dentro de cada linha,
deve haver uma matriz associativa das colunas e valores para a linha. Lembre-se de que
cada registro na matriz $records deve ter uma chave para todos os campos especificados
na matriz $fields
. Se um campo para um registro específico precisar ter um valor
null
, basta especificar o valor dessa chave como null
.
Dados Dinâmicos e Fixtures
Como os registros de uma fixture são declarados como uma propriedade de classe,
você não pode usar funções ou outros dados dinâmicos para definir fixtures.
Para resolver esse problema, você pode definir $records
na função init()
de sua
fixture. Por exemplo, se você quiser que todos os carimbos de data e hora criados e modificados
reflitam a data de hoje, faça o seguinte:
namespace AppTestFixture;
use CakeTestSuiteFixtureTestFixture;
class ArticlesFixture extends TestFixture
{
- public $fields = [
‘id’ => [‘type’ => ‘integer’],
‘title’ => [‘type’ => ‘string’, ‘length’ => 255, ‘null’ => false],
‘body’ => ‘text’,
‘published’ => [‘type’ => ‘integer’, ‘default’ => ‘0’, ‘null’ => false],
‘created’ => ‘datetime’,
‘modified’ => ‘datetime’,
‘_constraints’ => [
‘primary’ => [‘type’ => ‘primary’, ‘columns’ => [‘id’]],
]
];
public function init()
{
- $this->records = [
- [
‘title’ => ‘First Article’,
‘body’ => ‘First Article Body’,
‘published’ => ‘1’,
‘created’ => date(‘Y-m-d H:i:s’),
‘modified’ => date(‘Y-m-d H:i:s’),
],
];
parent::init();
}
}
Ao substituir init()
lembre-se de sempre chamar parent::init()
.
Carregando Fixtures em seus Casos de Teste
Depois de criar suas fixtures, convém usá-los em seus casos de teste. Em cada
caso de teste, você deve carregar as fixtures necessárias. Você deve carregar
uma fixture para cada modelo que terá uma consulta executada nele. Para carregar a fixture,
defina a propriedade $fixtures
no seu modelo:
class ArticlesTest extends TestCase
{
public $fixtures = ['app.Articles', 'app.Comments'];
}
O item acima carregará os fixtures de Article e Coment do
diretório fixture do aplicativo. Você também pode carregar fixture do core do CakePHP ou plugins:
class ArticlesTest extends TestCase
{
public $fixtures = [
'plugin.DebugKit.Articles',
'plugin.MyVendorName/MyPlugin.Messages',
'core.Comments'
];
}
Usar o prefixo core
carregará fixtures do CakePHP e, usando o nome de um plugin
como prefixo, carregará o fixture do plugin nomeado.
Você pode controlar quando seus fixtures são carregados configurando Cake\TestSuite\TestCase::$autoFixtures
para false
e carregá-los posteriormente usando Cake\TestSuite\TestCase::loadFixtures()
:
class ArticlesTest extends TestCase
{
public $fixtures = ['app.Articles', 'app.Comments'];
public $autoFixtures = false;
public function testMyFunction()
{
$this->loadFixtures('Articles', 'Comments');
}
}
Você pode carregar fixtures em subdiretórios. O uso de vários diretórios pode
facilitar a organização de suas fixtures, se você tiver um aplicativo maior.
Para carregar fixtures em subdiretórios, basta incluir o nome do subdiretório
no nome do fixtures:
class ArticlesTest extends CakeTestCase
{
public $fixtures = ['app.Blog/Articles', 'app.Blog/Comments'];
}
No exemplo acima, ambos os aparelhos seriam carregados a partir de tests/Fixture/Blog/
.
Classes de Tabela de Teste
Digamos que já temos nossa classe de tabela de artigos definida em
src/Model/Table/ArticlesTable.php e se parece com:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
class ArticlesTable extends Table
{
public function findPublished(Query $query, array $options)
{
$query->where([
$this->alias() . '.published' => 1
]);
return $query;
}
}
Agora, queremos configurar um teste que verifique esta classe de tabela. Vamos
agora criar um arquivo chamado ArticlesTableTest.php no seu diretório tests/TestCase/Model/Table,
com o seguinte conteúdo:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.Articles'];
}
Na variável de nossos casos de teste $fixtures
, definimos o conjunto de
fixtures que usaremos. Lembre-se de incluir todas as fixtures que terão consultas
executadas em comparação a eles.
Criando um Método de Teste
Vamos agora adicionar um método para testar a função publish()
na tabela Articles.
Edite o arquivo tests/TestCase/Model/Table/ArticlesTableTest.php para que agora fique assim:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\ArticlesTable;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class ArticlesTableTest extends TestCase
{
public $fixtures = ['app.Articles'];
public function setUp()
{
parent::setUp();
$this->Articles = TableRegistry::getTableLocator()->get('Articles');
}
public function testFindPublished()
{
$query = $this->Articles->find('published');
$this->assertInstanceOf('Cake\ORM\Query', $query);
$result = $query->enableHydration(false)->toArray();
$expected = [
['id' => 1, 'title' => 'First Article'],
['id' => 2, 'title' => 'Second Article'],
['id' => 3, 'title' => 'Third Article']
];
$this->assertEquals($expected, $result);
}
}
Você pode ver que adicionamos um método chamado testFindPublished()
. Começamos
criando uma instância da classe ArticlesTable
e, em seguida, executamos o método
find('Published')
. Em $expected
, definimos o que esperamos que seja o resultado
adequado (que sabemos desde que definimos quais registros são preenchidos inicialmente na
tabela de artigos). Testamos que o resultado é igual à nossa expectativa usando o método
assertEquals()
. Veja a seção Executando Testes para obter mais informações sobre como
executar seu caso de teste.
Métodos Mocks de Modelo
Haverá momentos em que você desejará burlar métodos nos modelos ao testá-los. Você
deve usar getMockForModel
para criar simulações de teste de classes de tabela.
Isso evita problemas com propriedades refletidas que as burlações (mocking) normais possuem:
public function testSendingEmails()
{
$model = $this->getMockForModel('EmailVerification', ['send']);
$model->expects($this->once())
->method('send')
->will($this->returnValue(true));
$model->verifyEmail('[email protected]');
}
No método tearDown()
, remova o mock com:
Teste de Integração do Controlador
Embora você possa testar as classes de controladores de maneira semelhante aos Helpers,
Models e Components, o CakePHP oferece uma trait especializada de nome IntegrationTestTrait
.
O uso dessa trait nos casos de teste do controlador permite realizar testes de alto nível.
Novo na versão 3.7.0: A classe IntegrationTestCase
foi movida para a trait IntegrationTestTrait
.
Se você não está familiarizado com o teste de integração, o teste de integração é uma abordagem
que facilita a verificação de várias unidades em conjunto. Os recursos de teste de integração
no CakePHP simulam uma solicitação HTTP sendo tratada pelo seu aplicativo. Por exemplo, testar
seu controlador também exercitará quaisquer componentes, modelos e auxiliares envolvidos no
processamento de uma determinada solicitação. Isso oferece um teste de alto nível da sua aplicação
e de todas as suas partes de trabalho.
Digamos que você tenha um ArticlesController típico e seu modelo correspondente. O código do
controlador se parece com:
namespace App\Controller;
use App\Controller\AppController;
class ArticlesController extends AppController
{
public $helpers = ['Form', 'Html'];
public function index($short = null)
{
if ($this->request->is('post')) {
$article = $this->Articles->newEntity($this->request->getData());
if ($this->Articles->save($article)) {
// Redirect as per PRG pattern
return $this->redirect(['action' => 'index']);
}
}
if (!empty($short)) {
$result = $this->Articles->find('all', [
'fields' => ['id', 'title']
]);
} else {
$result = $this->Articles->find();
}
$this->set([
'title' => 'Articles',
'articles' => $result
]);
}
}
Crie um arquivo chamado ArticlesControllerTest.php em seu
diretório tests/TestCase/Controller e coloque o seguinte dentro:
namespace App\Test\TestCase\Controller;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase;
class ArticlesControllerTest extends TestCase
{
use IntegrationTestTrait;
public $fixtures = ['app.Articles'];
public function testIndex()
{
$this->get('/articles');
$this->assertResponseOk();
// Mais asserts.
}
public function testIndexQueryData()
{
$this->get('/articles?page=1');
$this->assertResponseOk();
// Mais asserts.
}
public function testIndexShort()
{
$this->get('/articles/index/short');
$this->assertResponseOk();
$this->assertResponseContains('Articles');
// Mais asserts.
}
public function testIndexPostData()
{
$data = [
'user_id' => 1,
'published' => 1,
'slug' => 'new-article',
'title' => 'New Article',
'body' => 'New Body'
];
$this->post('/articles', $data);
$this->assertResponseSuccess();
$articles = TableRegistry::getTableLocator()->get('Articles');
$query = $articles->find()->where(['title' => $data['title']]);
$this->assertEquals(1, $query->count());
}
}
Este exemplo mostra alguns dos métodos de envio de solicitação e algumas das
asserções que o IntegrationTestTrait
fornece. Antes de fazer qualquer
afirmação, você precisará enviar uma solicitação. Você pode usar um dos seguintes
métodos para enviar uma solicitação:
get()
Envia uma solicitação GET.
post()
Envia uma solicitação POST.
put()
Envia uma solicitação PUT.
delete()
Envia uma solicitação DELETE.
patch()
Envia uma solicitação PATCH.
options()
Envia uma solicitação OPTIONS.
head()
Envia uma solicitação HEAD.
Todos os métodos, exceto get()
e delete()
, aceitam um segundo parâmetro que
permite enviar um corpo de solicitação. Depois de enviar uma solicitação, você pode
usar as várias asserções fornecidas por IntegrationTestTrait
ou PHPUnit para
garantir que sua solicitação tenha os efeitos colaterais corretos.
Novo na versão 3.5.0: options()
e head()
foram adicionados no 3.5.0.
Configurando a Solicitação
A trait IntegrationTestTrait
vem com vários métodos auxiliares para facilitar a
configuração das solicitações que você enviará ao seu aplicativo em teste:
// Configura cookies
$this->cookie('name', 'Uncle Bob');
// Defina um valor na sessão
$this->session(['Auth.User.id' => 1]);
// Configura cabeçalhos
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
O estado definido por esses métodos auxiliares é redefinido no método tearDown()
.
Testando Ações que Exigem Autenticação
Se você estiver usando AuthComponent
, precisará remover os dados da sessão que o
AuthComponent usa para validar a identidade de um usuário. Você pode usar métodos
auxiliares em IntegrationTestTrait
para fazer isso. Supondo que você tivesse um
ArticlesController
que continha um método add e que exigisse autenticação com o
método add, você poderia escrever os seguintes testes:
public function testAddUnauthenticatedFails()
{
// Nenhum conjunto de dados da sessão.
$this->get('/articles/add');
$this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
}
public function testAddAuthenticated()
{
// Define dados da sessão
$this->session([
'Auth' => [
'User' => [
'id' => 1,
'username' => 'testing',
// outras chaves
]
]
]);
$this->get('/articles/add');
$this->assertResponseOk();
// Outras asserts.
}
Testando Autenticação Stateless e APIs
Para testar APIs que usam autenticação sem estado, como autenticação Básica, você
pode configurar a solicitação para injetar condições do ambiente ou cabeçalhos que
simulam cabeçalhos de solicitação de autenticação reais.
Ao testar a autenticação Básica ou Digest, você pode adicionar as variáveis de
ambiente que o PHP cria <http://php.net/manual/en/features.http-auth.php>
automaticamente. Essas variáveis de ambiente usadas no adaptador de autenticação
descritas em basic-authentication:
public function testBasicAuthentication()
{
$this->configRequest([
'environment' => [
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'password',
]
]);
$this->get('/api/posts');
$this->assertResponseOk();
}
Se você estiver testando outras formas de autenticação, como OAuth2, poderá definir o
cabeçalho de Autorização diretamente:
public function testOauthToken()
{
$this->configRequest([
'headers' => [
'authorization' => 'Bearer: oauth-token'
]
]);
$this->get('/api/posts');
$this->assertResponseOk();
}
A chave de cabeçalhos em configRequest()
pode ser usada para configurar
qualquer cabeçalho HTTP adicional necessário para uma ação.
Testando Ações Protegidas por CsrfComponent ou SecurityComponent
Ao testar ações protegidas por SecurityComponent ou CsrfComponent, você pode ativar
a geração automática de token para garantir que seus testes não falhem devido a
incompatibilidades de token:
public function testAdd()
{
$this->enableCsrfToken();
$this->enableSecurityToken();
$this->post('/posts/add', ['title' => 'Exciting news!']);
}
Também é importante habilitar a depuração em testes que usam tokens para impedir
que o SecurityComponent pense que o token de depuração está sendo usado em um
ambiente sem depuração. Ao testar com outros métodos como requireSecure()
,
você pode usar configRequest()
para definir as variáveis de ambiente corretas:
// Falsificar conexões SSL.
$this->configRequest([
'environment' => ['HTTPS' => 'on']
]);
Novo na versão 3.1.2: Os métodos enableCsrfToken()
e enableSecurityToken()
foram adicionados no 3.1.2
Teste de Integração PSR-7 Middleware
O teste de integração também pode ser usado para testar todo o aplicativo PSR-7 e
Middleware. Por padrão, o IntegrationTestTrait
detecta
automaticamente a presença de uma classe App\Application
e habilita automaticamente
o teste de integração do seu aplicativo. Você pode alternar esse comportamento com o
método useHttpServer()
:
public function setUp()
{
// Ative o teste de integração PSR-7.
$this->useHttpServer(true);
// Desative o teste de integração PSR-7.
$this->useHttpServer(false);
}
Você pode personalizar o nome da classe do aplicativo usado e os
argumentos do construtor, usando o método configApplication()
:
public function setUp()
{
$this->configApplication('App\App', [CONFIG]);
}
Depois de ativar o modo PSR-7 e, possivelmente, configurar sua classe de
aplicativo, você pode usar os recursos restantes do IntegrationTestTrait
normalmente.
Você também deve tentar usar Application::bootstrap() para carregar qualquer
plug-in que contenha eventos/rotas. Isso garantirá que seus eventos/rotas
estejam conectados para cada caso de teste. Como alternativa, se você deseja carregar
plug-ins manualmente em um teste, pode usar o método loadPlugins()
.
Novo na versão 3.3.0: O Middleware PSR-7 e o método useHttpServer()
foram adicionados no 3.3.0.
Testando Mensagens Flash
Se você deseja testar a presença de mensagens flash na sessão e não o HTML
renderizado, pode usar enableRetainFlashMessages()
em seus testes
para reter mensagens flash na sessão, para poder escrever as assertions:
$this->enableRetainFlashMessages();
$this->get('/bookmarks/delete/9999');
$this->assertSession('That bookmark does not exist', 'Flash.flash.0.message');
A partir da versão 3.7.0, existem auxiliares de teste adicionais para mensagens flash:
$this->enableRetainFlashMessages();
$this->get('/bookmarks/delete/9999');
// Coloque uma mensagem flash na chave 'flash'.
$this->assertFlashMessage('Bookmark deleted', 'flash');
// Afirme a segunda mensagem flash, também na chave 'flash'.
$this->assertFlashMessageAt(1, 'Bookmark really deleted');
// Afirme uma mensagem flash na chave 'auth' na primeira posição
$this->assertFlashMessageAt(0, 'You are not allowed to enter this dungeon!', 'auth');
// Afirmar que uma mensagem flash usa o elemento error
$this->assertFlashElement('Flash/error');
// Afirme o segundo elemento de mensagem flash
$this->assertFlashElementAt(1, 'Flash/error');
Novo na versão 3.4.7: enableRetainFlashMessages()
foi adicionado em 3.4.7
Novo na versão 3.7.0: Asserções de mensagens em Flash foram adicionadas.
Testando um Controlador Com Resposta em JSON
JSON é um formato amigável e comum a ser usado ao criar um serviço da web.
Testar os pontos finais do seu serviço da web é muito simples com o CakePHP.
Vamos começar com um exemplo simples de controlador que responde em JSON:
class MarkersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}
public function view($id)
{
$marker = $this->Markers->get($id);
$this->set([
'_serialize' => ['marker'],
'marker' => $marker,
]);
}
}
Agora, criamos o arquivo tests/TestCase/Controller/MarkersControllerTest.php e
garantimos que nosso serviço da Web retorne a resposta adequada:
class MarkersControllerTest extends IntegrationTestCase
{
public function testGet()
{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$result = $this->get('/markers/view/1.json');
// Check that the response was a 200
$this->assertResponseOk();
$expected = [
['id' => 1, 'lng' => 66, 'lat' => 45],
];
$expected = json_encode($expected, JSON_PRETTY_PRINT);
$this->assertEquals($expected, (string)$this->_response->getBody());
}
}
Nós usamos a opção JSON_PRETTY_PRINT
, pois o CakePHP
embutido no JsonView usará essa opção quando debug
estiver ativado.
Desabilitando o Tratamento de Erros de Middlewares nos Testes
Ao depurar testes que estão falhando porque seu aplicativo está encontrando erros,
pode ser útil desativar temporariamente o middleware de manipulação de erros para
permitir que o erro subjacente seja exibido. Você pode usar o método disableErrorHandlerMiddleware()
para fazer isso:
public function testGetMissing()
{
$this->disableErrorHandlerMiddleware();
$this->get('/markers/not-there');
$this->assertResponseCode(404);
}
No exemplo acima, o teste falharia e a mensagem de exceção subjacente e o
rastreamento da pilha seriam exibidos em vez da verificação da página de erro
renderizada.
Métodos Assertion
A característica IntegrationTestTrait
fornece vários métodos de asserção
que tornam as respostas de teste muito mais simples. Alguns exemplos são:
// Verifica se o código da resposta é 2xx
$this->assertResponseOk();
// Verifica se o código de resposta é 2xx/3xx
$this->assertResponseSuccess();
// Verifica se o código de resposta é 4xx
$this->assertResponseError();
// Verifica se o código de resposta 5xx
$this->assertResponseFailure();
// Verifica se a resposta tem um código específico, exemplo: 200
$this->assertResponseCode(200);
// Verifica o cabeçalho do local
$this->assertRedirect(['controller' => 'Articles', 'action' => 'index']);
// Verifica se nenhum cabeçalho de redirecionamento foi definido
$this->assertNoRedirect();
// Verifique uma parte do cabeçalho Location
$this->assertRedirectContains('/articles/edit/');
// Adicionado em 3.7.0
$this->assertRedirectNotContains('/articles/edit/');
// Verifica se conteúdo de resposta não está vazio
$this->assertResponseNotEmpty();
// Verifica conteúdo de resposta vazio
$this->assertResponseEmpty();
// Afirmar conteúdo de resposta
$this->assertResponseEquals('Yeah!');
// Afirmar que o conteúdo da resposta não é igual ao especifícado
$this->assertResponseNotEquals('No!');
// Afirmar conteúdo de resposta parcialmente
$this->assertResponseContains('You won!');
$this->assertResponseNotContains('You lost!');
// Afirmar arquivo enviado de volta
$this->assertFileResponse('/absolute/path/to/file.ext');
// Afirmar layout
$this->assertLayout('default');
// Afirme qual modelo foi renderizado (se houver)
$this->assertTemplate('index');
// Afirmar dados na sessão
$this->assertSession(1, 'Auth.User.id');
// Afirmar cabeçalho de resposta.
$this->assertHeader('Content-Type', 'application/json');
$this->assertHeaderContains('Content-Type', 'html');
// Adicionado em 3.7.0
$this->assertHeaderNotContains('Content-Type', 'xml');
// Afirmar variáveis de exibição
$user = $this->viewVariable('user');
$this->assertEquals('jose', $user->username);
// Afirmar cookies na resposta
$this->assertCookie('1', 'thingid');
// Verifique o tipo de conteúdo
$this->assertContentType('application/json');
Além dos métodos de asserção acima, você também pode usar todas as asserções no TestSuite e
os encontrados em PHPUnit.
Teste de Integração de Console
Veja console-integration-testing para obter informações sobre testes de shells e comandos.
Testando Views
Geralmente a maioria dos aplicativos não testa diretamente seu código HTML.
Fazer isso geralmente resulta em conjuntos de testes frágeis e difíceis de
manter, com tendência a serem quebrados. Ao escrever testes funcionais usando
IntegrationTestTrait
, você pode inspecionar o conteúdo da
visualização renderizada configurando a opção` return` para ‘view’. Embora
seja possível testar o conteúdo da visualização usando IntegrationTestTrait
,
um teste de integração/visualização mais robusto e sustentável pode ser realizado
usando ferramentas como Selenium webdriver.
Testando Componentes
Vamos fingir que temos um componente chamado PagematronComponent em nosso aplicativo.
Esse componente nos ajuda a definir o valor limite de paginação em todos os
controladores que o utilizam. Aqui está o nosso exemplo de componente localizado
em src/Controller/Component/PagematronComponent.php:
class PagematronComponent extends Component
{
public $controller = null;
public function setController($controller)
{
$this->controller = $controller;
// Verifique se o controlador está usando paginação
if (!isset($this->controller->paginate)) {
$this->controller->paginate = [];
}
}
public function startup(Event $event)
{
$this->setController($event->getSubject());
}
public function adjust($length = 'short')
{
switch ($length) {
case 'long':
$this->controller->paginate['limit'] = 100;
break;
case 'medium':
$this->controller->paginate['limit'] = 50;
break;
default:
$this->controller->paginate['limit'] = 20;
break;
}
}
}
Agora podemos escrever testes para garantir que nosso parâmetro paginado limit
esteja sendo definido corretamente pelo método Adjust()
em nosso componente.
Criamos o arquivo tests/TestCase/Controller/Component/PagematronComponentTest.php:
namespace App\Test\TestCase\Controller\Component;
use App\Controller\Component\PagematronComponent;
use Cake\Controller\Controller;
use Cake\Controller\ComponentRegistry;
use Cake\Event\Event;
use Cake\Http\ServerRequest;
use Cake\Http\Response;
use Cake\TestSuite\TestCase;
class PagematronComponentTest extends TestCase
{
public $component = null;
public $controller = null;
public function setUp()
{
parent::setUp();
// Configure nosso componente e o controlador de teste fake
$request = new ServerRequest();
$response = new Response();
$this->controller = $this->getMockBuilder('Cake\Controller\Controller')
->setConstructorArgs([$request, $response])
->setMethods(null)
->getMock();
$registry = new ComponentRegistry($this->controller);
$this->component = new PagematronComponent($registry);
$event = new Event('Controller.startup', $this->controller);
$this->component->startup($event);
}
public function testAdjust()
{
// Teste nosso método de ajuste com diferentes configurações de parâmetros
$this->component->adjust();
$this->assertEquals(20, $this->controller->paginate['limit']);
$this->component->adjust('medium');
$this->assertEquals(50, $this->controller->paginate['limit']);
$this->component->adjust('long');
$this->assertEquals(100, $this->controller->paginate['limit']);
}
public function tearDown()
{
parent::tearDown();
// Limpar depois que terminarmos
unset($this->component, $this->controller);
}
}
Testando Ajudantes
Como uma quantidade decente de lógica reside nas classes Helper, é importante
garantir que essas classes sejam cobertas por casos de teste.
Primeiro, criamos um exemplo de auxiliar para testar. O CurrencyRendererHelper
nos ajudará a exibir moedas em nossos pontos de vista e, por simplicidade, só possui
um método usd()
:
// src/View/Helper/CurrencyRendererHelper.php
namespace App\View\Helper;
use Cake\View\Helper;
class CurrencyRendererHelper extends Helper
{
public function usd($amount)
{
return 'USD ' . number_format($amount, 2, '.', ',');
}
}
Aqui, definimos as casas decimais como 2, separador decimal para ponto,
separador de milhares para vírgula e prefixamos o número formatado com a
string ‘USD’.
Agora criamos nossos testes:
// tests/TestCase/View/Helper/CurrencyRendererHelperTest.php
namespace App\Test\TestCase\View\Helper;
use App\View\Helper\CurrencyRendererHelper;
use Cake\TestSuite\TestCase;
use Cake\View\View;
class CurrencyRendererHelperTest extends TestCase
{
public $helper = null;
// Aqui instanciamos nosso ajudante
public function setUp()
{
parent::setUp();
$View = new View();
$this->helper = new CurrencyRendererHelper($View);
}
// Testando a função usd()
public function testUsd()
{
$this->assertEquals('USD 5.30', $this->helper->usd(5.30));
// Devemos sempre ter 2 dígitos decimais
$this->assertEquals('USD 1.00', $this->helper->usd(1));
$this->assertEquals('USD 2.05', $this->helper->usd(2.05));
// Testando o separador de milhares
$this->assertEquals(
'USD 12,000.70',
$this->helper->usd(12000.70)
);
}
}
Aqui, chamamos usd()
com parâmetros diferentes e dizemos ao conjunto
de testes para verificar se os valores retornados são iguais ao esperado.
Salve isso e execute o teste. Você deverá ver uma barra verde e mensagens
indicando 1 passe e 4 asserções.
Quando você estiver testando um Helper que use outros helpers, “mock” o método
loadHelpers
da classe View.
Testando Eventos
O Sistema de Eventos é uma ótima maneira de desacoplar o código do
aplicativo, mas às vezes ao testar, você tende a testar os resultados dos eventos
nos casos de teste que os executam. Esta é uma forma adicional de acoplamento que
pode ser removida usando assertEventFired
e assertEventFiredWith
.
Expandindo no exemplo Orders, digamos que temos as seguintes tabelas:
class OrdersTable extends Table
{
public function place($order)
{
if ($this->save($order)) {
// remoção de carrinho movido para CartsTable
$event = new Event('Model.Order.afterPlace', $this, [
'order' => $order
]);
$this->eventManager()->dispatch($event);
return true;
}
return false;
}
}
class CartsTable extends Table
{
public function implementedEvents()
{
return [
'Model.Order.afterPlace' => 'removeFromCart'
];
}
public function removeFromCart(Event $event)
{
$order = $event->getData('order');
$this->delete($order->cart_id);
}
}
Nota
Para afirmar que os eventos foram disparados, você deve primeiro
ativar Rastreando Eventos no gerenciador de eventos que deseja reivindicar.
Para testar o OrdersTable
acima, habilitamos o rastreamento em setUp()
,
depois afirmamos que o evento foi disparado e afirmamos que a entidade $order
foi passada nos dados do evento:
namespace App\Test\TestCase\Model\Table;
use App\Model\Table\OrdersTable;
use Cake\Event\EventList;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;
class OrdersTableTest extends TestCase
{
public $fixtures = ['app.Orders'];
public function setUp()
{
parent::setUp();
$this->Orders = TableRegistry::getTableLocator()->get('Orders');
// ativar o rastreamento de eventos
$this->Orders->getEventManager()->setEventList(new EventList());
}
public function testPlace()
{
$order = new Order([
'user_id' => 1,
'item' => 'Cake',
'quantity' => 42,
]);
$this->assertTrue($this->Orders->place($order));
$this->assertEventFired('Model.Order.afterPlace', $this->Orders->getEventManager());
$this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->getEventManager());
}
}
Por padrão, o global EventManager
é usado para asserções, portanto, testar
eventos globais não requer a aprovação do gerenciador de eventos:
$this->assertEventFired('My.Global.Event');
$this->assertEventFiredWith('My.Global.Event', 'user', 1);
Novo na versão 3.2.11: O rastreamento de eventos, assertEventFired()
e assertEventFiredWith
foram adicionados.
Testando Email
Veja email-testing para obter informações sobre o teste de email.
Criando Suítes de Teste
Se você deseja que vários de seus testes sejam executados ao mesmo tempo, é possível
criar um conjunto de testes. Um conjunto de testes é composto por vários casos de teste.
Você pode criar suítes de teste no arquivo phpunit.xml do seu aplicativo. Um exemplo
simples seria:
<testsuites>
<testsuite name="Models">
<directory>src/Model</directory>
<file>src/Service/UserServiceTest.php</file>
<exclude>src/Model/Cloud/ImagesTest.php</exclude>
</testsuite>
</testsuites>
Criando Testes para Plugins
Os testes para plugins são criados em seu próprio diretório, dentro da pasta plugins.:
/src
/plugins
/Blog
/tests
/TestCase
/Fixture
Eles funcionam como testes normais, mas você deve se lembrar de usar as convenções de
nomenclatura para plug-ins ao importar classes. Este é um exemplo de uma caixa de teste
para o modelo BlogPost
do capítulo de plugins deste manual. A diferença de outros
testes está na primeira linha em que ‘Blog.BlogPost’ é importado. Você também precisa
prefixar os dispositivos de seu plugin com plugin.Blog.BlogPosts
:
namespace Blog\Test\TestCase\Model\Table;
use Blog\Model\Table\BlogPostsTable;
use Cake\TestSuite\TestCase;
class BlogPostsTableTest extends TestCase
{
// Acessórios para plug-ins localizados em /plugins/Blog/tests/Fixture/
public $fixtures = ['plugin.Blog.BlogPosts'];
public function testSomething()
{
// Teste alguma coisa.
}
}
Se você deseja usar fixtures de plug-in nos testes do aplicativo, pode referenciá-los usando a
sintaxe plugin.pluginName.fixtureName
na matriz $fixtures
. Além disso, se você usar o
nome do plugin do fornecedor ou os diretórios do equipamento, poderá usar o seguinte:
plugin.vendorName/pluginName.folderName/fixtureName
.
Antes de usar os equipamentos, verifique novamente se o seu phpunit.xml
contém o ouvinte do equipamento:
<!-- Configurar um ouvinte para fixtures -->
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
Você também deve garantir que suas fixtures sejam carregáveis. Verifique se o
seguinte arquivo está presente em seu arquivo composer.json:
"autoload-dev": {
"psr-4": {
"MyPlugin\\Test\\": "plugins/MyPlugin/tests/"
}
}
Nota
Lembre-se de executar o composer.phar dumpautoload
ao adicionar novos
mapeamentos de carregamento automático.