Slide 1

Slide 1 text

Entendendo Os famosos test doubles

Slide 2

Slide 2 text

Olá! Eu sou Vinícius Alonso CTO na Letsgrow Sistemas Mentor pelo Training Center 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

Tudo começa com TDD Para entender os Test Doubles precisamos começar do básico. 1

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

7 public function testShouldBePalindrome () { $checker = new Checker(); $isPalindrome = $checker->isPalindrome('arara') $this->assertTrue($isPalindrome); }

Slide 8

Slide 8 text

8 class Checker { public function isPalindrome(string $word) : bool { $originalWord = str_replace(' ', '', strtolower($word)); $reversed = str_replace(' ', '', strrev($originalWord)); return $originalWord == $reversed; } }

Slide 9

Slide 9 text

Detalhes sobre TDD Algumas curiosidades sobre a prática que geralmente não entendemos de cara quando começamos a estudar 9

Slide 10

Slide 10 text

“ 10 TDD não é sobre testes e sim sobre design

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

“ As suítes de testes não são tão fiéis ao que estudamos na literatura. 12

Slide 13

Slide 13 text

Conhecendo a família xUnit Agora que já entendemos um pouco de TDD, vamos falar sobre as suítes de testes. 2

Slide 14

Slide 14 text

Gerard Meszaros ● Estudou as estruturas das principais suítes de testes ● Lançou xUnit Test Patterns (2007) 14

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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(); }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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()); }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Test Doubles 3

Slide 23

Slide 23 text

Test Doubles ● Termo genérico para testes que substituem objetos de produção por: ○ Dummy; ○ Fake; ○ Stub; ○ Spy; ○ Mock; 23

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

25 public function depositMoney(float value, Account $account) { if ($value <= 0) { throw new \InvalidArgumentException(); } $account->subtractCurrentBalance( $value); }

Slide 26

Slide 26 text

26 /** * @expectedException InvalidArgumentException * */ public function testWhenValueLessOrEqualZeroShouldThrowAnException () { $account = $this->prophesize(Account::class); $deposit = new Deposit(); $deposit->depositMoney(-10, $account->reveal(); }

Slide 27

Slide 27 text

Fake ● Substituem objetos para criar atalhos ou formas mais fáceis de executar algo ● Exemplo: InMemoryTestDatabase 27

Slide 28

Slide 28 text

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; } }

Slide 29

Slide 29 text

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()); }

Slide 30

Slide 30 text

Stub ● Fornece respostas prontas programadas para chamadas específicas em momento de execução ● São utilizados na fase do setup 30

Slide 31

Slide 31 text

31 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw new \Exception('Invalid CPF'); } return $this->repository->create(); }

Slide 32

Slide 32 text

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()); }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

34 class AddressReposity { public function save(Address $address) { $this->entityManager->persist($address); $this->entityManager->flush(); } }

Slide 35

Slide 35 text

35 public function shouldSaveNewAddress() { $em = $prophet->prophesize('Doctrine\ORM\EntityManager' ); $address = new Address(); $addressRepository = new AddressRepository($em->reveal()); $addressRepository->save($address); $em->flush()->shouldHaveBeenCalled(); }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

37 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw new \Exception('Invalid CPF'); } return $this->repository->create(); }

Slide 38

Slide 38 text

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(); }

Slide 39

Slide 39 text

Características da técnica Alguns pontos a considerar sobre usar essa técnica 4

Slide 40

Slide 40 text

Design e feedback ● Graças ao feedback dos testes podemos melhorar o design de classes ● Evitando erros de OO e Bad Smell Codes 40

Slide 41

Slide 41 text

# 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(); }

Slide 42

Slide 42 text

# 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()); }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

45 Obrigado! Dúvidas? Twitter: @alonsoemacao Github: @viniciusalonso E-mail: [email protected]

Slide 46

Slide 46 text

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