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.

44d238e94ac021ed797b5d7ef2664dd5?s=128

Vinícius Alonso

July 21, 2018
Tweet

Transcript

  1. Entendendo Os famosos test doubles

  2. Olá! Eu sou Vinícius Alonso CTO na Letsgrow Sistemas Mentor

    pelo Training Center 2
  3. 3

  4. Tudo começa com TDD Para entender os Test Doubles precisamos

    começar do básico. 1
  5. 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
  6. 6

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

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

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

    não entendemos de cara quando começamos a estudar 9
  10. “ 10 TDD não é sobre testes e sim sobre

    design
  11. “ Nossos testes nos fornecem feedbacks instantâneos sobre nosso código

    11
  12. “ As suítes de testes não são tão fiéis ao

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

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

    testes • Lançou xUnit Test Patterns (2007) 14
  15. 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
  16. 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(); }
  17. 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
  18. 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
  19. 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()); }
  20. 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
  21. 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
  22. Test Doubles 3

  23. Test Doubles • Termo genérico para testes que substituem objetos

    de produção por: ◦ Dummy; ◦ Fake; ◦ Stub; ◦ Spy; ◦ Mock; 23
  24. 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
  25. 25 public function depositMoney(float value, Account $account) { if ($value

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

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

    fáceis de executar algo • Exemplo: InMemoryTestDatabase 27
  28. 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; } }
  29. 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()); }
  30. Stub • Fornece respostas prontas programadas para chamadas específicas em

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

    new \Exception('Invalid CPF'); } return $this->repository->create(); }
  32. 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()); }
  33. 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
  34. 34 class AddressReposity { public function save(Address $address) { $this->entityManager->persist($address);

    $this->entityManager->flush(); } }
  35. 35 public function shouldSaveNewAddress() { $em = $prophet->prophesize('Doctrine\ORM\EntityManager' ); $address

    = new Address(); $addressRepository = new AddressRepository($em->reveal()); $addressRepository->save($address); $em->flush()->shouldHaveBeenCalled(); }
  36. 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
  37. 37 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw

    new \Exception('Invalid CPF'); } return $this->repository->create(); }
  38. 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(); }
  39. Características da técnica Alguns pontos a considerar sobre usar essa

    técnica 4
  40. Design e feedback • Graças ao feedback dos testes podemos

    melhorar o design de classes • Evitando erros de OO e Bad Smell Codes 40
  41. # 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(); }
  42. # 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()); }
  43. 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
  44. 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
  45. 45 Obrigado! Dúvidas? Twitter: @alonsoemacao Github: @viniciusalonso E-mail: vba321@hotmail.com

  46. Referências • https://martinfowler.com/bliki/InMemoryTestDatabase.ht ml • https://martinfowler.com/bliki/TestDouble.html • https://martinfowler.com/articles/mocksArentStubs.html • https://www.amazon.com.br/Xunit-Test-Patterns-Refacto

    ring-Code/dp/0131495054 46