You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
413 lines
25 KiB
413 lines
25 KiB
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
<meta http-equiv="content-type" content="text/html; charset=utf-8"> |
|
|
|
<!-- Enable responsiveness on mobile devices--> |
|
<!-- viewport-fit=cover is to support iPhone X rounded corners and notch in landscape--> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, viewport-fit=cover"> |
|
|
|
<title>Julio Biason .Me 4.3</title> |
|
|
|
<!-- CSS --> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/print.css" media="print"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/poole.css"> |
|
<link rel="stylesheet" href="https://blog.juliobiason.me/hyde.css"> |
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=PT+Sans:400,400italic,700|Abril+Fatface"> |
|
|
|
|
|
|
|
|
|
|
|
</head> |
|
|
|
<body class=" "> |
|
|
|
<div class="sidebar"> |
|
<div class="container sidebar-sticky"> |
|
<div class="sidebar-about"> |
|
|
|
<a href="https://blog.juliobiason.me"><h1>Julio Biason .Me 4.3</h1></a> |
|
|
|
<p class="lead">Old school dev living in a 2.0 dev world</p> |
|
|
|
|
|
</div> |
|
|
|
<ul class="sidebar-nav"> |
|
|
|
|
|
<li class="sidebar-nav-item"><a href="/">English</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt">Português</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/tags">Tags (EN)</a></li> |
|
|
|
<li class="sidebar-nav-item"><a href="/pt/tags">Tags (PT)</a></li> |
|
|
|
|
|
</ul> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="content container"> |
|
|
|
<div class="post"> |
|
<h1 class="post-title">O "Unit" de "Unit Tests"</h1> |
|
<span class="post-date"> |
|
2016-11-23 |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/unit/">#unit</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/unit-tests/">#unit tests</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/tests/">#tests</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/kent-beck/">#kent beck</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/tchelinux/">#tchelinux</a> |
|
|
|
<a href="https://blog.juliobiason.me/pt/tags/companion-post/">#companion post</a> |
|
|
|
</span> |
|
<p>Existem vários artigos sobre os "testes de unidade" e alguns até |
|
falando de "a unidade dos testes"; todos estes estão errados e é |
|
preciso parar de falar dessa forma.</p> |
|
<span id="continue-reading"></span><div style="border:1px solid grey; margin:7px; padding: 7px"> |
|
<p>(Este post é relacionado com a apresentação que eu fiz no dia 19 de |
|
novembro no TchêLinux. Os slides podem ser encontrados <a href="http://presentations.juliobiason.net/unit-in-unittest.html#">na área de |
|
apresentações</a>.</p> |
|
|
|
</div> |
|
<p>Boa parte do conteúdo que eu vou comentar aqui é relacionado com o vídeo de Ian |
|
Cooper <a href="https://vimeo.com/68375232">TDD, where did it all go wrong</a>. Quando eu |
|
vi o vídeo, imediatamente eu comecei a relacionar os comentários dele sobre |
|
TDD, na versão original de Kent Beck (o author do livro <a href="https://www.goodreads.com/book/show/387190.Test_Driven_Development?from_search=true">Test Driven |
|
Development By Example</a>, |
|
que foi o responsável por "reativar" a discussão sobre testes) com uma |
|
experiência pessoal trabalhando com TDD no mundo embedded.</p> |
|
<p>Antes que eu entre na discussão sobre o que é TDD de verdade, o que Kent Beck |
|
quis dizer com o seu "unit test" e essa experiência pessoal, é preciso fazer |
|
duas perguntas:</p> |
|
<ul> |
|
<li>Quem já usou a expressão "testes de unidade"?</li> |
|
<li>Quem já discutiu qual era a "unidade" de "testes de unidade"?</li> |
|
</ul> |
|
<p>Existem várias discussões sobre isso, incluíndo uma recente (com relação à |
|
este post, no entanto) sobre <a href="http://www.marclittlemore.com/how-to-write-high-quality-unit-tests/">como escrever testes de alta qualidade</a> (em |
|
inglês), em que o autor comenta</p> |
|
<p>Se você estiver programando com orientação à objetos, a sua unidade devem |
|
ser as classes; se estiver programando de forma funcional, a sua unidade |
|
são as funções.</p> |
|
<p>O problema é que essa visão, além de incorreta com relação ao que Kent Beck |
|
prega no seu livro (pelo menos, segundo Ian Cooper), ela gera mais problemas |
|
do que soluções.</p> |
|
<h2 id="um-exemplo-errado-de-unidade">Um exemplo errado de unidade</h2> |
|
<p>Vamos pegar a ideia geral de "unidades de teste" e aplicar num pequeno caso. |
|
Digamos que você tenha a seguinte classe para manter seus clientes:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Client</span><span style="color:#eff1f5;">: |
|
</span><span> </span><span style="color:#b48ead;">def </span><span style="color:#96b5b4;">__init__</span><span>(</span><span style="color:#bf616a;">self</span><span>, </span><span style="color:#bf616a;">name</span><span>): |
|
</span><span> </span><span style="color:#bf616a;">self</span><span>.name = name |
|
</span></code></pre> |
|
<p>Uma classe simples, onde você tem o nome do cliente (obviamente, num caso real, |
|
a classe teria mais propriedades e mais uma pilha de métodos, mas para fins de |
|
exemplo, vamos manter simples e curto.)</p> |
|
<p>Conforme o produto evolui, surge o seguinte requerimento:</p> |
|
<blockquote> |
|
<p>Não podem ser cadastrados clientes com apenas um nome.</p> |
|
</blockquote> |
|
<p>Nesse ponto, você resolve aplicar os princípios <a href="https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)">SOLID</a>, aplicando o |
|
"Princípio da Responsabilidade Única" e gera a seguinte alteração:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">_multiple_names</span><span>(</span><span style="color:#bf616a;">name</span><span>): |
|
</span><span> split_names = name.</span><span style="color:#bf616a;">split</span><span>(' ') |
|
</span><span> </span><span style="color:#b48ead;">return </span><span style="color:#96b5b4;">len</span><span>(split_names) > </span><span style="color:#d08770;">1 |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">_validate_name</span><span>(</span><span style="color:#bf616a;">name</span><span>): |
|
</span><span> </span><span style="color:#b48ead;">if </span><span>not </span><span style="color:#bf616a;">_multiple_names</span><span>(name): |
|
</span><span> </span><span style="color:#b48ead;">raise </span><span style="color:#bf616a;">Exception</span><span>("</span><span style="color:#a3be8c;">Invalid name</span><span>") |
|
</span><span> </span><span style="color:#b48ead;">return </span><span>name |
|
</span><span> |
|
</span><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Client</span><span style="color:#eff1f5;">: |
|
</span><span> </span><span style="color:#b48ead;">def </span><span style="color:#96b5b4;">__init__</span><span>(</span><span style="color:#bf616a;">self</span><span>, </span><span style="color:#bf616a;">name</span><span>): |
|
</span><span> </span><span style="color:#bf616a;">self</span><span>.name = </span><span style="color:#bf616a;">_validate_name</span><span>(name) |
|
</span></code></pre> |
|
<p>Isso obedece boa parte do SOLID e do SRP porque a função <code>_validade_name</code> |
|
tem uma única responsabilidade, dizer se o nome é valido ou não (e o "bacana" é |
|
que, se surgir uma nova condição definindo o que é um nome válido, basta |
|
adicionar nessa função a chamada do novo validador); ainda, <code>_multiple_names</code> |
|
tem uma única responsabilidade, dizer se o nome é composto por múltiplos nomes |
|
ou um só.</p> |
|
<p>Até aqui, não falamos nada de teste. Então vamos lá: você criou estes três |
|
componentes e agora quer escrever os testes. Como é preciso escrever um |
|
teste por classe e por função, você escreve essa excelente suíte de testes:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">import </span><span>pytest |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_single_name</span><span>(): |
|
</span><span> </span><span style="color:#b48ead;">assert </span><span>not </span><span style="color:#bf616a;">_multiple_names</span><span>('</span><span style="color:#a3be8c;">Cher</span><span>') |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_multiple_name</span><span>(): |
|
</span><span> </span><span style="color:#b48ead;">assert </span><span style="color:#bf616a;">_multiple_names</span><span>('</span><span style="color:#a3be8c;">Julio Biason</span><span>') |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_valid_name</span><span>(): |
|
</span><span> </span><span style="color:#bf616a;">_validate_name</span><span>('</span><span style="color:#a3be8c;">Julio Biason</span><span>') |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_invalid_name</span><span>(): |
|
</span><span> </span><span style="color:#b48ead;">with </span><span>pytest.</span><span style="color:#bf616a;">raises</span><span>(Exception): |
|
</span><span> </span><span style="color:#bf616a;">_validate_name</span><span>('</span><span style="color:#a3be8c;">Cher</span><span>') |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_client_error</span><span>(): |
|
</span><span> </span><span style="color:#b48ead;">with </span><span>pytest.</span><span style="color:#bf616a;">raises</span><span>(Exception): |
|
</span><span> </span><span style="color:#bf616a;">Client</span><span>(</span><span style="color:#bf616a;">name</span><span>='</span><span style="color:#a3be8c;">Cher</span><span>') |
|
</span><span> |
|
</span><span style="color:#b48ead;">def </span><span style="color:#8fa1b3;">test_client</span><span>(): |
|
</span><span> </span><span style="color:#bf616a;">Client</span><span>(</span><span style="color:#bf616a;">name</span><span>='</span><span style="color:#a3be8c;">Julio Biason</span><span>') |
|
</span></code></pre> |
|
<div style="border:1px solid grey; margin:7px; padding: 7px"> |
|
<p>Sim, o certo seria escrever os testes antes do código, mas isso não é o que |
|
acontece; afinal de contas, como é que você teria um teste para a sua classe se |
|
você não escreveu a classe ainda -- e isso é mais uma prova que a pergunta de |
|
"unidade de teste" está errada.</p> |
|
|
|
</div> |
|
<p>Uma vez escritos os testes, você roda os testes e... </p> |
|
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ pytest client.py |
|
</span><span>==== test session starts ==== |
|
</span><span>rootdir: /home/jbiason/unitt, inifile: |
|
</span><span>collected 6 items |
|
</span><span> |
|
</span><span>client.py ...... |
|
</span><span> |
|
</span><span>==== 6 passed in 0.03 seconds ==== |
|
</span></code></pre> |
|
<p>Excelente, todos os testes passaram; e como está a cobertura de testes (que é |
|
algo que todo mundo que fala em "unidades de teste" se preocupa, afinal de |
|
contas, você precisa garantir que tem testes para todas as classes e todas as |
|
funções, certo?) </p> |
|
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ pytest --cov=client client.py |
|
</span><span>==== test session starts ==== |
|
</span><span>plugins: cov-2.4.0 |
|
</span><span>collected 6 items |
|
</span><span> |
|
</span><span>client.py ...... |
|
</span><span> |
|
</span><span>---- coverage: platform linux, python 3.4.3-final-0 ---- |
|
</span><span>Name Stmts Miss Cover |
|
</span><span>------------------------------- |
|
</span><span>client.py 25 0 100% |
|
</span><span> |
|
</span><span>==== 6 passed in 0.11 seconds ==== |
|
</span></code></pre> |
|
<p>100% de cobertura; excelente! Você encerra o dia e fica vendo vídeos no |
|
YouTube o dia todo, até que alguém do financeiro vêm com essa notícia:</p> |
|
<blockquote> |
|
<p>"Não podemos perder a Cher, a Xuxa, a Madonna, a Björk e o String como |
|
clientes!"</p> |
|
</blockquote> |
|
<p>Seu pensamento é "meleca". Bom, hora de retornar o código para seu estado |
|
anterior:</p> |
|
<pre data-lang="python" style="background-color:#2b303b;color:#c0c5ce;" class="language-python "><code class="language-python" data-lang="python"><span style="color:#b48ead;">class </span><span style="color:#ebcb8b;">Client</span><span style="color:#eff1f5;">: |
|
</span><span> </span><span style="color:#b48ead;">def </span><span style="color:#96b5b4;">__init__</span><span>(</span><span style="color:#bf616a;">self</span><span>, </span><span style="color:#bf616a;">name</span><span>): |
|
</span><span> </span><span style="color:#bf616a;">self</span><span>.name = name |
|
</span></code></pre> |
|
<div style="border:1px solid grey; margin:7px; padding: 7px"> |
|
<p>Na verdade, a parte que deveria ser alterada é justamente a <code>_validate_name</code>, |
|
que não precisa mais perguntar se o cliente tem múltiplos nomes; eu mudei no |
|
lugar errado propositadamente para aumentar o efeito catastrófico da coisa.</p> |
|
|
|
</div> |
|
<p>Alteração feita, é hora de rodar os testes para ver o que aconteceu:</p> |
|
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>==== FAILURES ==== |
|
</span><span>____ test_client_error ____ |
|
</span><span> |
|
</span><span> def test_client_error(): |
|
</span><span> with pytest.raises(Exception): |
|
</span><span>> Client(name='Cher') |
|
</span><span>E Failed: DID NOT RAISE <class 'Exception'> |
|
</span><span> |
|
</span><span>client.py:37: Failed |
|
</span><span>==== 1 failed, 5 passed in 0.63 seconds ==== |
|
</span></code></pre> |
|
<p>Ah, lógico! "Cher" agora é um nome válido e, portanto, não vai mais levantar a |
|
exceção. Você altera o <code>test_client_error</code> para não esperar a exceção mais e |
|
roda os testes de novo: </p> |
|
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ pytest client.py |
|
</span><span>==== test session starts ==== |
|
</span><span>rootdir: /home/jbiason/unitt, inifile: |
|
</span><span>plugins: cov-2.4.0 |
|
</span><span>collected 6 items |
|
</span><span> |
|
</span><span>client.py ...... |
|
</span><span> |
|
</span><span>==== 6 passed in 0.03 seconds ==== |
|
</span></code></pre> |
|
<p>Só para garantir, vamos rodar com cobertura de novo: </p> |
|
<pre style="background-color:#2b303b;color:#c0c5ce;"><code><span>$ pytest --cov=client client.py |
|
</span><span>==== test session starts ==== |
|
</span><span>rootdir: /home/jbiason/unitt, inifile: |
|
</span><span>plugins: cov-2.4.0 |
|
</span><span>collected 6 items |
|
</span><span> |
|
</span><span>client.py ...... |
|
</span><span> |
|
</span><span>---- coverage: platform linux, python 3.4.3-final-0 ---- |
|
</span><span>Name Stmts Miss Cover |
|
</span><span>------------------------------- |
|
</span><span>client.py 24 0 100% |
|
</span><span> |
|
</span><span>==== 6 passed in 0.12 seconds ==== |
|
</span></code></pre> |
|
<p>Aí você se dá uns tapinhas nas costas por ter arrumado as coisas super rápido |
|
por ter pensado em seguir o SOLID, os testes escritos continuam todos |
|
funcionando corretamente e volta a ver vídeos.</p> |
|
<p>Entretanto, agora você criou um monstro: as funções <code>_validate_name</code> e |
|
<code>_multiple_names</code> <em>não</em> são mais necessárias, mas você não vê isso porque os |
|
testes de cobertura continuam indicando que tudo está sendo testado. E assim |
|
sua base de código vai crescendo e ninguém vê exatamente que há partes |
|
desnecessárias porque a cobertura, que deveria indicar partes que não estão |
|
sendo chamadas... estão sendo chamadas (pelos testes).</p> |
|
<div style="border:1px solid grey; margin:7px; padding: 7px"> |
|
<p>Algumas linguagens compiladas utilizam o conceito de "dead code": código que |
|
nunca é chamado e que não é necessário. Entretanto, dependendo da linguagem, |
|
isso pode não ser indicado em lugar nenhum porque o teste está usando a função |
|
e, portanto, ele não está realmente morto.</p> |
|
|
|
</div> |
|
<h2 id="de-volta-a-kent-beck">De volta a Kent Beck</h2> |
|
<p>Se voltarmos ao que Kent Beck disse no seu livro, temos que </p> |
|
<blockquote> |
|
<p>"Rode de forma isolada", nada mais, nada menos.</p> |
|
</blockquote> |
|
<p>Ou seja, não são "testes de unidade", mas "testes unitários", no sentido de |
|
que o teste consegue gerar e consumir todas as informações necessárias para |
|
completar sua execução. No nosso exemplo de clientes, um teste que faz a |
|
pesquisa por um cliente com um certo nome não deve, de forma alguma, depender |
|
do teste que cria clientes. O teste <strong>deve ser</strong> unitário, ele não depende de |
|
mais nenhum outro teste para funcionar.</p> |
|
<p>Discussões sobre "qual a unidade" é que levaram a criação de modelos como |
|
Behavior Driven Development (BDD) e Acceptance Test-Driven Development (ATDD); |
|
na verdade, o que elas fazem é mudar a semântica do que são os testes para que |
|
as pessoas olhem o que precisa ser feito ao invés de sair testando "unidades".</p> |
|
<h2 id="o-que-testar">O que testar</h2> |
|
<p>A aproximadamente três semanas (novamente, baseada na data deste post) |
|
apareceu uma pergunta no Reddit sobre se <a href="https://www.reddit.com/r/django/comments/5bearg/should_i_write_unit_tests_for_djangos_built_in/">devem ser testados os componentes |
|
internos do Django</a>, |
|
especificamente, se um campo inteiro deve ter testes para averiguar se o mesmo |
|
retorna erro no caso de serem passados valores não-numéricos.</p> |
|
<p>Esse foi um ponto que, numa primeira instancia, eu achei que não, mas a |
|
verdade é que sim, isso deve ser testado. Não porque você quer garantir que um |
|
campo numérico só aceita valores numéricos, mas porque a sua definição desse |
|
campo (que pode ser, por exemplo, um CEP, ou uma idade) deve aceitar somente |
|
valores numéricos. Você não está testando se o framework retorna um erro com |
|
valores não-numéricos, mas porque você precisa validar se <em>o seu campo é |
|
numérico</em>.</p> |
|
<p>Em outras palavras, o que deve ser testado não é como o sistema foi |
|
implementado, mas se os <em>requisitos</em> pedidos estão sendo cumpridos ("a |
|
idade/CEP deve aceitar apenas números"). <em>Essa</em> é a unidade dos testes, se |
|
vocês quiserem insistir na falácia. Essa é a <em>única</em> unidade que deve ser |
|
testada.</p> |
|
<p>Ou, como melhor colocou Kent Beck:</p> |
|
<blockquote> |
|
<p>Evite testar detalhes de implementação, teste comportamentos.</p> |
|
</blockquote> |
|
<h2 id="o-ciclo-do-tdd-e-o-lsp">O ciclo do TDD e o LSP</h2> |
|
<p>Uma coisa que o TDD promove é o ciclo "Vermelho", "Verde", "Refatoração". Isso |
|
pode ser "traduzido" como</p> |
|
<blockquote> |
|
<p>Escreva seu teste que garanta um certo requisito; como não há |
|
implementação, ele irá falhar (aparecer como vermelho nos resultados dos |
|
testes).</p> |
|
<p>Escreva a implementação. O teste irá passar para verde. Essa refatoração |
|
deve ser o mínimo de código necessário para fazer o teste passar.</p> |
|
<p>Refatore o código para remover código duplicado, má escolha de nomes de |
|
variáveis, quebras do SOLID, etc [#unclebob]_.</p> |
|
</blockquote> |
|
<div style="border:1px solid grey; margin:7px; padding: 7px"> |
|
<p>Eu nunca consegui produzir código TDD de verdade, mas alguns vídeos, como por |
|
exemplo <a href="https://vimeo.com/97516288%3E">Uncle Bob falando sobre test transformations</a>, |
|
fazem com que eu acredite que TDD puro e correto <em>funciona</em>.</p> |
|
|
|
</div> |
|
<p>Normalmente, depois de refatorar, espera-se que o código volte para o vermelho |
|
-- ou, pelo menos, é o que o gráfico utilizado deixa a entender. Entretanto, |
|
se você focou no requisito e não na implementação, o teste deve continuar |
|
verde, por mais que você refatore.</p> |
|
<p>Se voltarmos para o exemplo do nome único, nossos testes seriam apenas dois: |
|
barrar usuários com apenas um nome e deixar passar aqueles com dois ou mais. |
|
Todos os demais testes são desnecessários porque não fazem parte dos |
|
requisitos. Se essa validação será feita no próprio <code>__init__</code>, se isso vai |
|
ser outra classe, se vamos mandar isso para outro serviço que fará a validação |
|
do nome, nada disso importa: o que precisamos é <em>barrar</em> usuários com apenas |
|
um nome e <em>aceitar</em> aqueles com dois ou mais. Nada mais, nada menos.</p> |
|
<p>Se voltamos para o SOLID, existe um princípio que se encaixa nessa |
|
consideração: LSP - Princípio de Substituição de Liskov. Esse princípio é, na |
|
verdade, um nome complicado para "design por contrato"; no nosso exemplo, |
|
nosso contrato é: "Se você mandar um cliente com apenas um nome, irá ocorrer |
|
uma falha; clientes devem ter dois nome ou mais." Se amanhã eu trocar a classe |
|
inteira por outra, se eu reescrever a classe inteira, se eu mudar de |
|
linguagem, o teste deverá continuar passando (claro, considerando que eu |
|
consiga rodar os testes numa linguagem diferente da linguagem da |
|
implementação). Meu "contrato" continua válido, não importa a implementação |
|
que estamos falando. Meus requisitos continuam sendo verdadeiros, não importa |
|
como eu implementei. Meu <em>comportamento</em> continua sendo verdadeiro, validado |
|
pelos testes.</p> |
|
<p>Parte disso vem da definição de requisitos. Se você não tem requisitos bem |
|
definidos, você não tem o que testar. E ficar testando funções e/ou classes |
|
não vai lhe ajudar em nada, porque estes não são seus requisitos e, portanto, |
|
não fazem parte do seu contrato.</p> |
|
<h2 id="como-eu-vi-tdd-funcionar-de-verdade">Como eu vi TDD funcionar de verdade</h2> |
|
<p>Eu comentei no começo desse post como o vídeo do Ian Cooper ressonou com uma |
|
experiência pessoal trabalhando com TDD. Essa experiência foi relacionada ao |
|
trabalho com um gerenciador de alarmes: Vários componentes do sistema iriam |
|
gerar "eventos" e, conforme o evento gerado, o gerenciador de alarmes deveria</p> |
|
<ol> |
|
<li>gerar apenas um log; 2) gerar um log e enviar um aviso pela rede; 3) ativar |
|
um indicador de erro que continuaria ligado até que outro evento o desligasse.</li> |
|
</ol> |
|
<p>Como seguimos os princípios do SOLID, o código ficou separado, internamente, |
|
em quatro componentes: um componente de tomada de decisão, que indicaria para |
|
quais outros componentes o evento seria enviado; um componente para geração de |
|
logs; um componente para avisos através da rede; e, finalmente, um componente |
|
que ativaria ou desativaria alarmes relacionados com o evento. Da mesma forma, |
|
como eu não tinha noção ainda de testar comportamentos, eu acabei escrevendo |
|
testes que injetavam diretamente um evento que deveria ser logado no |
|
componente de logs e verificava o resultado; um evento que deveria ser enviado |
|
pela rede e verificava se o mesmo era gerado; um evento que deveria ser |
|
mantido como alarme para o componente de alarmes e verificava se o mesmo era |
|
ativado (e outro teste que enviada o evento de ativar alarme e depois enviava |
|
o evento de desativar alarme e verificava se o mesmo ficava desativado -- |
|
afinal de contas, um teste não pode depender de outro).</p> |
|
<p>Entretanto, algo não "parecia" certo. No momento, parecia que tudo estava ok, |
|
mas não tínhamos a sensação de que o aplicativo como um todo estava |
|
funcionando. Foi nesse ponto que começamos a falar de "testes de ponta a |
|
ponta": testes que utilizariam um componente para gerar o evento externamente |
|
do gerenciador de alarme e, depois de chamado, verificaria o estado final do |
|
sistema. Estes testes, apesar de serem mais complexos de serem escritos |
|
(porque haviam várias camadas intermediárias até que que o evento partisse de |
|
um aplicativo e chegasse ao gerenciador de alarmes), eles passaram a fazer |
|
muito mais sentido que testar cada componente isoladamente. Na época, eu não |
|
me liguei o porquê, mas depois do vídeo, eu entendi: Fazia mais sentido porque |
|
estávamos testado o <em>comportamento</em>, não a <em>implementação</em>.</p> |
|
<h2 id="resumo">Resumo</h2> |
|
<p>Se tudo ficou complexo, eu posso resumir TDD (de verdade) da seguinte forma:</p> |
|
<ul> |
|
<li>Escreva testes que verifiquem requisitos, não a implementação do requisito;</li> |
|
<li>A implementação pode mudar, os testes não deveriam;</li> |
|
<li>Garanta que o teste dependa apenas de si mesmo e não de outros testes;</li> |
|
<li>Use cobertura apenas para identificar código que pode ser <em>removido</em>, não |
|
que precisa de testes (afinal de contas, se você está verificando todos os |
|
requisitos e um trecho de código nunca é chamado, o que esse trecho de |
|
código está fazendo?)</li> |
|
</ul> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
</div> |
|
|
|
</body> |
|
|
|
</html>
|
|
|