{"id":279,"date":"2018-04-16T12:00:37","date_gmt":"2018-04-16T15:00:37","guid":{"rendered":"http:\/\/suspensao.blog.br\/descrenca\/?p=279"},"modified":"2018-05-21T07:48:36","modified_gmt":"2018-05-21T10:48:36","slug":"doctest","status":"publish","type":"post","link":"https:\/\/suspensao.blog.br\/descrenca\/doctest\/","title":{"rendered":"D\u00ea uma chance a Doctest"},"content":{"rendered":"<p>Um dos meus m\u00f3dulos Python preferidos \u00e9 <a href=\"https:\/\/docs.python.org\/3\/library\/doctest.html\">doctest<\/a>. Com ele, \u00e9 poss\u00edvel executar trechos de c\u00f3digo inseridos em documenta\u00e7\u00e3o. Voc\u00ea poderia, por exemplo, escrever algo assim no seu arquivo <code>turorial.md<\/code>&#8230;<\/p>\n<p><code>&gt;&gt;&gt; f()<br \/>\n1<\/code><\/p>\n<p>&#8230;e executar <code>python -mdoctest tutorial.md<\/code>. Se <code>f()<\/code> retornar 1, nada acontecer\u00e1. Se retornar algo diferente, por\u00e9m, aparecer\u00e1 uma mensagem de erro similar a esta:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/caYngeWY\"><\/script><br \/>\n<noscript>**********************************************************************<br \/>\nFile &#8220;f.txt&#8221;, line 2, in f.txt<br \/>\nFailed example:<br \/>\nf()<br \/>\nExpected:<br \/>\n1<br \/>\nGot:<br \/>\n2<br \/>\n**********************************************************************<br \/>\n1 items had failures:<br \/>\n1 of   2 in f.txt<br \/>\n***Test Failed*** 1 failures.<\/noscript><\/p>\n<p>\u00c9 uma ferramenta impressionante, mas tamb\u00e9m \u00e9 impopular. O problema \u00e9 que Doctest \u00e9 frequentemente utilizado de maneira inadequada. Por exemplo, \u00e9 comum tentar escrever testes unit\u00e1rios como doctests. Grande erro!<\/p>\n<p>Ainda assim, considero injusto desconsiderar o m\u00f3dulo devido a estes enganos. Doctest pode, e deve, ser usado para o que faz melhor: manter sua documenta\u00e7\u00e3o viva, e at\u00e9 guiar seu desenvolvimento!<\/p>\n<p>Deixe-me mostrar um exemplo.<\/p>\n<h3>Quando n\u00e3o se sabe o que fazer<\/h3>\n<p>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\u00e7\u00e3o que mapeasse classes CSS para <em>nodes<\/em> do documento. Esta seria uma fun\u00e7\u00e3o bem complicada por si s\u00f3! N\u00e3o sabia por onde come\u00e7ar.<\/p>\n<p>Teoricamente, testes unit\u00e1rios seriam \u00fateis aqui. S\u00f3 n\u00e3o seriam t\u00e3o pr\u00e1ticos: esta era uma fun\u00e7\u00e3o interna, privada, um detalhe de implementa\u00e7\u00e3o. Para test\u00e1-la, ela teria de ser exposta. Tamb\u00e9m precisar\u00edamos de um novo arquivo para os testes. E, afinal, <em class=\"en\">test cases<\/em> n\u00e3o s\u00e3o t\u00e3o leg\u00edveis.<\/p>\n<h3>Lendo a documenta\u00e7\u00e3o do futuro<\/h3>\n<p>Ao inv\u00e9s disso, documentei a fun\u00e7\u00e3o primeiro. Escrevi um paragrafozinho informando o que ela faria. S\u00f3 isso j\u00e1 clareou minha mente um pouco:<\/p>\n<blockquote><p>Given an xml.dom.minidom.Node, returns a map<br \/>\nfrom every &#8220;class&#8221; attribute to a list of nodes<br \/>\nwith this class.<\/p><\/blockquote>\n<p>Ent\u00e3o, pensei em como escrever a mesma coisa, mas como um exemplo de c\u00f3digo. Na minha cabe\u00e7a, esta fun\u00e7\u00e3o (que chamei de <code>get_css_class_dict()<\/code>) receberia um documento <code>xml.dom.minidom<\/code>. Ent\u00e3o, criei um de exemplo:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/vG9Ydb1g\"><\/script><\/p>\n<p>Dado este exemplo, eu esperaria que a fun\u00e7\u00e3o retornasse um dicion\u00e1rio. Meu documento tem duas classes, &#8220;a&#8221; e &#8220;b&#8221;, e portanto o dicion\u00e1rio teria duas chaves. Cada chave teria uma lista dos n\u00f3s que possu\u00edssem tais classes. Algo mais ou menos assim:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/n4kcbAbs\"><\/script><\/p>\n<p>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\u00e9 agora foi essa fun\u00e7\u00e3o:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/phARAZVE\"><\/script><\/p>\n<p>Dava para fazer algo similar com testes unit\u00e1rios, mas haveria muito mais c\u00f3digo \u00e0 volta, poluindo a documenta\u00e7\u00e3o. Al\u00e9m disso,\u00a0 a prosa graciosamente complementa o c\u00f3digo, dando ritmo \u00e0 leitura.<\/p>\n<p>Executo os doctests, e o resultado \u00e9 esse:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/18LQid1L\"><\/script><\/p>\n<p>Basicamente, estou seguindo <em class=\"en\">test-driven development<\/em>, mas com documenta\u00e7\u00e3o execut\u00e1vel. Consegui, de uma vez, um exemplo leg\u00edvel e um teste b\u00e1sico.<\/p>\n<p>Agora basta implementar a fun\u00e7\u00e3o! Usei recurs\u00e3o e, se o c\u00f3digo n\u00e3o ficou o mais sucinto poss\u00edvel de primeira&#8230;<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/69HF5w4A\"><\/script><\/p>\n<p>&#8230;ao menos funciona como esperado:<\/p>\n<p><script src=\"https:\/\/pastebin.com\/embed_js\/riWKQc0S\"><\/script><\/p>\n<p>Opa, espere a\u00ed! O que foi isso?!<\/p>\n<h3>Quando a documenta\u00e7\u00e3o erra<\/h3>\n<p>Pois bem, h\u00e1 um errozinho no meu doctest! O span n\u00e3o possui a classe b: \u00e9 o div que a possui. Basta alterar a linha<\/p>\n<p><code>    [&lt;DOM Element: span at ...&gt;]<\/code><\/p>\n<p>para<\/p>\n<p><code>    [&lt;DOM Element: div at ...&gt;]<\/code><\/p>\n<p>e o doctest passar\u00e1.<\/p>\n<p>N\u00e3o \u00e9 uma maravilha? Descobri quase imediatamente um deslize na documenta\u00e7\u00e3o. Mais que isto: se algum dia o comportamento da minha fun\u00e7\u00e3o mudar, o exemplo da minha docstring vai falhar! Saberei exatamente onde a documenta\u00e7\u00e3o precisar\u00e1 de atualiza\u00e7\u00f5es.<\/p>\n<h3>Fazendo doctests valerem a pena<\/h3>\n<p>Esta \u00e9 a raz\u00e3o de ser de doctest. Nossa documenta\u00e7\u00e3o possu\u00eda um erro sutil, e ao execut\u00e1-la pudemos perceb\u00ea-lo. Doctests n\u00e3o garantem a corretude do c\u00f3digo; doctests garantem a corretude da documenta\u00e7\u00e3o. \u00c9 um aspecto bem compreendido do pacote, mas poucas pessoas parecem acreditar que isto valha a pena.<\/p>\n<p>Eu acho que vale! Documenta\u00e7\u00e3o tem a fama de ser um trabalho desagrad\u00e1vel, mas n\u00e3o precisa ser. Assim como TDD torna testes mais empolgantes, \u00e9 poss\u00edvel tornar documenta\u00e7\u00e3o divertida com doctests.<\/p>\n<p>Al\u00e9m disso, do mesmo modo que dificuldades com TDD indicam limita\u00e7\u00f5es de design, dificuldades em escrever doctest apontam para problemas na API. Se foi dif\u00edcil escrever um exemplo conciso e claro, cercado de prosa, para sua API, ela provavelmente \u00e9 complicada demais, n\u00e3o \u00e9?<\/p>\n<h3>D\u00ea uma chance a Doctest<\/h3>\n<p>No final, entendo as restri\u00e7\u00f5es de doctests. S\u00e3o inadequados para testes unit\u00e1rios, por exemplo. Entretanto, doctest torna t\u00e3o f\u00e1cil e divertido documentar! N\u00e3o entendo por que continua t\u00e3o impopular.<\/p>\n<p>Ainda assim, a maior vantagem para mim \u00e9 como doctest torna o <em>processo de desenvolvimento<\/em> mais f\u00e1cil. H\u00e1 um tempo atr\u00e1s, brinquei que dever\u00edamos criar o DocDD (<em>documentation-driven development<\/em>):<\/p>\n<blockquote class=\"twitter-tweet\" data-width=\"500\" data-dnt=\"true\">\n<p lang=\"en\" dir=\"ltr\">I need to invent the documentation-driven development. I&#39;m writing some docstrings here and uh! so many things were wrong!<\/p>\n<p>&mdash; Adam Brandizzi (@adambrandizzi) <a href=\"https:\/\/twitter.com\/adambrandizzi\/status\/608430368233017344?ref_src=twsrc%5Etfw\">June 10, 2015<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>\n<p>Com Doctest, isto n\u00e3o \u00e9 apenas uma brincadeira.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Um dos meus m\u00f3dulos Python preferidos \u00e9 doctest. Com ele, \u00e9 poss\u00edvel executar trechos de c\u00f3digo inseridos em documenta\u00e7\u00e3o. Voc\u00ea poderia, por exemplo, escrever algo assim no seu arquivo turorial.md&#8230; &gt;&gt;&gt; f() 1 &#8230;e executar python -mdoctest tutorial.md. Se f() retornar 1, nada acontecer\u00e1. Se retornar algo diferente, por\u00e9m, aparecer\u00e1 uma mensagem de erro similar [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[21,87,88,26],"tags":[84,85,86,59],"class_list":["post-279","post","type-post","status-publish","format-standard","hentry","category-desenvolvimento-orientado-a-testes","category-documentacao","category-documentacao-2","category-python","tag-doctest","tag-documentacao","tag-documentation-driven-development","tag-tdd"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/s23QLV-doctest","jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/posts\/279","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/comments?post=279"}],"version-history":[{"count":36,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/posts\/279\/revisions"}],"predecessor-version":[{"id":324,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/posts\/279\/revisions\/324"}],"wp:attachment":[{"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/media?parent=279"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/categories?post=279"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/suspensao.blog.br\/descrenca\/wp-json\/wp\/v2\/tags?post=279"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}