Dê uma chance a Doctest

You are viewing an old revision of this post, from May 14, 2018 @ 08:22:06. See below for differences between this version and the current revision.

Um dos meus módulos Python preferidos é doctest. Com ele, é possível executar trechos de código inseridos em documentação. Você poderia, por exemplo, escrever algo assim no seu arquivo turorial.md

>>> f()
1

…e executar python -mdoctest tutorial.md. Se f() retornar 1, nada acontecerá. Se retornar algo diferente, porém, aparecerá uma mensagem de erro similar a esta:


É uma ferramenta impressionante, mas também é impopular. O problema é que Doctest é frequentemente utilizado de maneira inadequada. Por exemplo, é comum tentar escrever testes unitários como doctests. Grande erro!

Ainda assim, considero injusto desconsiderar o módulo devido a estes enganos. Doctest pode, e deve, ser usado para o que faz melhor: manter sua documentação viva, e até guiar seu desenvolvimento!

Deixe-me mostrar um exemplo.

Quando não se sabe o que fazer

Esses dias, estava escrevendo uma classe que alteraria um documento HTML utilizando xml.dom.minidom. Esse era o esqueleto da minha classe:

Em um determinado momento, eu precisava que a função get_css_dict() mapeasse classes CSS para nodes do documento. Esta seria uma função bem complicada por si só! Não sabia por onde começar.

Teoricamente, testes unitários seriam úteis aqui. Só não seriam tão práticos: esta era uma função interna, privada, um detalhe de implementação. Para testá-la, ela teria de ser exposta. Também precisaríamos de um novo arquivo para os testes. E, afinal, test cases não são tão legíveis.

Lendo a documentação do futuro

Ao invés disso, documentei a função primeiro. Escrevi um paragrafozinho informando o que ela faria. Só isso já clareou minha mente um pouco:

Given an xml.dom.minidom.Node, returns a map
from every “class” attribute to a list of nodes
with this class.

Então, pensei em como escrever a mesma coisa, mas como um exemplo de código. Na minha cabeça, get_css_class_dict() receberia um documento xml.dom.minidom. Então, criei um de exemplo:

Dado este exemplo, eu esperaria que a função retornasse um dicionário. Meu documento tem duas classes, “a” e “b”, e portanto o dicionário teria duas chaves. Cada chave teria uma lista dos nós que possuíssem tais classes. Algo mais ou menos assim:

Coloquei esses rascunhos na docstring de get_css_class_dict(). O resultado até agora foi essa função:

Dava para fazer algo similar com testes unitários, mas haveria muito mais código à volta, poluindo a documentação. Além disso,  a prosa graciosamente complementa o código, dando ritmo à leitura.

Executo os doctests, e o resultado é esse:

Basicamente, estou seguindo test-driven development, mas com documentação executável. Consegui, de uma vez, um exemplo legível e um teste básico.

Agora basta implementar a função! Usei recursão e, se o código não ficou o mais sucinto possível de primeira…

…ao menos funciona como esperado:

Opa, espere aí! O que foi isso?!

Quando a documentação erra

Pois bem, há um errozinho no meu doctest! O span não possui a classe b: é o div que a possui. Basta alterar a linha

[<DOM Element: span at ...>]

para

[<DOM Element: div at ...>]

e o doctest passará.

Não é uma maravilha? Descobri quase imediatamente um deslize na documentação. Mais que isto: se algum dia o comportamento da minha função mudar, o exemplo da minha docstring vai falhar! Saberei exatamente onde a documentação precisará de atualizações.

Fazendo doctests valerem a pena

Esta é a razão de ser de doctest. Nossa documentação possuía um erro sutil, e ao executá-la pudemos percebê-lo. Doctests não garantem a corretude do código; doctests garantem a corretude da documentação. É um aspecto bem compreendido do pacote, mas poucas pessoas parecem acreditar que isto valha a pena.

Eu acho que vale! Documentação tem a fama de ser um trabalho desagradável, mas não precisa ser. Assim como TDD torna testes mais empolgantes, é possível tornar documentação divertida com doctests.

Além disso, do mesmo modo que dificuldades com TDD indicam limitações de design, dificuldades em escrever doctest apontam para problemas na API. Se foi difícil escrever um exemplo conciso e claro, cercado de prosa, para sua API, ela provavelmente é complicada demais, não é?

Dê uma chance a Doctest

No final, entendo as restrições de doctests. São inadequados para testes unitários, por exemplo. Entretanto, doctest torna tão fácil e divertido documentar! Não entendo por que continua tão impopular.

Ainda assim, a maior vantagem para mim é como doctest torna o processo de desenvolvimento mais fácil. Há um tempo atrás, brinquei que deveríamos criar o DocDD (documentation-driven development):

