<!doctype html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
< title > Filosofando sobre Tests< / title >
< meta name = "description" content = "A framework for easily creating beautiful presentations using HTML" >
< meta name = "author" content = "Hakim El Hattab" >
< meta name = "apple-mobile-web-app-capable" content = "yes" >
< meta name = "apple-mobile-web-app-status-bar-style" content = "black-translucent" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui" >
< link rel = "stylesheet" href = "reveal.js/css/reveal.css" >
< link rel = "stylesheet" href = "reveal.js/css/theme/night.css" id = "theme" >
<!-- Code syntax highlighting -->
< link rel = "stylesheet" href = "reveal.js/lib/css/zenburn.css" >
<!-- Printing and PDF exports -->
< script >
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
< / script >
<!-- [if lt IE 9]>
< script src = "lib/js/html5shiv.js" > < / script >
<![endif]-->
< style type = "text/css" media = "screen" >
.happy {
color: yellow;
}
.reveal section img {
border: none;
}
.reveal ul.empty {
list-style: none outside;
}
li {
display: block;
}
.semi-opaque {
background-color: rgba(0, 0, 0, 0.7);
}
< / style >
< / head >
< body >
< div class = "reveal" >
< div class = "slides" >
< section >
< section data-background = "_images/tdd-kentbeck-book.jpg" data-header >
< h1 class = "semi-opaque" > Filosofando sobre Testes< / h1 >
< / section >
< / section >
< section >
< section >
< img src = "_images/avatar-20170726.png" alt = "Me" style = "float:left;width:200px;" class = "no-border" >
< div >
< ul class = "empty" >
< li > Júlio Biason< / li >
< li > < a href = "https://functional.cafe/@juliobiason" > https://functional.cafe/@juliobiason< / a > < / li >
< li > < a href = "https://t.me/juliobiason" > https://t.me/juliobiason< / a > < / li >
< li > julio.biason@pm.me< / li >
< li > < a href = "https://presentations.juliobiason.me" > https://presentations.juliobiason.me< / a > < / li >
< / ul >
< / div >
< / section >
< section >
< img src = "_images/start-a-fight.jpg" alt = "Eu faço perguntas em reuniões que eu não sei nada e reunião explode; não é de propósito" class = 'stretch' / >
< aside class = "notes" >
Eu sou famoso (ou era) por entrar eu reuniões, fazer uma
pergunta e a reunião explodir em discussões (úteis, diga-se
de passagem). Mas eu não fazia isso de propósito.
< / aside >
< / section >
< section >
< img src = "_images/filho-do-capeta.jpg" alt = "... mas hoje eu vim botar os filho dos outro no go horse." class = 'stretch' / >
< aside class = "notes" >
... mas hoje eu vim "botá os fio dos otro no Go Horse, fazê
eles escrevê menos teste"
< / aside >
< / section >
< section >
< h2 > < span style = "color:red" > WARNING< / span > < / h2 >
< img src = "_images/patriarch.jpg" alt = "... e eu sou meio não-ortodoxo sobre testes." / >
< / section >
< aside class = "notes" >
Um aviso: muitas das coisas que eu vou discutir aqui,
não são as coisas que normalmente são discutidas quando
se fala sobre testes.
Fiquem avisados que muitas pessoas não concordam comigo.
< / aside >
< / section >
< section >
< h2 > Agenda:< / h2 >
< ul >
< li > TDD Kent Beck Style.< / li >
< li > Fast Test, Slow Tests.< / li >
< li > Explosão de testes lentos.< / li >
< li > Coverage.< / li >
< / ul >
< / section >
< section >
< section >
< img src = "_images/tdd-where-it-went-wrong.png" alt = "TDD: Where it went wrong" class = "stretch" / >
< p > Ian Cooper: < a href = "https://vimeo.com/68375232" > "TDD, where did it all go wrong"< / a > < / p >
< aside class = "notes" >
Boa parte das coisas que eu vou discutir aqui foram
influenciadas por esse video do Ian Cooper, em que
ele discute coisas sobre TDD no estilo discutido
por Kent Beck, que foi quem escreveu o livro que
trouxe o termo "TDD" para as massas.
< / aside >
< / section >
< section >
< h2 > TDD< / h2 >
< p > Kent Beck:< / p >
< ul >
< li > "Run in isolation", nothing more, nothing less.< / li >
< li > "Avoid testing implementation details, test behaviours"< / li >
< / ul >
< aside class = "notes" >
No seu livro, Kent Beck diz que "unit tests"
significam "rodam de forma isolada, nada mais, nada
menos". Esse é o _único_ significado de "unit
test"; "testes unitários" e não "testes de
unidade", na tradução correta.
Outra coisa que Kent Beck fala no seu livro é que
devem ser testados comportamentos, não
implementação. Pensem um pouco sobre isso: Se
estamos testando comportamento ao invés de
implementações, como é que podemos escrever um
teste de um objeto ou uma função? O que sabemos é
que uma aplicação tem um protocolo de entrada de
dados (terminal, web, GUI) e um protocolo de saída
(provavelmente o mesmo que o de entrada, no sentido
contrário) e como essa aplicação tem que se
comportar com relação à entrada.
< / aside >
< / section >
< section >
< h2 > TDD< / h2 >
< p >
Discussões como "qual a unidade a ser testada" é
que geraram coisas como BDD e ATDD (Acceptance
Test-Driven Development).
< / p >
< aside class = "notes" >
Se vocês pensarem, essa questão de como uma
aplicação deve se comportar é exatamente que fez
com o BDD (behaviour driven development) e ATDD
surgiram -- e eles não são diferentes do que o que
Kent Beck quis dizer com o TDD.
< / aside >
< / section >
< section >
< h2 > TDD< / h2 >
< p > Reddit: < a href = "https://www.reddit.com/r/django/comments/5bearg/should_i_write_unit_tests_for_djangos_built_in/" target = "_blank" > Devo escrever testes para a validação interna do Django?< / a > < / p >
< aside class = "notes" >
Uma boa pergunta uma vez surgiu no Reddit do Django
foi "Devo eu criar um teste para um campo inteiro
quando o Django já verifica que um campo inteiro
não possa ter caracteres?"
< / aside >
< h1 class = "fragment" > SIM!< / h1 >
< aside class = "notes" >
A questão do teste não é relacionado com o tipo de
campo, mas o _significado_ do campo.
Exemplo, se o campo for "ano de nascimento",
obviamente "AAAA" não é um ano válido, e o Django
vai barrar isso se eu definir o campo como inteiro.
Mas eu poderia usar um campo caracter (por um
motivo qualquer) e eu teria que testar por ser um
inteiro.
Mas porque eu usaria um caracter ao invés de um
inteiro?
"Teste comportamento, não implementação".
< / aside >
< / section >
< section >
< h2 > TDD< / h2 >
< p > Nossos testes End-to-End.< / p >
< aside class = "notes" >
Explicação do que aconteceu com os testes do
gerenciador de alertas.
< / aside >
< / section >
< / section >
< section >
< section >
< img src = "_images/tdd-fast-test-slow-test.png" alt = "" class = "stretch" / >
< p > Gary Bernhardt: < a href = "https://www.youtube.com/watch?v=RAxiiRPHS9k" target = "_blank" > Fast Test, Slow Test< / a > < / p >
< / section >
< section >
< img src = "_images/tdd-fast-test-slow-test-2.png" alt = "" / class = "stretch" >
< / section >
< section >
< h2 > Fast Test, Slow Test< / h2 >
< ul >
< li > Testar modelos.< / li >
< li > Testar views.< / li >
< li > Testar controllers.< / li >
< / ul >
< p class = "fragment" > ... soa familiar?< / p >
< / section >
< section >
< h2 > Fast Test, Slow Test< / h2 >
< ul >
< li > Desenvolvedores podem testar, rapidamente, suas alterações.< / li >
< li > Não significa que testes de integração não são necessários,
mas estes podem ser rodados no CI.< / li >
< / ul >
< p class = "fragment" > ... continua soando familiar?< / p >
< / section >
< section >
< h2 > Fast Test, Slow Test< / h2 >
< p > Qual o valor desse teste para o meu projeto?< / p >
< / section >
< section >
< h2 > Fast Test, Slow Test< / h2 >
< p > Se eu estou escrevendo testes do meu modelo sem
que seja integrado com o meu controller, eu estou
testando o que?< / p >
< p class = "fragment" > < u > Aderência a arquitetura do projeto< / u > .< / p >
< / section >
< section >
< h2 > Fast Test, Slow Test< / h2 >
< p > Se o teste que verifica a funcionalidade para
o usuário (integração) produz mais valor
que o teste que garante a aderência do projeto à
uma arquitetura, qual teste deve ser rodado
primariamente?< / p >
< / section >
< / section >
< section >
< section >
< h2 > A Explosão dos Testes Lentos< / h2 >
< / section >
< section >
< h2 > Explosão de Testes Lentos< / h2 >
< p > "Testes de integração são lentos, então eu tenho que rodar-los
menos ou ter menos desses testes."< / p >
< / section >
< section >
< h2 > Explosão de Testes Lentos< / h2 >
< ul >
< li > Rode apenas o teste da alteração que está sendo feita.< / li >
< li class = "fragment" > Rode os testes relacionados.< / li >
< li class = "fragment" > Deixe todos os demais testes para o CI.< / li >
< / ul >
< / section >
< / section >
< section >
< section >
< h2 > Coverage< / h2 >
< / section >
< section >
< h2 > Coverage< / h2 >
< p > Se os testes indicam quais funcionalidades devem estar
presentes...< / p >
< p class = "fragment" > ... coverage vai indicar quais partes
de código não tem nada a ver com as funcionalidades que
vão ser entregues.< / p >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
class Client:
def __init__(self, name):
self.name = name
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< p > Novo requisito: "somente aceitar nomes válidos, que tem 2
palavras ou mais."< / p >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
def _multiple_names(name):
split_names = name.split(' ')
return len(split_names) > 1
def _validate_name(name):
if not _multiple_names(name):
raise Exception("Invalid name")
return name
class Client:
def __init__(self, name):
self.name = _validate_name(name)
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
import pytest
def test_single_name():
assert not _multiple_names('Cher')
def test_multiple_name():
assert _multiple_names('Julio Biason')
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
def test_valid_name():
_validate_name('Julio Biason')
def test_invalid_name():
with pytest.raises(Exception):
_validate_name('Cher')
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
def test_client_error():
with pytest.raises(Exception):
Client(name='Cher')
def test_client():
Client(name='Julio Biason')
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code data-trim >
$ pytest client.py
==== test session starts ====
rootdir: /home/jbiason/unitt, inifile:
collected 6 items
client.py ......
==== 6 passed in 0.03 seconds ====
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code data-trim >
$ pytest --cov=client client.py
==== test session starts ====
plugins: cov-2.4.0
collected 6 items
client.py ......
---- coverage: platform linux, python 3.4.3-final-0 ----
Name Stmts Miss Cover
-------------------------------
client.py 25 0 100%
==== 6 passed in 0.11 seconds ====
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< p > "Não podemos perder a Cher, a Xuxa, a Madonna, a Björk e o String como clientes!"< / p >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code class = "python" data-trim >
class Client:
def __init__(self, name):
self.name = name
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code data-trim >
==== FAILURES ====
____ test_client_error ____
def test_client_error():
with pytest.raises(Exception):
> Client(name='Cher')
E Failed: DID NOT RAISE < class 'Exception'>
client.py:37: Failed
==== 1 failed, 5 passed in 0.63 seconds ====
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code data-trim >
$ pytest client.py
==== test session starts ====
rootdir: /home/jbiason/unitt, inifile:
plugins: cov-2.4.0
collected 6 items
client.py ......
==== 6 passed in 0.03 seconds ====
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< pre > < code data-trim >
$ pytest --cov=client client.py
==== test session starts ====
rootdir: /home/jbiason/unitt, inifile:
plugins: cov-2.4.0
collected 6 items
client.py ......
---- coverage: platform linux, python 3.4.3-final-0 ----
Name Stmts Miss Cover
-------------------------------
client.py 24 0 100%
==== 6 passed in 0.12 seconds ====
< / code > < / pre >
< / section >
< section >
< h2 > Coverage< / h2 >
< h3 > Exemplo< / h3 >
< p > Encontre o erro.< / p >
< / section >
< section >
< h2 > Coverage< / h2 >
< p > 100% de cobertura de testes é possível.< / p >
< p class = "fragment" > ... desde que esteja disposto a escrever
código que testa funcionalidades do usuário e não conformidade
de arquitetura e código não coberto seja apagado.< / p >
< / section >
< / section >
< section data-background = '_images/thats-all-folks.jpg' >
< section >
< h1 class = "fragment semi-opaque" > Perguntas?< / h1 >
< div class = "fragment semi-opaque" >
< ul >
< li > Júlio Biason< / li >
< li > https://functional.cafe/@juliobiason< / li >
< li > julio.biason@pm.me< / li >
< li > < a href = "https://presentations.juliobiason.me" > https://presentations.juliobiason.me< / a > < / li >
< / ul >
< / div >
< / section >
< / section >
< / div >
< / div >
< script src = "reveal.js/lib/js/head.min.js" > < / script >
< script src = "reveal.js/js/reveal.js" > < / script >
< script >
// Full list of configuration options available at:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
// showNotes: true,
transition: 'slide', // none/fade/slide/convex/concave/zoom
// Optional reveal.js plugins
dependencies: [
{ src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: 'reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: 'reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: 'reveal.js/plugin/zoom-js/zoom.js', async: true },
{ src: 'reveal.js/plugin/notes/notes.js', async: true }
]
});
< / script >
< / body >
< / html >