Dê uma chance a Doctest

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. Em um determinado momento, eu precisava de uma função que 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, esta função (que chamei de 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:

Post a Comment

Your email is never shared. Required fields are marked *

*
*

%d bloggers like this: