TDD com Mock e Orientação a Objetos


Bernardo Fontes


Serra - ES

29 de Abril de 2017

Só para dizer um oi!

twitter.com/bbfontes

github.com/berinhard

pessoas.cc

bernardoxhc@gmail.com

berinhard.github.io/talks/

# Roteiro - ## Básicos de Orientação a Objetos - ## Test-driven Development (TDD) - ## TDD Com Mocks - ## Dúvidas
## Básicos de Orientação a Objetos - ### *Não é fazer getter e setter*
# Orientação a Objetos? - ## Estratégia de **Design** - ## Definição de **Estado** - ## **Encapsulamento** - ## Colaboração com troca de **Mensagens**
## E se cerveja fosse um objeto? - ### **Quantidade** seria um **atributo** - ### **Gelada** seria um **estado** - ### **Ser servida** seria um **comportamento**
## Exemplo Python ```python class Cerveja(object): def __init__(self, temperatura): self.temperatura = temperatura self.quantidade = 600 def esta_gelada(self): return self.temperatura < 5 #> def servir(self, quantidade): if self.quantidade < quantidade: #> quantidade = self.quantidade self.quantidade -= quantidade ############################# In : cerveja = Cerveja(temperatura=-2) In : cerveja.esta_gelada() Out: True In : cerveja.servir(150) In : cerveja.quantidade Out: 450 ```
## Exemplo do Ingresso ```python from datetime import date class Ticket(object): def __init__(self, buyer, price, schedule_date): self.buyer = buyer self.price = price self.schedule_date = schedule_date def has_expired(self): return date.today() > self.schedule_date ############################# ticket = Ticket("Bernardo", 10, date(2015, 10, 10)) if not ticket.has_expired(): #processa ticket válido ```
## Pensando o Design com OO - ### **S** - ### **O** - ### **L** - ### **I** - ### **D**
## Pensando o Design com OO - ### **S**ingle Responsibility Principle - ### **O**pen Closed Principle - ### **L**iskov Substitution Principle - ### **I**nterface Segregation Principle - ### **D**ependency Inversion Principle
## Single Responsibility Principle - ### Nunca deve existir mais de **uma razão para modificar** algo em uma classe.
## Open Closed Principle - ### Toda classe deve estar **aberta para extensão**, mas **fechada para modificação**
## Referências

Object Mentor

## Test-driven Development (TDD)
## Primeiro o teste ```python class FizzbuzzTests(TestCase): def test_3_returns_fizz(self): self.assertEqual("fizz", fizzbuzz(3)) ```
## Primeira implementação ```python def fizzbuzz(number): return "fizz" class FizzbuzzTests(TestCase): def test_3_returns_fizz(self): self.assertEqual("fizz", fizzbuzz(3)) ```

Coding Dojo!!!

## Processo de Design
## Design Emergente - Refatoração - Remoção de Acoplamentos - Duplicidade
## **Exploração** e **Descoberta** - ## cansativo...
## Sem Evidência do Design ```python class TestTicketManager(TestCase): def test_expire_ticket(self): ticket = Ticket.objects.create(id=30) self.assertFalse(ticket.expired) manager = TicketManager() manager.expire_ticket(ticket_id=30) ticket = Ticket.objects.get(id=30) self.assertTrue(ticket.expired) ```
## Difícil de começar a testar no alto nível - ### **Infra** vs **Domínio**
## Referências

Curso TDD - J. B. Rainsberger

## TDD com Mock
## Mock - ### **Simulam** funcionamento de objetos - ### Respeitam **API** dos objetos - ### Viabilizam maneira de fazer **asserções**
## Foco do Domíno nas **Mensagens** - ### Menos **Estado** e mais **Colaboração**
## Implementação de Testes Top-Down
## Need-Driven Development - ### Código **criado** só se **necessário**
## TODO List - ### Recuperar Ingresso - ### Expirá-lo - ### Notificar Usuário
## Entry Point ```python class TestTicketManager(TestCase): def test_expire_ticket(self): manager = TicketManager() manager.expire_ticket(ticket_id=30) ```
## Recuperar Ingresso ```python from mock import Mock class TestTicketManager(TestCase): def test_expire_ticket(self): tickets_repository = Mock(TicketsRepository) tickets_repository.get_by_id.return_value = Ticket() manager = TicketManager(tickets_repository) manager.expire_ticket(ticket_id=30) tickets_repository.get_by_id.assert_called_once_with(30) ```
```python class TicketManager(object): def __init__(self, tickets_repository): self.repository = tickets_repository def expire_ticket(self, ticket_id): self.repository.get_by_id(ticket_id) class Ticket(object): pass class TicketsRepository(object): def get_by_id(self, id): raise NotImplementedError ```
## Expirar Ingresso ```python from mock import Mock class TestTicketManager(TestCase): def test_expire_ticket(self): ticket = Mock(Ticket) tickets_repository = Mock(TicketsRepository) tickets_repository.get_by_id.return_value = ticket manager = TicketManager(tickets_repository) manager.expire_ticket(ticket_id=30) tickets_repository.get_by_id.assert_called_once_with(30) ticket.expire.assert_called_once_with() ```
```python class TicketManager(object): def __init__(self, tickets_repository): self.repository = tickets_repository def expire_ticket(self, ticket_id): ticket = self.repository.get_by_id(ticket_id) ticket.expire() class Ticket(object): def expire(self): raise NotImplementedError class TicketsRepository(object): def get_by_id(self, id): raise NotImplementedError ```
## Notificar Usuário ```python from mock import Mock class TestTicketManager(TestCase): def test_expire_ticket(self): ticket = Mock(Ticket) tickets_repository = Mock(TicketsRepository) tickets_repository.get_by_id.return_value = ticket notificator = Mock(TicketNotificator) manager = TicketManager(tickets_repository, notificator) manager.expire_ticket(ticket_id=30) tickets_repository.get_by_id.assert_called_once_with(30) ticket.expire.assert_called_once_with() notificator.notify_expired.assert_called_once_with(ticket) ```
```python class TicketManager(object): def __init__(self, tickets_repository, notificator): self.repository = tickets_repository self.notificator = notificator def expire_ticket(self, ticket_id): ticket = self.repository.get_by_id(ticket_id) ticket.expire() self.notificator.notify_expired(ticket) class Ticket(object): def expire(self): raise NotImplementedError class TicketsRepository(object): def get_by_id(self, id): raise NotImplementedError class TicketNotificator(object): def notify_expired(self, ticket): raise NotImplementedError ```
## Respeito ao contrato é **tudo** - ### Comportamentos de **Entrada** - ### Comportamentos de **Saída**
## Novos testes para garantir **contrato** e **funcionamento unitário** dos componentes
## Referências

Mock Roles, not Objects

GOOS Guided By Tests

## Dúvidas?

Python Sudeste 2017

Python Sudeste 2018 no ES ???

Obrigado!


Bernardo Fontes


twitter.com/bbfontes

github.com/berinhard

pessoas.cc

bernardoxhc@gmail.com