Com Doctest, isto não é apenas uma brincadeira.

Post Revisions:

Changes:

May 14, 2018 @ 08:22:06Current Revision
Content
Unchanged: Um dos meus módulos Python preferidos é <a href="https:/ /docs.python.org/3/library/ doctest.html" >doctest</a>. Com ele, é possível executar trechos de código inseridos em documentação. Você poderia, por exemplo, escrever algo assim no seu arquivo <code>turorial.md</code>...Unchanged: Um dos meus módulos Python preferidos é <a href="https:/ /docs.python.org/3/library/ doctest.html" >doctest</a>. Com ele, é possível executar trechos de código inseridos em documentação. Você poderia, por exemplo, escrever algo assim no seu arquivo <code>turorial.md</code>...
Unchanged: <code>&gt;&gt;&gt; f()Unchanged: <code>&gt;&gt;&gt; f()
Unchanged: 1</code>Unchanged: 1</code>
Unchanged: ...e executar <code>python -mdoctest tutorial.md</code>. Se <code>f()</code> retornar 1, nada acontecerá. Se retornar algo diferente, porém, aparecerá uma mensagem de erro similar a esta:Unchanged: ...e executar <code>python -mdoctest tutorial.md</code>. Se <code>f()</code> retornar 1, nada acontecerá. Se retornar algo diferente, porém, aparecerá uma mensagem de erro similar a esta:
Unchanged: <script src="https:// pastebin.com/ embed_js/caYngeWY"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/caYngeWY"></script>
Unchanged: <noscript>**********************************************************************Unchanged: <noscript>**********************************************************************
Unchanged: File "f.txt", line 2, in f.txtUnchanged: File "f.txt", line 2, in f.txt
Unchanged: Failed example:Unchanged: Failed example:
Unchanged: f()Unchanged: f()
Unchanged: Expected:Unchanged: Expected:
Unchanged: 1Unchanged: 1
Unchanged: Got:Unchanged: Got:
Unchanged: 2Unchanged: 2
Unchanged: **********************************************************************Unchanged: **********************************************************************
Unchanged: 1 items had failures:Unchanged: 1 items had failures:
Unchanged: 1 of 2 in f.txtUnchanged: 1 of 2 in f.txt
Unchanged: ***Test Failed*** 1 failures.</noscript>Unchanged: ***Test Failed*** 1 failures.</noscript>
Unchanged: É uma ferramenta impressionante, mas também é impopular. O problema é que Doctest é frequentemente utilizado de maneira inadequada. Por exemplo, é comum tentar escrever testes unitários como doctests. Grande erro!Unchanged: É uma ferramenta impressionante, mas também é impopular. O problema é que Doctest é frequentemente utilizado de maneira inadequada. Por exemplo, é comum tentar escrever testes unitários como doctests. Grande erro!
Unchanged: Ainda assim, considero injusto desconsiderar o módulo devido a estes enganos. Doctest pode, e deve, ser usado para o que faz melhor: manter sua documentação viva, e até guiar seu desenvolvimento!Unchanged: Ainda assim, considero injusto desconsiderar o módulo devido a estes enganos. Doctest pode, e deve, ser usado para o que faz melhor: manter sua documentação viva, e até guiar seu desenvolvimento!
Unchanged: Deixe-me mostrar um exemplo.Unchanged: Deixe-me mostrar um exemplo.
Unchanged: <h3>Quando não se sabe o que fazer</h3>Unchanged: <h3>Quando não se sabe o que fazer</h3>
Deleted: Esses dias, estava escrevendo uma classe que alteraria um documento HTML utilizando <code>xml.dom.minidom</code>. Esse era o esqueleto da minha classe: 
Deleted: <script src="https:// pastebin.com/ embed_js/LGQKGjTy"></script> 
Deleted: Em um determinado momento, eu precisava que a função <code>get_css_ dict()</code> mapeasse classes CSS para <em>nodes</em> do documento. Esta seria uma função bem complicada por si só! Não sabia por onde começar. Added: Esses dias, estava escrevendo uma classe que alteraria um documento HTML utilizando <code>xml.dom.minidom</code>. Em um determinado momento, eu precisava de uma função que mapeasse classes CSS para <em>nodes</em> do documento. Esta seria uma função bem complicada por si só! Não sabia por onde começar.
Unchanged: Teoricamente, testes unitários seriam úteis aqui. Só não seriam tão práticos: esta era uma função interna, privada, um detalhe de implementação. Para testá-la, ela teria de ser exposta. Também precisaríamos de um novo arquivo para os testes. E, afinal, <em class="en">test cases</em> não são tão legíveis.Unchanged: Teoricamente, testes unitários seriam úteis aqui. Só não seriam tão práticos: esta era uma função interna, privada, um detalhe de implementação. Para testá-la, ela teria de ser exposta. Também precisaríamos de um novo arquivo para os testes. E, afinal, <em class="en">test cases</em> não são tão legíveis.
Unchanged: <h3>Lendo a documentação do futuro</h3>Unchanged: <h3>Lendo a documentação do futuro</h3>
Unchanged: Ao invés disso, documentei a função primeiro. Escrevi um paragrafozinho informando o que ela faria. Só isso já clareou minha mente um pouco:Unchanged: Ao invés disso, documentei a função primeiro. Escrevi um paragrafozinho informando o que ela faria. Só isso já clareou minha mente um pouco:
Unchanged: <blockquote>Given an xml.dom.minidom.Node, returns a mapUnchanged: <blockquote>Given an xml.dom.minidom.Node, returns a map
Unchanged: from every "class" attribute to a list of nodesUnchanged: from every "class" attribute to a list of nodes
Unchanged: with this class.</blockquote>Unchanged: with this class.</blockquote>
Deleted: Então, pensei em como escrever a mesma coisa, mas como um exemplo de código. Na minha cabeça, <code>get_css_ class_dict()</code> receberia um documento <code>xml.dom.minidom</code>. Então, criei um de exemplo: Added: Então, pensei em como escrever a mesma coisa, mas como um exemplo de código. Na minha cabeça, esta função (que chamei de <code>get_css_ class_dict()</code>) receberia um documento <code>xml.dom.minidom</code>. Então, criei um de exemplo:
Unchanged: <script src="https:// pastebin.com/ embed_js/vG9Ydb1g"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/vG9Ydb1g"></script>
Unchanged: Dado este exemplo, eu esperaria que a função retornasse um dicionário. Meu documento tem duas classes, "a" e "b", e portanto o dicionário teria duas chaves. Cada chave teria uma lista dos nós que possuíssem tais classes. Algo mais ou menos assim:Unchanged: Dado este exemplo, eu esperaria que a função retornasse um dicionário. Meu documento tem duas classes, "a" e "b", e portanto o dicionário teria duas chaves. Cada chave teria uma lista dos nós que possuíssem tais classes. Algo mais ou menos assim:
Unchanged: <script src="https:// pastebin.com/ embed_js/n4kcbAbs"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/n4kcbAbs"></script>
Unchanged: Coloquei esses rascunhos na <a href="https:/ /pythonhelp.wordpress.com/ 2011/02/14/docstrings/ ">docstring</a> de <code>get_css_ class_dict()</code>. O resultado até agora foi essa função:Unchanged: Coloquei esses rascunhos na <a href="https:/ /pythonhelp.wordpress.com/ 2011/02/14/docstrings/ ">docstring</a> de <code>get_css_ class_dict()</code>. O resultado até agora foi essa função:
Unchanged: <script src="https:// pastebin.com/ embed_js/phARAZVE"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/phARAZVE"></script>
Unchanged: Dava para fazer algo similar com testes unitários, mas haveria muito mais código à volta, poluindo a documentação. Além disso,  a prosa graciosamente complementa o código, dando ritmo à leitura.Unchanged: Dava para fazer algo similar com testes unitários, mas haveria muito mais código à volta, poluindo a documentação. Além disso,  a prosa graciosamente complementa o código, dando ritmo à leitura.
Unchanged: Executo os doctests, e o resultado é esse:Unchanged: Executo os doctests, e o resultado é esse:
Unchanged: <script src="https:// pastebin.com/ embed_js/18LQid1L"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/18LQid1L"></script>
Unchanged: Basicamente, estou seguindo <em class="en">test-driven development</em>, mas com documentação executável. Consegui, de uma vez, um exemplo legível e um teste básico.Unchanged: Basicamente, estou seguindo <em class="en">test-driven development</em>, mas com documentação executável. Consegui, de uma vez, um exemplo legível e um teste básico.
Unchanged: Agora basta implementar a função! Usei recursão e, se o código não ficou o mais sucinto possível de primeira...Unchanged: Agora basta implementar a função! Usei recursão e, se o código não ficou o mais sucinto possível de primeira...
Unchanged: <script src="https:// pastebin.com/ embed_js/69HF5w4A"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/69HF5w4A"></script>
Unchanged: ...ao menos funciona como esperado:Unchanged: ...ao menos funciona como esperado:
Unchanged: <script src="https:// pastebin.com/ embed_js/riWKQc0S"></script>Unchanged: <script src="https:// pastebin.com/ embed_js/riWKQc0S"></script>
Unchanged: Opa, espere aí! O que foi isso?!Unchanged: Opa, espere aí! O que foi isso?!
Unchanged: <h3>Quando a documentação erra</h3>Unchanged: <h3>Quando a documentação erra</h3>
Unchanged: Pois bem, há um errozinho no meu doctest! O span não possui a classe b: é o div que a possui. Basta alterar a linhaUnchanged: Pois bem, há um errozinho no meu doctest! O span não possui a classe b: é o div que a possui. Basta alterar a linha
Unchanged: <code> [&lt;DOM Element: span at ...&gt;]</code>Unchanged: <code> [&lt;DOM Element: span at ...&gt;]</code>
Unchanged: paraUnchanged: para
Unchanged: <code> [&lt;DOM Element: div at ...&gt;]</code>Unchanged: <code> [&lt;DOM Element: div at ...&gt;]</code>
Unchanged: e o doctest passará.Unchanged: e o doctest passará.
Unchanged: Não é uma maravilha? Descobri quase imediatamente um deslize na documentação. Mais que isto: se algum dia o comportamento da minha função mudar, o exemplo da minha docstring vai falhar! Saberei exatamente onde a documentação precisará de atualizações.Unchanged: Não é uma maravilha? Descobri quase imediatamente um deslize na documentação. Mais que isto: se algum dia o comportamento da minha função mudar, o exemplo da minha docstring vai falhar! Saberei exatamente onde a documentação precisará de atualizações.
Unchanged: <h3>Fazendo doctests valerem a pena</h3>Unchanged: <h3>Fazendo doctests valerem a pena</h3>
Unchanged: Esta é a razão de ser de doctest. Nossa documentação possuía um erro sutil, e ao executá-la pudemos percebê-lo. Doctests não garantem a corretude do código; doctests garantem a corretude da documentação. É um aspecto bem compreendido do pacote, mas poucas pessoas parecem acreditar que isto valha a pena.Unchanged: Esta é a razão de ser de doctest. Nossa documentação possuía um erro sutil, e ao executá-la pudemos percebê-lo. Doctests não garantem a corretude do código; doctests garantem a corretude da documentação. É um aspecto bem compreendido do pacote, mas poucas pessoas parecem acreditar que isto valha a pena.
Unchanged: Eu acho que vale! Documentação tem a fama de ser um trabalho desagradável, mas não precisa ser. Assim como TDD torna testes mais empolgantes, é possível tornar documentação divertida com doctests.Unchanged: Eu acho que vale! Documentação tem a fama de ser um trabalho desagradável, mas não precisa ser. Assim como TDD torna testes mais empolgantes, é possível tornar documentação divertida com doctests.
Unchanged: Além disso, do mesmo modo que dificuldades com TDD indicam limitações de design, dificuldades em escrever doctest apontam para problemas na API. Se foi difícil escrever um exemplo conciso e claro, cercado de prosa, para sua API, ela provavelmente é complicada demais, não é?Unchanged: Além disso, do mesmo modo que dificuldades com TDD indicam limitações de design, dificuldades em escrever doctest apontam para problemas na API. Se foi difícil escrever um exemplo conciso e claro, cercado de prosa, para sua API, ela provavelmente é complicada demais, não é?
Unchanged: <h3>Dê uma chance a Doctest</h3>Unchanged: <h3>Dê uma chance a Doctest</h3>
Unchanged: No final, entendo as restrições de doctests. São inadequados para testes unitários, por exemplo. Entretanto, doctest torna tão fácil e divertido documentar! Não entendo por que continua tão impopular.Unchanged: No final, entendo as restrições de doctests. São inadequados para testes unitários, por exemplo. Entretanto, doctest torna tão fácil e divertido documentar! Não entendo por que continua tão impopular.
Unchanged: Ainda assim, a maior vantagem para mim é como doctest torna o <em>processo de desenvolvimento</em> mais fácil. Há um tempo atrás, brinquei que deveríamos criar o DocDD (<em>documentation-driven development</em>):Unchanged: Ainda assim, a maior vantagem para mim é como doctest torna o <em>processo de desenvolvimento</em> mais fácil. Há um tempo atrás, brinquei que deveríamos criar o DocDD (<em>documentation-driven development</em>):
Unchanged: https://twitter.com/ adambrandizzi/ status/608430368233017344Unchanged: https://twitter.com/ adambrandizzi/ status/608430368233017344
Unchanged: Com Doctest, isto não é apenas uma brincadeira.Unchanged: Com Doctest, isto não é apenas uma brincadeira.

Note: Spaces may be added to comparison text to allow better line wrapping.

Post a Comment

Your email is never shared. Required fields are marked *

*
*

%d bloggers like this: