Repositório para meu workshop no WordCamp Catania 2019
Opcional, mas pode ser necessário instalar o docker:
sudo apt-get install docker-ce docker-ce-cli containerd.io
Presumo que você tenha o Composer instalado. Vamos instalar o PHPUnit primeiro:
composer require --dev phpunit/phpunit ^8.3
Por favor, verifique também os requisitos!
PHPUnit 8.3 requer pelo menos PHP 7.2! A propósito – o suporte de segurança para PHP 7.1 termina em 1º de dezembro de 2019.
Dica : você não tem o Composer instalado? Experimente isso!
docker run --rm -it -v $PWD:/app -u $(id -u):$(id -g) composer install
Existem pelo menos duas estruturas válidas que são úteis quando você planeja testar extensões do WordPress:
Vamos tentar o Brain Monkey :
composer require --dev brain/monkey:2.*`
Isso instalará automaticamente também o Mockery e o Patchwork. Basta executar composer install
e pronto.
Crie um diretório que abrigará uma pequena classe de teste chamada WcctaTest.php :
mkdir -p tests/wccta
Excelente! Agora vamos criar um arquivo de configuração phpunit.xml no diretório raiz.
Você também pode decidir executar seus testes com os parâmetros de configuração da linha de comando. Veja a próxima parte (dica: 'scripts')!
Ótimo! Adicione algumas seções ao arquivo compositor.json :
composer test
Vamos criar um diretório que abrigará nosso código-fonte. Este é o local onde você colocará uma primeira aula que testará em breve.
mkdir -p src/wccta && touch src/wccta/Plugin.php
rm -f tests/wccta/WcctaTest.php && touch tests/wccta/PluginTest.php
touch wordpress-plugins-phpunit.php
Queremos testar alguns métodos da classe Plugin
. Imagine um método chamado is_loaded
que retorna true
em caso de sucesso. Quando estiver pronto, execute:
composer test
Dica : Seu sistema ou versão do PHP não está atualizado? Você poderia simplesmente pular esta etapa, mas vamos tentar algo [não tão] novo!
docker run -it --rm -v $PWD:/app -w /app php:7.3-alpine php ./vendor/bin/phpunit
Você provavelmente pode imaginar que alguns plugins terão muitas classes e que você pode facilmente esquecer de testar todas as funcionalidades que precisam ser testadas.
Então, vamos falar sobre Cobertura !
Basta adicionar um comando personalizado à seção de scripts em seu compositor.json :
"coverage": "./vendor/bin/phpunit --coverage-html ./reports/php/coverage"
e um filtro para seu phpunit.xml :
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory>./src</directory>
</whitelist>
</filter>
Agora é só executar composer coverage
! Isso criará um diretório ./reports/php/coverage
junto com alguns arquivos html. Bem, não em todos os computadores. Alguns ainda receberão mensagens de erro como:
Error: No code coverage driver is available
Vamos consertar isso em nossa imagem docker. Preparei um Dockerfile para que você possa apenas executar:
docker build -t coverage .
E depois que o processo de construção for concluído:
docker run -it --rm -v $PWD:/app -w /app coverage:latest php ./vendor/bin/phpunit --coverage-html ./reports/php/coverage
Agora você conhece o Kung Fu! Por favor, abra o arquivo ./reports/php/coverage/index.html no seu navegador!
Vamos conectar nossa classe Plugin
ao plugin. Antes de realmente iniciarmos os testes, mostrarei apenas como declarar partes de seus códigos para não testar.
@codeCoverageIgnore
Esta é uma das anotações importantes que estão disponíveis. Voltaremos a isso mais tarde, mas primeiro:
Execute os unittests com o relatório de cobertura novamente!
Talvez você tenha notado a coluna CRAP
no relatório de cobertura. CRAP é um acrônimo para antipadrões de risco de mudança . Indica o quão arriscada pode ser uma mudança de código em uma classe ou método. Você pode diminuir o risco (e, portanto, o índice) com código menos complexo e cobertura total com testes.
Vamos começar a testar algo. Mas o quê? Ainda não há nenhuma funcionalidade adicional escrita que precise de testes.
Aí entra o TDD (Test Driven Development) no jogo.
Mesmo que decida não usar esta técnica, você deve pelo menos saber do que estamos falando.
Vamos primeiro criar um Test CarTest
que deverá testar se o método get_price
retorna a string '€ 14.500'
. Em seguida, crie uma classe Car
e escreva o método get_price
que satisfaça o teste. Não comece com a implementação.
Neste ponto, deixe-me apresentar também o padrão de teste AAA (Arrange Act Assert), que é amplamente aceito no TDD . Ele descreve como organizar um teste e é muito semelhante ao GWT (Given When Then) do BDD (Behavior-Driven Development).
Você pode testar suas classes se elas gerarem exceções em determinadas condições. Vamos agora implementar o método get_price
.
Basta criar uma classe Registry
que defina um valor misto como um item nomeado em uma matriz interna. Use um método set()
ou o método mágico __set()
para isso. Primeiro de tudo, suponha que podemos passar um objeto JSON para nossa classe Car
. Isso dará à nossa classe um pouco mais de valor.
Outro método get
ou __get()
deve verificar se existe um item com determinado e retorná-lo em caso de sucesso. Se não existir tal item, lance um OutOfBoundsException
. Agora escreva um construtor que lide com a entrada JSON e armazene o objeto em member-var data
. O método get_price
deve pegar o preço da var data
e cuidar da saída formatada.
Verifique a etapa 10 do branch se tiver dificuldade em escrever o código! O price
variável deve ser um número inteiro. Provavelmente isso não é problema agora porque você pode usar a função PHP number_format()
para criar a saída correta. Mas em uma instalação do WordPress você espera ter a localidade definida, como it_IT
(italiano), por exemplo.
A forma correta de formatar números no WordPress é usando a função number_format_i18n()
.
Então vamos mudar isso e ver o que acontece:
Error: Call to undefined function wcctanumber_format_i18n()
Vamos consertar isso em um segundo, mas vamos preparar um pouco primeiro. Brain Monkey usa setUp()
e tearDown()
fornecidos por PHPUnit . Você pode substituir esses métodos. Vamos criar um TestCase
personalizado - nomeie-o WcctaCase
- que podemos estender porque provavelmente faremos isso em todas as classes de teste.
Agora vamos incluir o namespace para testes na seção autoload-dev:
"autoload-dev": {
"psr-4": {
"tests\wccta\": "tests/wccta"
}
},
Finalmente, vamos mudar o pai de nossas classes de teste.
class CarTest extends WcctaTestCase { // ... }
Estamos prontos para simular nossa primeira função WordPress com
Functionsexpect( $name_of_function )->andReturn( $value );
Escrever um teste para apenas uma expectativa parece exigir muito esforço. E se você quiser testar valores diferentes?
Provedor de dados para o resgate. Já falei sobre anotações no passo 5. Esta também é muito útil:
@dataprovider method_that_returns_data
Dê uma olhada no meu exemplo. getData
retorna uma matriz de matrizes. Cada uma dessas matrizes contém 3 valores. Nosso método test_getPrice
pode não apenas aceitar o provedor de dados com a anotação, mas também pode definir os input-vars como parâmetros.
Você pode testar suas classes se elas gerarem exceções em determinadas condições.
Basta criar uma classe Registry
que defina um valor misto como um item nomeado em uma matriz interna. Use um método set()
ou o método mágico __set()
para isso.
Outro método get
ou __get()
deve verificar se existe um item com uma determinada chave e retorná-lo em caso de sucesso. Se não existir tal item, lance um OutOfBoundsException
.
Verifique a etapa 10 do branch se tiver dificuldade em escrever o código!
As últimas etapas nos levaram às Fábricas . O que é uma fábrica? Às vezes você cria funções ou métodos que simplesmente ocultam o processo complexo de criação de um objeto específico. E às vezes você precisa decidir que tipo de objeto deseja criar.
Nos plugins do WordPress eu prefiro adicionar ganchos em fábricas aos objetos. Existem plugins que adicionam ganchos em construtores de classes. Isso não é uma coisa boa (especialmente quando você ainda testa a maneira clássica – criando um ambiente completo com o WordPress instalado e funcionando).
Vamos criar uma classe Factory
com uma função estática chamada create
. Este método deve retornar um objeto Car
. Mas vamos refatorar o construtor de Car
para que ele já espere um objeto e nenhuma string JSON. Faremos isso no método create da classe Factory
.
Teste seu plugin agora com composer test
e você verá alguns erros:
TypeError: Argument 1 passed to wcctaCar::__construct() must be an object, string given, called in ...
Devemos corrigir nossos testes também...
Excelente! Vamos criar um teste para nossa Fábrica. Deixaremos o método sem nenhum conteúdo por enquanto. Execute os testes novamente!
There was 1 risky test:
1) testswcctaFactoryTest::test_create
This test did not perform any assertions
Os testes passam, mas você recebe a mensagem de que houve um teste arriscado. A propósito: Nomeie a função test_create
basta create
e use a anotação @test
. Acredito que o uso dessa anotação depende do seu gosto pessoal!
Agora vamos nos aprofundar um pouco mais nisso.
Crie uma interface FooterInterface
que defina info
de método público que não esperarão nenhum valor de retorno. Implemente a interface em Car
, info
poderia - por exemplo - gerar uma mensagem engraçada.
Defina o tipo de retorno FooterInterface
para o método create
de Factory
e adicione o método info
de Car
à ação WordPress wp_footer
.
Agora vamos testar isso no FactoryTest
. Existem pelo menos duas maneiras de testar isso corretamente. Use has_action ou ActionsexpectAdded()
. Um teste para filtros seria semelhante e está bem descrito na página vinculada.
Verifique se composer test
ainda passa em todos os testes.
Como está a cobertura agora? Execute composer coverage
e verifique a saída gerada.
O método info
da nossa classe Car
não é coberto por nenhum teste. Mas podemos testar a saída de um método?
Acontece que é muito fácil com expectOutputString.
Vamos comemorar o que aprendemos!
Crie uma classe Locale
que possua um método público get
que retorne get_locale()
. Exclua o método da cobertura!
Agora crie um construtor em nossa classe Plugin
que aceite uma instância Locale
e armazene-a em um member-var $this->locale
. Crie então um método get_region_code
que retorne o valor de $this->locale->get()
. Ah, e remova o método is_loaded
. ;)
Em nosso teste poderíamos criar um objeto do tipo Locale
, simular a função get_locale
do WordPress e passá-la para o construtor Plugin
! Mas eu quero o Tuse Mocker aqui:
public function test_get_region_code() {
$code = 'it_IT';
$locale = Mockery::mock( Locale::class );
$locale->shouldReceive( 'get' )->andReturn( $code );
$sut = new Plugin( $locale );
$this->assertEquals( $code, $sut->get_region_code() );
}
Agora você pode tornar seus plug-ins do WordPress à prova de balas!
Divirta-se!