Skip to content

Instantly share code, notes, and snippets.

@marcelgsantos
Created March 20, 2019 05:45
Show Gist options
  • Save marcelgsantos/a3ae177aa7a63e8b9a08663cf6e30474 to your computer and use it in GitHub Desktop.
Save marcelgsantos/a3ae177aa7a63e8b9a08663cf6e30474 to your computer and use it in GitHub Desktop.

1. Anotações

  • 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/" } em autoload-dev que define que as classes no diretório tests deve pertencer ao namespace Tests.
  • Uma classe de teste deve ter o seu nome terminado com o sufixo Test e estender a classe TestCase 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 em test.
  • 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étodo setUp. 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 e tearDownAfterClass.
  • 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 o dataProvider.
  • 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 de assertCount(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() ou exactly().
  • 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 em with().
  • 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 da EntityManagerInterface 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 ou Argument::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() ou shouldBeCalledTimes($numberOfTimes).
  • Existem também os métodos shouldNotBeCalled e should.
  • 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 e will 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 ou phpunit 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
  • 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 ou phpunit.xml.dist.
  • No arquivo de configuração do PHPUnit existe uma entrada chamada bootstrap que refere-se ao arquivo de autoload vendor/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.

2. Dúvidas

  • Como simular a data e horário atual para a realização de testes?

3. Referências

4. Tarefas

  • como testar APIs
    • curso de postman
    • curso de platform api
  • curso de behat
  • curso de testes em laravel
  • livro tdd php
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment