Created
July 14, 2018 07:22
-
-
Save maurobaraldi/266435b877f6dd342b19280cdee8908e to your computer and use it in GitHub Desktop.
Apresentação de Mocks para seres humanospara Just Python.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="keywords" content="remark,remarkjs,markdown,slideshow,presentation" /> | |
<meta name="description" content="A simple, in-browser, markdown-driven slideshow tool." /> | |
<title>Remark</title> | |
<style> | |
@import url(https://fonts.googleapis.com/css?family=Droid+Serif); | |
@import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); | |
@import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic); | |
body { | |
font-family: 'Droid Serif'; | |
} | |
h1, h2, h3 { | |
font-family: 'Yanone Kaffeesatz'; | |
font-weight: 400; | |
margin-bottom: 0; | |
} | |
.remark-slide-content h1 { font-size: 3em; } | |
.remark-slide-content h2 { font-size: 2em; } | |
.remark-slide-content h3 { font-size: 1.6em; } | |
.footnote { | |
position: absolute; | |
bottom: 3em; | |
} | |
li p { line-height: 1.25em; } | |
.red { color: #fa0000; } | |
.large { font-size: 2em; } | |
a, a > code { | |
color: rgb(249, 38, 114); | |
text-decoration: none; | |
} | |
code { | |
background: #e7e8e2; | |
border-radius: 5px; | |
} | |
.remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; } | |
.remark-code-line-highlighted { background-color: #373832; } | |
.pull-left { | |
float: left; | |
width: 47%; | |
} | |
.pull-right { | |
float: right; | |
width: 47%; | |
} | |
.pull-right ~ p { | |
clear: both; | |
} | |
#slideshow .slide .content code { | |
font-size: 0.8em; | |
} | |
#slideshow .slide .content pre code { | |
font-size: 0.9em; | |
padding: 15px; | |
} | |
.inverse { | |
background: #272822; | |
color: #777872; | |
text-shadow: 0 0 20px #333; | |
} | |
.inverse h1, .inverse h2 { | |
color: #f3f3f3; | |
line-height: 0.8em; | |
} | |
/* Slide-specific styling */ | |
#slide-inverse .footnote { | |
bottom: 12px; | |
left: 20px; | |
} | |
#slide-how .slides { | |
font-size: 0.9em; | |
position: absolute; | |
top: 151px; | |
right: 140px; | |
} | |
#slide-how .slides h3 { | |
margin-top: 0.2em; | |
} | |
#slide-how .slides .first, #slide-how .slides .second { | |
padding: 1px 20px; | |
height: 90px; | |
width: 120px; | |
-moz-box-shadow: 0 0 10px #777; | |
-webkit-box-shadow: 0 0 10px #777; | |
box-shadow: 0 0 10px #777; | |
} | |
#slide-how .slides .first { | |
background: #fff; | |
position: absolute; | |
top: 20%; | |
left: 20%; | |
z-index: 1; | |
} | |
#slide-how .slides .second { | |
position: relative; | |
background: #fff; | |
z-index: 0; | |
} | |
/* Two-column layout */ | |
.left-column { | |
color: #777; | |
width: 20%; | |
height: 92%; | |
float: left; | |
} | |
.left-column h2:last-of-type, .left-column h3:last-child { | |
color: #000; | |
} | |
.right-column { | |
width: 75%; | |
float: right; | |
padding-top: 1em; | |
} | |
</style> | |
</head> | |
<body> | |
<textarea id="source"> | |
name: inverse | |
layout: true | |
class: center, middle, inverse | |
--- | |
# Mock Para Seres Humanos | |
Por Mauro Baraldi | |
.footnote[Esses slides estão disponíveis no meu [gist](https://gist.github.com/maurobaraldi/)] | |
--- | |
# Quem sou eu? | |
--- | |
layout: false | |
.left-column[ | |
### Mauro Navarro Baraldi | |
] | |
.right-column[ | |
- Programador desde o século passado. Desde de 2001 com Python. | |
- Developer na [FieldLink](http://www.fieldlink.me) | |
- Eu quebro sistemas e eu escrevo testes. | |
- https://github.com/maurobaraldi | |
] | |
--- | |
layout: false | |
.left-column[ | |
## O que são mocks? | |
] | |
.right-column[ | |
Em programação Orientada a Objetos, Objetos Mock são objetos que imitam o comportamento de um outro objeto real. | |
São criados para simular o comportamento de um objeto, que não é o objeto em foco, e usados no fluxo do teste para facilitar, ou mesmo, viabilizar o caso de teste. | |
] | |
--- | |
.left-column[ | |
## O que são mocks? | |
] | |
.right-column[ | |
Breve História sobre Mocks | |
- As primeiras idéias datam de 1999, a partir de debates de um grupo de discussão sobre arquitetura de software de Londres. | |
- Primeiras implementações vieram das comunidades Smalltalk e Java | |
- A motivação foi buscar uma forma de escrever testes unitários sem violar os principios da Orientação a Objetos e sem impactar no design da aplicação. | |
- Tem origens nos patterns **Composition** e **Chain of Responsibility** | |
] | |
--- | |
.left-column[ | |
## Vantagens e Desvanntagens | |
] | |
.right-column[ | |
Vantagens | |
- Reduz a sobrecarga. | |
- Sobrepoe restrições temporais sobre as funções. | |
- Isolamento de dependencias. | |
Desvantagens | |
- Diificuldade em manter os mocks, se a implementação mudar. | |
- Aumenta a complexidade dos testes. | |
- Pode comprometer a garantia de funcionamento dos testes. | |
] | |
--- | |
template: inverse | |
## Mocks em Python | |
--- | |
.left-column[ | |
## Mocks em Python | |
] | |
.right-column[ | |
Até Python 3.2 precisa ser instalado via Pip | |
`pip install mock` | |
A partir do 3.3 esta em `unittest.mock` <3. | |
] | |
--- | |
template: inverse | |
## Ok, vamos ao código. | |
--- | |
.left-column[ | |
## Exemplo 1 | |
] | |
.right-column[ | |
## Simular o comportamento de um método que não se pode prever o resultado: | |
<pre><code>from random import randint | |
def lancar_dados(qtd): | |
result = [randint(1, 6) for i in range(qtd)] | |
return sum(result) | |
</code></pre> | |
Testando o resultado do lançamento de um dado. | |
<pre><code>import unittest | |
class LancadorDeDadosTestCase(unittest.TestCase): | |
def test_lanca_um_dado(self): | |
self.assertTrue(lancar_dados(1) > 3) | |
</code></pre> | |
Resultado dos testes. | |
<pre><code>Traceback (most recent call last): | |
self.assertTrue(resultado > 3) | |
AssertionError: False is not true | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 1 | |
] | |
.right-column[ | |
## Solução: | |
Usar o recurso **return_value** para atribuir o resultado a uma função, que não a testada, para retornar o comportamento esperado e poder validar o teste. | |
`return_value`: Valor retornado quando o mock é chamado. | |
Por padrão, é um Mock (criado quando chamado). | |
] | |
--- | |
.left-column[ | |
## Exemplo 1 | |
] | |
.right-column[ | |
## Solução: | |
### Teste usando mock para manipular o resultado do método **randint** da lib **random**. | |
<pre><code>import unittest | |
from unittest.mock import patch | |
class LancadorDeDadosTestCase(unittest.TestCase): | |
@patch('random.randint') | |
def test_resultado_maior_que_tres(self, mock_randint): | |
mock_randint.return_value = 4 | |
self.assertTrue(lancar_dados(1) > 3) | |
@patch('random.randint') | |
def test_resultado_memor_que_tres(self, mock_randint): | |
mock_randint.return_value = 2 | |
self.assertTrue(lancar_dados(2) < 3) | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 2 | |
] | |
.right-column[ | |
## Como testar um método com uma data/hora específica: | |
<pre><code>from datetime import datetime | |
def hoje(): | |
return datetime.date.today() | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 2 | |
] | |
.right-column[ | |
## Problema: | |
Em CPython, não é possivel fazer o **monkeypatch** dos atributos da maioria das libs implemnetadas em C. | |
## Solução: | |
Lembra que o **return_value**, por padrão é um Mock, criado quando chamado? | |
Então, basta atribuir ao return value de um mock, um outro mock. | |
] | |
--- | |
.left-column[ | |
## Exemplo 2 | |
] | |
.right-column[ | |
## Como testar um método com uma data/hora específica: | |
<pre><code>from datetime import datetime | |
def hoje(): | |
return datetime.date.today() | |
</code></pre> | |
No teste, faz-se o **patch** da lib `datetime`, onde ela esta sendo usada, e não da standard lib. | |
Nesse caso, a classe de teste esta no mesmo arquivo que a função, por isso `__main__.datetime`. | |
<pre><code>from unittest.mock import patch, Mock | |
class HojeTestCase(unittest.TestCase): | |
@patch('__main__.datetime') | |
def test_retorna_hoje(self, datetime_mock): | |
mock = Mock(return_value=datetime.date(2015, 10, 6)) | |
datetime_mock.date.today = mock | |
self.assertEqual(hoje(), datetime.date(2015, 10, 6)) | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 3 | |
] | |
.right-column[ | |
## Quando o teste for executado com uma condicão específica, o mock executa uma função. | |
<pre><code>class ValidadorDeEmail(): | |
def validar(self, email): | |
if email.__contains__('@'): | |
if email.endswith('.com.br'): | |
return True | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 3 | |
] | |
.right-column[ | |
## Quando o teste for executado com uma condicão específica, o mock executa uma função. | |
<pre><code>from unittest.mock import patch, Mock | |
def side_effect(email): | |
if email.endswith('@teste.com.br'): | |
return False | |
return True | |
class ValidaEmailTestCase(unittest.TestCase): | |
def test_validar_email_br(self): | |
v = ValidadorDeEmail() | |
self.assertTrue(v.validar('eu@mesmo.com.br')) | |
def test_validar_email_br_menos_teste(self): | |
with patch.object(ValidadorDeEmail, 'validar_email', side_effect=side_effect): | |
v = ValidadorDeEmail() | |
self.assertTrue(v.validar('tu@mesmo.com.br')) | |
self.assertFalse(v.validar('outro@teste.com.br'))</code></pre> | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 4 | |
] | |
.right-column[ | |
## Garantir que o metodo tenha sido chamado com algun(s) ou nenhum parametros. | |
<pre><code>def nome_completo(nome, sobrenome=None): | |
return '{n} {s}'.format(n=nome, s=sobrenome) | |
</code></pre> | |
Nesse caso, o mock usa a funcionalidade de garantir que o método tenha sido chamado com o parametro específico. | |
<pre><code>import unittest | |
from unittest.mock import patch, Mock | |
class NomeTestCase(unittest.TestCase): | |
@patch('__main__.nome_completo') | |
def test_retornar_nome_completo(self, mock_nome): | |
nome = nome_completo('João', 'Silva') | |
mock_nome.assert_called_with('João', 'Silva') | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 5 | |
] | |
.right-column[ | |
## Testar metodos que usam funções builtin, como o **open()**. | |
<pre><code>import json | |
def carrega_json(arquivo): | |
try: | |
return json.loads(open(arquivo).read()) | |
except (IOError, ValueError): | |
return "" | |
</code></pre> | |
Como o **open** é um método builtin, ele deve ser chamado a partir do arquivo que estiver sendo chamado. | |
<pre><code>import unittest | |
from unittest.mock import patch, Mock | |
class CarregaJSONTestCase(unittest.TestCase): | |
@patch('__main__.open') | |
def test_retorna_json_valido(self, mock_open): | |
arquivo = 'um_nome_qualquer.json' | |
mock_file = Mock() | |
mock_file.read.return_value = '{"chave": "valor"}' | |
mock_open.return_value = mock_file | |
self.assertEqual({"chave": "valor"}, carrega_json(arquivo)) | |
</code></pre> | |
] | |
--- | |
template: inverse | |
## Ok, mas e o tratamento de erro? | |
--- | |
.left-column[ | |
## Exemplo 5 | |
] | |
.right-column[ | |
## Qunado o arquivo não existe (**IOError**). | |
Nesse caso, não importa que o arquivo exista, pois ele nao vai ser usado. | |
<pre><code>import unittest | |
from unittest.mock import patch, Mock | |
class CarregaJSONTestCase(unittest.TestCase): | |
@patch('__main__.open') | |
def test_arquivo_inesistente(self, mock_open): | |
arquivo = 'arquivo_nao_exite.json' | |
mock_open.side_effect = IOError | |
self.assertEqual("", carrega_json(arquivo)) | |
</code></pre> | |
] | |
--- | |
.left-column[ | |
## Exemplo 5 | |
] | |
.right-column[ | |
## Qunado os dados do arquivo estão inconsistentes (**ValueError**) | |
<pre><code>import unittest | |
from unittest.mock import patch, Mock | |
class CarregaJSONTestCase(unittest.TestCase): | |
@patch('__main__.open') | |
@patch('json.loads') | |
def test_arquivo_inconsistente(self, mock_loads, mock_open): | |
arquivo = 'outro_arquivo.json' | |
mock_file = Mock() | |
mock_file.read.return_value = '{"chave": "valor"}' | |
mock_open.return_value = mock_file | |
mock_loads.side_effect = ValueError | |
self.assertEqual("", carrega_json(arquivo)) | |
</code></pre> | |
] | |
--- | |
# Dicas | |
- Avalie a necessidade de usar mock. As vezes, rever o código e encontrar uma solução mais simples pode ser melhor. | |
- Mocks, geralmente são uma boa opção para simular comportamento de componentes que integram com seu código (bibliotecas externas). | |
- A prática leva a experiência. | |
# Mais sobre mocks | |
- [Documentação Oficial - Python 3](https://docs.python.org/3.3/library/unittest.mock.html) | |
- [StackOverflow: Categorias Python + Mock](https://stackoverflow.com/questions/tagged/python+mocking?sort=frequent&pageSize=50) | |
--- | |
name: last-page | |
template: inverse | |
## That's all folks (for now)! | |
[GitHub](http://github.com/maurobaraldi/) - [Twitter](http://twitter.com/mauro_baraldi/) - [SlideShare](https://pt.slideshare.net/mauro.baraldi) - [Linkedin](https://www.linkedin.com/in/maurobaraldi/) | |
</textarea> | |
<script src="https://remarkjs.com/downloads/remark-latest.min.js"></script> | |
<script> | |
var hljs = remark.highlighter.engine; | |
</script> | |
<script src="remark.language.js"></script> | |
<script> | |
var slideshow = remark.create({ | |
highlightStyle: 'monokai', | |
highlightLanguage: 'remark', | |
highlightLines: true | |
}) ; | |
</script> | |
<script> | |
var _gaq = _gaq || []; | |
_gaq.push(['_setAccount', 'UA-44561333-1']); | |
_gaq.push(['_trackPageview']); | |
(function() { | |
var ga = document.createElement('script'); | |
ga.src = 'https://ssl.google-analytics.com/ga.js'; | |
var s = document.scripts[0]; | |
s.parentNode.insertBefore(ga, s); | |
}()); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment