Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Entendendo os famosos Test Doubles

Entendendo os famosos Test Doubles

Palestra apresentada na trilha PHP Essencial durante o The Developers Conference (TDC) 2018 em São Paulo.

Nessa apresentação discutimos um pouco sobre as diferenças entre dummies, fakes, spies, stubs e mocks.

Vinícius Alonso

July 21, 2018
Tweet

More Decks by Vinícius Alonso

Other Decks in Programming

Transcript

  1. 3

  2. Kent Beck: um pouco de história • Escreveu Sunit 1994

    • Apresentou TDD na OOPSLA 1995 • Junto com Erich Gramma publicou “Test Infected” (2000) • TDD by Example 5
  3. 6

  4. 7 public function testShouldBePalindrome () { $checker = new Checker();

    $isPalindrome = $checker->isPalindrome('arara') $this->assertTrue($isPalindrome); }
  5. 8 class Checker { public function isPalindrome(string $word) : bool

    { $originalWord = str_replace(' ', '', strtolower($word)); $reversed = str_replace(' ', '', strrev($originalWord)); return $originalWord == $reversed; } }
  6. Detalhes sobre TDD Algumas curiosidades sobre a prática que geralmente

    não entendemos de cara quando começamos a estudar 9
  7. “ As suítes de testes não são tão fiéis ao

    que estudamos na literatura. 12
  8. Conhecendo a família xUnit Agora que já entendemos um pouco

    de TDD, vamos falar sobre as suítes de testes. 2
  9. Gerard Meszaros • Estudou as estruturas das principais suítes de

    testes • Lançou xUnit Test Patterns (2007) 14
  10. As fases do xUnit • Setup ◦ Preparamos o ambiente

    para o teste • Exercise ◦ Simulamos o comportamento que esperamos testar • Verify ◦ Verificamos se ele teve o resultado esperado • Teardown ◦ Fazemos o ambiente voltar ao normal, como se o teste nunca tivesse acontecido 15
  11. 16 public function testShouldBeInactivated () { $this->user = User::create( $this->data);

    $this->user->setStatus(UserStatus::INACTIVE); $this->assertFalse($this->user->isActive()); } public function teardown() { $this->user->destroy(); }
  12. 17 public function testShouldBeInactivated () { $this->user = User::create( $this->data);

    $this->user->setStatus(UserStatus::INACTIVE); $this->assertFalse($this->user->isActive()); } public function teardown() { $this->user->destroy(); } Setup Exercise Verify Teardown
  13. SUT e colaboradores • SUT (System Under Test) ◦ O

    que está sendo testado em si ◦ Classe, método, função, etc • Colaboradores ◦ Interagem com o SUT durante a execução do teste 18
  14. 19 public function testShouldSumItemsValueAtCart () { $product = new Product(['value'

    => 20]); $item = new Item(['quantity' => 2, 'product' => $product]); $cart = new Cart(); $cart->addItem($item); $this->assertEquals(40, $cart->totalValue()); }
  15. 20 public function testShouldSumItemsValueAtCart () { $product = new Product(['value'

    => 20]); $item = new Item(['quantity' => 2, 'product' => $product]); $cart = new Cart(); $cart->addItem($item); $this->assertEquals(40, $cart->totalValue()); } Colaboradores SUT
  16. Colaboradores • No exemplo anterior utilizamos as classes reais para

    testar o SUT • Podemos substituir esses colaboradores reais por dublês, com isso, poderíamos chamar de Test Double 21
  17. Test Doubles • Termo genérico para testes que substituem objetos

    de produção por: ◦ Dummy; ◦ Fake; ◦ Stub; ◦ Spy; ◦ Mock; 23
  18. Dummy • São utilizados para preencher listas de parâmetros, porém,

    não são utilizados • Existem dois tipos: ◦ Dummy Object ◦ Dummy Value 24
  19. 25 public function depositMoney(float value, Account $account) { if ($value

    <= 0) { throw new \InvalidArgumentException(); } $account->subtractCurrentBalance( $value); }
  20. 26 /** * @expectedException InvalidArgumentException * */ public function testWhenValueLessOrEqualZeroShouldThrowAnException

    () { $account = $this->prophesize(Account::class); $deposit = new Deposit(); $deposit->depositMoney(-10, $account->reveal(); }
  21. Fake • Substituem objetos para criar atalhos ou formas mais

    fáceis de executar algo • Exemplo: InMemoryTestDatabase 27
  22. 28 class ClientRepositoryFake { private $clients = []; public function

    save(Client $client) : Client { $id = count($this->clients); $client->setId($id); $this->clients[$id] = $client; return $client; } public function find(int $id) : Client { return $this->clients[$id]; } public function update(Client $client) : Client { $this->clients[$client->getId()] = $client; return $client; } }
  23. 29 public function setup() { $client = new Client(['name' =>

    'Gandalf', 'cpf' => '87511196098']); $this->clientRepository = new ClientRepositoryFake(); $this->clientRepository->save($user); } public function testAssociateShouldGenerateAccountNumber() { $client = $this->clientRepository->find(1); $account = new Account(); $account->setClient($client); $accountAssociator = AccountAssociator(); $accountAssociator->associateAccountToClient($account, $client); $this->assertNotNull($account->getNumber()); }
  24. Stub • Fornece respostas prontas programadas para chamadas específicas em

    momento de execução • São utilizados na fase do setup 30
  25. 31 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw

    new \Exception('Invalid CPF'); } return $this->repository->create(); }
  26. 32 public function testShouldCreateAccountWhenCPFIsValid () { $validator = $this->prophesize(Validator::class); $validator->isCPFValid('23430222915')->willReturn(true);

    $data = ['cpf' => '23430222915']; $account = new Account($data); $account->setValidator($validator->reveal()); $this->instanceOf(Account::class, $account->create()); }
  27. Spy • Possue controle sobre as chamadas que são feitas

    em tempo de execução • Se foco é saber se determinado método/função foi chamado 33
  28. 35 public function shouldSaveNewAddress() { $em = $prophet->prophesize('Doctrine\ORM\EntityManager' ); $address

    = new Address(); $addressRepository = new AddressRepository($em->reveal()); $addressRepository->save($address); $em->flush()->shouldHaveBeenCalled(); }
  29. Mock • São utilizados na fase do verify • Diferente

    dos demais, é o único que faz a verificação por comportamento e não por estado 36
  30. 37 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw

    new \Exception('Invalid CPF'); } return $this->repository->create(); }
  31. public function testShouldCreateAccountWhenCPFIsValid() { $validator = $this->prophesize(Validator::class); $validator->isCPFValid('23430222915') ->willReturn(true) ->shouldBeCalled($this->once());

    $repository = $this->prophesize(Repository::class); $repository->create()->shouldBeCalled($this->once()); $data = ['cpf' => '23430222915']; $account = new Account($data); $account->setValidator($validator->reveal()); $account->setRepository($repository->reveal()); $account->create(); }
  32. Design e feedback • Graças ao feedback dos testes podemos

    melhorar o design de classes • Evitando erros de OO e Bad Smell Codes 40
  33. # Tem como mockar a classe abaixo? # Não sem

    usar o princípio da inversão de dependências do SOLID :D public function create() { $validator = new Validator(); if (!$validator->isCPFValid($this->data['cpf']) { throw new \Exception('Invalid CPF'); } return $this->repository->create(); }
  34. # Será que os parâmetros do mé todo create fazem

    sentido? public function testShouldCreateAccountWhenCPFIsValid () { $validator = $this->prophesize(Validator::class); $validator->isCPFValid('23430222915')->willReturn( true); $repository = $this->prophesize(Repository::class); $repository->create()->shouldBeCalled(); $request = $this->prophesize(Request::class); $request->post()->shouldBeCalled(); $data = ['cpf' => '23430222915']; $account = new Account(); $account->create($data, $validator->reveal(), $repository->reveal(), $request->reveal()); }
  35. Mensagens • Quando utilizamos test doubles focamos na troca de

    mensagens entre os objetos • Ao invés de testar as classes concretas diretamente • Evitando efeitos colaterais no mundo exterior ◦ Envio de e-mail ◦ Dados salvos em cache 43
  36. Quando mockar? • Quando um objeto é difícil de ser

    instanciado • Quando um objeto causa efeitos no mundo exterior (ex: enviar e-mail) • Quando quero verificar o garantir o comportamento de um colaborador 44