Share
Embed
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
Transcript
46 slides
-
Olá! Eu sou Vinícius Alonso CTO na Letsgrow Sistemas Mentor
pelo Training Center 2 -
Tudo começa com TDD Para entender os Test Doubles precisamos
começar do básico. 1 -
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 -
7 public function testShouldBePalindrome () { $checker = new Checker();
$isPalindrome = $checker->isPalindrome('arara') $this->assertTrue($isPalindrome); } -
8 class Checker { public function isPalindrome(string $word) : bool
{ $originalWord = str_replace(' ', '', strtolower($word)); $reversed = str_replace(' ', '', strrev($originalWord)); return $originalWord == $reversed; } } -
Detalhes sobre TDD Algumas curiosidades sobre a prática que geralmente
não entendemos de cara quando começamos a estudar 9 -
“ As suítes de testes não são tão fiéis ao
que estudamos na literatura. 12 -
Conhecendo a família xUnit Agora que já entendemos um pouco
de TDD, vamos falar sobre as suítes de testes. 2 -
Gerard Meszaros • Estudou as estruturas das principais suítes de
testes • Lançou xUnit Test Patterns (2007) 14 -
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 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 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 -
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 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 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 -
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 -
Test Doubles • Termo genérico para testes que substituem objetos
de produção por: ◦ Dummy; ◦ Fake; ◦ Stub; ◦ Spy; ◦ Mock; 23 -
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 public function depositMoney(float value, Account $account) { if ($value
<= 0) { throw new \InvalidArgumentException(); } $account->subtractCurrentBalance( $value); } -
26 /** * @expectedException InvalidArgumentException * */ public function testWhenValueLessOrEqualZeroShouldThrowAnException
() { $account = $this->prophesize(Account::class); $deposit = new Deposit(); $deposit->depositMoney(-10, $account->reveal(); } -
Fake • Substituem objetos para criar atalhos ou formas mais
fáceis de executar algo • Exemplo: InMemoryTestDatabase 27 -
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 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()); } -
Stub • Fornece respostas prontas programadas para chamadas específicas em
momento de execução • São utilizados na fase do setup 30 -
31 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw
new \Exception('Invalid CPF'); } return $this->repository->create(); } -
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()); } -
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 class AddressReposity { public function save(Address $address) { $this->entityManager->persist($address);
$this->entityManager->flush(); } } -
35 public function shouldSaveNewAddress() { $em = $prophet->prophesize('Doctrine\ORM\EntityManager' ); $address
= new Address(); $addressRepository = new AddressRepository($em->reveal()); $addressRepository->save($address); $em->flush()->shouldHaveBeenCalled(); } -
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 public function create() { if (!$this->validator->isCPFValid( $this->data['cpf']) { throw
new \Exception('Invalid CPF'); } return $this->repository->create(); } -
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(); } -
Design e feedback • Graças ao feedback dos testes podemos
melhorar o design de classes • Evitando erros de OO e Bad Smell Codes 40 -
# 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(); } -
# 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()); } -
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 -
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