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