Skip to content

Instantly share code, notes, and snippets.

@maurobaraldi
Created July 14, 2018 07:22
Show Gist options
  • Save maurobaraldi/266435b877f6dd342b19280cdee8908e to your computer and use it in GitHub Desktop.
Save maurobaraldi/266435b877f6dd342b19280cdee8908e to your computer and use it in GitHub Desktop.
Apresentação de Mocks para seres humanospara Just Python.
<!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