- A ocorrência de bugs no código não são legais e a necessidade de correções em produção menos ainda.
- É recomendável programar de forma profissional utilizando testes.
- É importante escrever código cuidadosamente projetado e que seja possível adicionar novas funcionalidades com confiança.
- A ferramenta padrão de facto para testes em PHP é o PHPUnit.
- Utiliza-se o comando
composer require phpunit/phpunit --dev
para a instalação do PHPUnit no projeto. - Recomenda-se organizar os testes em um diretório chamado
tests
. - Os projetos Symfony possuem um arquivo
phpunit.xml.dist
por padrão. - Em projetos Symfony o arquivo
composer.json
possui uma entrada"psr-4": { "Tests\\": "tests/" }
emautoload-dev
que define que as classes no diretóriotests
deve pertencer ao namespaceTests
. - Uma classe de teste deve ter o seu nome terminado com o sufixo
Test
e estender a classeTestCase
do PHPUnit. - Todos os métodos em uma classe de teste devem ser públicos e começar com o prefixo
test
. - O PHPUnit procura por todas as classes terminadas em
Test
e executam os métodos públicos começados emtest
.
- O TDD ou Test-Driven Development é uma metodogia em que se escreve o teste antes do código.
- Recomenda-se que a estrutura do diretório de testes seja similar a estrutura do diretório do projeto.
- Nem todas as classes precisam de testes.
- O nome dos métodos de testes devem começar com o prefixo
test
e ser bem descritivo deixando claro a sua intenção. - O PHPUnit possui inúmeras funções de asserção.
- Pode-se consultar a lista de asserções disponíveis no PHPUnit através da documentação.
- Recomenda-se aprender as asserções aos poucos, a medida que se vai precisando delas.
- A asserção
assertSame($expected, $actual)
garante que valor e tipo são os mesmos. - Pode-se utilizar uma asserção
assertTrue($expected === $actual)
, porém utilizar uma asserção mais específica permite obter uma mensagem de erro mais descritiva. - Ao escrever os testes de forma antecipada somos forçados a pensar em como projetar uma classe.
- A asserção
$this->assertGreaterThanOrEqual($expected, $actual)
permite verificar se um valor é maior ou igual ao resultado esperado.
- Existem três tipos de testes que são: testes unitários, testes de integração e testes funcionais.
- Os testes unitários possuem as seguintes características:
- testar uma função específica em uma classe
- é a forma mais pura de testar
- você chama a função com um valor e testa o seu resultado
- os testes unitários devem ser feitos de forma isolada
- se uma classe à ser testada necessita de uma conexão com o banco de dados, deve-se criar uma classe falsa e focar somente na classe que precisa ser testada
- Os testes de integração são semelhantes à testes unitários porém que utilizam implementações reais. Por exemplo, utilizaria uma conexão verdadeira ao banco de dados.
- Os testes funcionais são testes que comandam um navegador de forma automatizada. Por exemplo, você escreve um código PHP que visita uma página, clica em um link, preenche um formulário, faz o envio do formulário e verifica se algum texto aparece na tela seguinte.
- Sempre nos perguntamos o quanto devemos testar? Ou se toda a função ou método precisa de um teste unitário? Ou ainda se toda página ou erro de validação de um formulário precisa de um teste funcional?.
- Ter pouco teste é melhor que não ter nenhum teste.
- Pode-se utilizar a regra "se te causa medo, teste".
- Ter muitos testes nem sempre é bom.
- A criação de muitos testes pode ser desperdício de tempo, entregar pouco valor ou ser lento ao precisar de fazer pequenas alterações.
- O TDD possui três passos: (1) criar o teste, (2) fazer o teste passar com o mínimo de código possível e (3) refatorar o código.
- Às vezes não é preciso nem testar.
- O TDD é mais do que testar o código, é sobre te forçar a pensar no design de seu código.
- No TDD é muito importante manter o código simples, ou seja, não enfeitar demais o código ou cobrir casos de usos desnecessários.
- Quando aparecer um caso específico deve-se, primeiramente, criar um teste e, então, realizar a implementação.
- Os testes passam confiança na implementação e refatoração do código.
- É possível configurar o PhpStorm para preencher o namespace por padrão ao criar uma nova classe. Para isso, basta informar o arquivo
composer.json
do projeto nas configurações. - Ao utilizar TDD torna-se fácil saber o nome da classe, métodos, argumentos e como devem se comportar.
- Utilizar menos métodos são menos oportunidades para bugs.
- Deve-se criar um método somente quando for necessário.
- O método
setUp
é utilizado para a criação do cenário de testes e é excutado antes de cada um dos testes pelo PHPUnit. - Os testes devem ser completamente independentes uns dos outros. Ou seja, os testes não devem depender de cenários de outros testes.
- Pode-se executar um teste por vez e é muito útil em situações de debug.
- O método
tearDown
é o oposto do métodosetUp
. Ele é utilizado para tornar o cenário de testes igual ao cenário inicial antes de qualquer teste ser executado. - O método
tearDown
é chamado após cada um dos testes ser executado. - O método
tearDown
é utilizado para limpeza. - Existem também os métodos
setUpBeforeClass
etearDownAfterClass
. - Ele são executados antes e depois de classe e são utilizados para coisas globais ou estáticas.
- Outro método disponível no PHPUnit é o
onNotSuccesfulTest
que pode ser utilizado para imprimir informações de debug. - O método
markTestIncomplete
permite marcar um teste como incompleto, ou seja, funciona como um lembrete para lembrar-nos que temos trabalho a fazer. - O método
markTestSkipped
permite pular um teste em casos específicos. - Utiliza-se o método
markTestSkipped
em situações em que o objeto à ser testado depende de algo que não foi implementado. - Trata-se de um método muito utilizado internamente no core do Symfony.
- Os data providers permitem rodar um mesmo teste inúmeras vezes de acordo com uma fonte de dados e passando argumentos diferentes em cada vez.
- Deve-se criar um método sem o prefixo
test
para ser odataProvider
. - O método deve retornar um array multidimensional, ou seja, uma lista de valores utilizados para a realização dos testes.
- O método de teste será executado uma vez para cada item do data provider, ou seja, se o data provider tiver três itens, o método de teste seja executado três vezes.
- Métodos de testes não podem ter argumentos, com exceção para métodos que possuem um data provider.
- O método que for utilizar o data provider deve utilizar a anotação
@dataProvider getDataForTests
. - Os valores de cada item serão informados como argumentos do método de testes.
- Pode-se adiciona uma chave para identificar um item do data provider tornando mais fácil a identificação de um erro.
- O importante é fazer os testes passarem e não fazer código bonito.
- Existem situações em que para testar uma classe A é necessário um objeto de uma classe B.
- O teste unitário deve testar uma classe de forma completamente isolada de outras classes.
- Com mocking, ao invés de passar objetos reais, um objeto falso é criado e utilizado já que ele não é o foco principal do teste.
- Um objeto de mock fornece várias formas de controle.
- Recomenda-se criar mocks para objetos de serviços.
- Faça o mock de serviços, mas não faça mock de objetos de modelo simples.
- Um classe de modelo é basicamente para o armazenamento de dados.
- As entidades são classes de modelo.
- Uma classe de serviço é uma classe que o seu objetivo é fazer um trabalho.
- Ela não costuma armazenar muitos dados.
- É possível fazer o mock de um modelo mas é desnecessário.
- Como uma classe de modelo tende a ser simples e apenas armazenar dados, é mais prático criar objetos desta classe e definir os valores que achar melhor.
- Pode-se utilizar o método de asserção
assertEmpty()
ao invés deassertCount(0)
.
- Pode-se informar o PHPUnit que uma exceção é esperada.
- Utiliza-se o método
expectException(CustomException::class)
e como argumento o nome da exceção esperada. - Deve-se adicionar o método antes do código que irá lançar uma exceção.
- Pode-se utilizar também a anotação
@expectedException
informando o nome completo da exceção esperada. Por exemplo:@expectedException \AppBundle\Exception\NotABuffetException
.
- Pode-se utilizar o método
expectExceptionMessage('Expected message')
para verificar a mensagem esperada da exceção lançada. - Pode-se rodar um teste apenas utilizando o parâmetro
--filter
seguido do nome do método de teste. Por exemplo,vendor/bin/phpunit --filter testMethodName
. - A utilização da anotação
@var Collection
diz que uma propriedade é do tipo Collection e a anotação@var Collection|Security[]
diz que uma propriedade é do tipo Collection que contém vários objetos do tipo Security. - A utilização da anotação
@var
permite um melhor auto-completion nos editores e IDEs.
- Pode-se rodar testes de uma classe utilizando o caminho completo para o arquivo da classe. Por exemplo,
vendor/bin/phpunit tests/AppBundle/Service/DinosaurLengthDeterminatorTest.php
. - Ao utilizar o PhpStorm pode-se definir uma dependência no construtor com a sua anotação de tipo e utilizar a funcionalidade initialize fields para auto-completar a propriedade.
- Deve-se criar um objeto de mock de uma classe de serviço, ou seja, de uma classe que faz um determinado trabalho.
- Um objeto de mock é um objeto que parece outro, mas que na realidade não é.
- Os objetos de mock são mais fáceis de criar do que objetos reais e seus métodos são falsos.
- Se uma classe real realiza uma consulta à uma base de dados ou faz uma chamada para uma API, o mock não realizará tarefa alguma.
- Ao criar um objeto de mock uma classe é criada na memória que estende a classe original, sobrescreve todos os seus métodos e retornam
null
,0
ou""
dependendo da assinatura de retorno do método. - Na chamada de método de um objeto de mock, nenhum código é executado e é retornado um valor padrão (
null
,0
ou""
). - O PHPUnit analisa o tipo de retorno do método e pode retornar
0
, por exemplo. - O objeto de mock é uma versão zumbi do objeto original.
- Um objeto de mock é criado utilizando o método
getMockBuilder()
. Ele faz parte da API interna do PHPUnit e é utilizado muitas vezes por dar maior controle na criação de mocks. - Quando um mock é criado utilizando o método
getMockBuilder()
a criação do método construtor é pulada. - Ao pular a criação do método construtor na criação do objeto de mock nos é permitido não nos preocupar com os argumentos do método construtor.
- Por padrão são criados mocks de todos os métodos.
- Pode-se utilizar o método
setMethods()
para criar mocks de alguns métodos. - Existem diferentes tipos de dublês de testes como dummies, stubs, spies e mocks.
- É possível configurar para que mocks de métodos retornem valores específicos.
- É possível, ao chamar um método, não executar o seu código e retornar um determinado valor.
- Para configurar um método para retornar determinado valor utiliza-se o método
willReturn($value)
. Por exemplo,$mock->method('doSomething')->willReturn(20)
. - O método
returnValueMap($map)
permite mapear diferentes valores de retorno para diferentes argumentos de entrada. Por exemplo,$stub->method('doSomething')->will($this->returnValueMap([...]))
. - Isso é bastante útil quando se tem que chamar o mesmo método várias vezes com valores diferentes.
- O método
willReturnCallback()
permite passar um callback e programá-lo para retornar o valor desejado. - Ele possui o poder do método
returnValueMap()
e possui uma API mais agradável. - Pode-se consultar a documentação do PHPUnit relacionado dublês de testes em https://phpunit.de/manual/6.5/pt_br/test-doubles.html.
- Ao criar o mock de um objeto e programar o retorno de um método para um valor específico, este valor será retornado mesmo com outros valores de argumentos ou passados de forma equivocada.
- É possível definir o número de vezes que um método deve ser chamado.
- É possível definir também quais argumentos devem ser passados para um método.
- Se um método não for chamado o número de vezes definido ou com os argumentos corretos o teste irá falhar.
- Utiliza-se o método
expects($this->once())
para definir o números de vezes que o método deve ser chamado. - Utiliza-se o método
with($arguments)
para definir os argumentos com que o método deve ser chamado. - Um exemplo completo que define o mock de um método, que deve ser chamado uma vez, com determinados argumentos e que irá retornar o valor
10
.
$mockObject
->expects($this->once())
->method('doSomething')
->with($arguments)
->willReturn(10);
- Não se deve utilizar indiscriminadamente esta abordagem por tornar o seu teste muito estrito.
- Deve-se atentar que, ao utilizar esta abordagem, estamos testando como o código interno é escrito.
- Quanto mais se utilizar esta abordagem, mais o teste irá quebrar quando não deveria.
- Trata-se de um microgerenciamento do código.
- Deve-se decidir o quão estrito um teste deve ser.
- Pode-se utilizar outros comparadores para definir a quantidade de vezes que um método deve ser chamado como
any()
,never()
,atLeastOnce()
ouexactly()
. - Para verificar outros comparadores consulte a seção de dublês de testes da documentação do PHPUnit.
- Pode-se utilizar métodos como
$this->greaterThan(10)
,$this->stringContains('something')
ou$this->anything()
para controlar os valores que o mock de um método deve receber emwith()
. - Pode-se ainda utilizar callbacks para definir a lógica de validação dos valores de argumentos passados para o mock do método.
- Os mocks sao muito úteis e importantes.
- O
EntityManager
do Doctrine é um serviço e deve ser instanciado. - O
EntityManager
requer uma conexão com base de dados e não faz sentido construir tudo do zero. - A utilização de objetos de mock torna a mais fácil lidar com objetos que possuem muitas dependências.
- É possível criar mocks de interfaces como
EntityManagerInterface
.
- Pode-se utilizar a função
dump()
do Symfony para imprimir na tela a estrutura do objeto de mock. - Graças ao retorno de tipo em métodos, funcionalidade disponível a partir do PHP7, o PHPUnit sabe qual é o tipo do objeto de retorno e cria o método em um objeto de mock que retorna o tipo correto em vez de retornar
null
.
- O PHPUnit possui um sistema de mocking, porém não é o único existente no mundo PHP.
- Existem outras bibliotecas para a criação de mocks como Mockery e Prophecy.
- As duas são utilizadas para a criação de mocks mas cada uma possui a sua particularidade.
- A biblioteca Prophecy é disponibilizada junto com o PHPUnit.
- Para a criação de mocks utilizando a biblioteca Prophecy utiliza-se o método
$this->prophesize()
. Por exemplo,$em = $this->prophesize(EntityManagerInterface::class)
cria objeto de mock daEntityManagerInterface
do Doctrine. - O PhpStorm não é capaz de lidar com auto-completion da biblioteca Prophecy pois ela funciona de forma "quase mágica".
- Existem dois plugins do PhpStorm que permitem lidar melhor com auto-complete e com a biblioteca Prophecy que são PHP Toolbox e PHPUnit Enhancement.
- Para simular uma chamada de método no objeto de mock basta chamar o método normalmente como se fosse em um objeto real.
- Para definir o tipo de argumento que deve ser passado para o método utiliza-se
Argument::type('string')
para argumentos do tipo string ouArgument::type(Person::class)
para argumentos do tipo especificado. - Para definir um número de vezes que um método deve ser chamado utiliza-se
shouldBeCalled()
oushouldBeCalledTimes($numberOfTimes)
. - Existem também os métodos
shouldNotBeCalled
eshould
. - Se não for importante definir o tipo de argumento da chamada de método deve-se deixar vazio.
- Para garantir que um valor deve ser passado como argumento de um método mas o seu tipo não importa utiliza-se
Argument::any()
. - Para garantir que um valor passado como argumento tenha uma regra personalizada utiliza-se
Argument::that($callback)
em que callback trata-se de uma função que faz a validação da regra. - Para definir o valor de retorno de um método do objeto de mock utiliza-se
willReturn(new Person())
. - Pode-se utilizar os métodos
willThrow
ewill
que aceitam um callback e permite personalizar o valor de retorno. - Para se obter o objeto de mock após a sua definição utiliza-se
reveal()
para construir e retornar o objeto. Por exemplo,$em->reveal()
. - O método
reveal()
permite tornar o objeto mock builder em um objeto de mock verdadeiro. - A biblioteca Prophecy tem-se tornado mais popular e mais utilizada no PHPUnit.
- Um exemplo completo que define um objeto de mock, o mock de um método, que deve ser chamado uma vez e com determinados argumentos e o mock de outro método, que deve ser chamado pelo menos uma vez e sem argumentos.
$em = $this->prophesize(EntityManagerInterface::class);
$em->persist(Argument::type(Person::class))
->shouldBeCalledTimes(1);
$em->flush()->shouldBeCalled();
$mockObject = $em->reveal();
- Para mostrar as opções disponíveis ao executar o PHPUnit através da linha de comando utiliza-se o comando
vendor/bin/phpunit -h
. - Pode-se informar arquivos ou diretórios ao executar o PHPUnit. Por exemplo,
phpunit tests/Unit/Entity/Person.php
ouphpunit tests/Unit
. - Para mais informações sobre as opções disponíveis deve-se consultar a documentação do PHPUnit.
- Ao utilizar Symfony 4 recomenda-se utilizar o phpunit-bridge que fornece funcionalidades adicionais.
- A flag
--filter
é bastante flexível e permite executar apenas um método de teste. Por exemplo,phpunit --filter testSum
. - A utilização da flag é muito útil em situações de debug.
- As opções da flag
--filter
é bastante rica e possui variações como:- namespaces
TestNamespace
- classes
TestCaseClass
- métodos
testMethod
- data provider específico
testMethod#2
- intervalo de data provider
testMethod#2-5
- namespaces
- A flag
--debug
permite mostrar mais informações sobre um teste específico. - Os exemplos de utilização são:
phpunit --filter testMethod --debug
- para um método específico
phpunit --filter 'testMethod #1' --debug
- para um data provider específico
phpunit --filter 'testMethod @default response' --debug
- para um data provider específico de acordo com a chave
- Para definir que o PHPUnit termine a execução caso um erro ou falha aconteça, em vez de esperar que todos os testes sejam executados, utiliza-se as flags
--stop-on-failure
e--stop-on-error
. - O PHPUnit possui um arquivo de configuração chamado
phpunit.xml
ouphpunit.xml.dist
. - No arquivo de configuração do PHPUnit existe uma entrada chamada
bootstrap
que refere-se ao arquivo de autoloadvendor/autoload.php
gerado pelo Composer. - Pode-se também, através do arquivo de configuração, alterar as configurações internas (php.ini) ou definir variáveis de ambiente.
- No arquivo de configuração pode-se definir várias suítes de testes.
- A maioria dos projetos possuem, normalmente, apenas uma suíte de testes.
- Utilizar suítes de testes permite separar testes unitários, testes de integração e testes funcionais e executá-los separadamente.
- Isso é importante pois testes de integração e funcionais são mais lentos que testes unitários.
- Para executar uma suíte de testes específica utiliza-se o comando
phpunit --testsuite feature
.
<testsuite name="feature">
<directory>tests/Feature</directory>
</testsuite>
- Para listar as suítes de testes disponíveis utiliza-se o comando
phpunit --list-suites
. - Para listar os testes disponíveis utiliza-se o comando
phpunit --list-tests
.
- Como testar se uma consulta complexa em um repositório Doctrine funciona?
- A única maneira de testar este método é executar uma consulta diretamente na base de dados.
- Ao testar uma classe e percebermos que mockamos tords as dependencias os teste pode ser inutil.
- Nestes cassos precisamos de testes de interação.
- Como simular a data e horário atual para a realização de testes?
- Testes em Javascript: Diferença entre Fake, Spy, Stub e Mock por Waldemar Neto
- Escreva Testes Melhores em 5 Passos por Marcos Brizeno
- The Practical Test Pyramid por Ham Vocke
- Mocks Aren't Stubs por Martin Fowler
- Mocks Não São Stubs por Martin Fowler
- Testing Asynchronous JavaScript
- GivenWhenThen por Martin Fowler
- SpecificationByExample por Martin Fowler
- InMemoryTestDatabase por Martin Fowler
- PageObject por Martin Fowler
- TestPyramid por Martin Fowler
- Introducing BDD por Dan North
- Introduzindo o BDD por Dan North
- https://code.tutsplus.com/articles/tdd-terminology-simplified--net-30626
- https://stackoverflow.com/questions/346372/whats-the-difference-between-faking-mocking-and-stubbing
- https://klauslaube.com.br/2014/08/07/os-testes-e-os-dubles-parte-1.html
- https://klauslaube.com.br/2015/06/29/os-testes-e-os-dubles-parte-2.html
- http://www.ifdattic.com/dummy-test-double-using-prophecy/
- http://www.ifdattic.com/stub-test-double-using-prophecy/
- http://www.ifdattic.com/spy-test-double-using-prophecy/
- http://www.ifdattic.com/mock-test-double-using-prophecy/
- http://www.ifdattic.com/fake-test-double-using-prophecy/
- https://inviqa.com/blog/php-test-doubles-patterns-prophecy
- Testes Unitários 101: Mocks, Stubs, Spies e todas essas palavras difíceis por Lucas Santos
- como testar APIs
- curso de postman
- curso de platform api
- curso de behat
- curso de testes em laravel
- livro tdd php