python doctest
# Python doctest被低估的文档测试利器什么是doctest说到Python的测试工具大部分人第一反应是unittest或者pytest。但有个藏在标准库里的小家伙经常被人忽略它就是doctest。简单来说doctest允许你在文档字符串里写代码示例然后自动验证这些示例是否正确运行。听起来很简单对吧但这东西背后的设计思路其实挺有意思的——它把文档和测试绑在了一起确保你文档里的例子永远是最新的。举个例子你去超市买牛奶包装上写着“保质期7天”结果拿回家发现其实已经过期了。这就像那些文档和实际代码不一致的情况。doctest就是要避免这种问题。它能做什么doctest的核心能力其实就两件事验证文档中的代码示例是否正确以及作为一个轻量级的测试框架使用。但它的价值远不止这些。想象一下这样的场景你写了一个函数文档里贴心地给出了使用示例。六个月后你修改了这个函数但忘了更新文档。新来的同事看着你的文档示例运行出错开始怀疑人生。doctest就在这种时刻发挥作用——它会在每次测试运行时自动检查这些例子是否还能正常工作。另外doctest的交互性测试方式很特别。它模拟的是Python交互环境的样子也就是说你可以直接复制粘贴交互环境中的对话来创建测试。比如你在终端里试了一串代码 1 2 3这段对话就能直接成为doctest的测试用例。对于简单的函数或模块这意味着你几乎不需要额外写测试代码。怎么使用用doctest其实很自然。假如你写了一个计算商品折扣的函数defcalculate_discount(price,discount_rate): 计算折扣后的价格 示例: calculate_discount(100, 0.2) 80.0 calculate_discount(50, 0.1) 45.0 returnprice*(1-discount_rate)运行测试的方式也简单在模块末尾加两行if__name____main__:importdoctest doctest.testmod()然后执行这个模块如果一切正常什么都不会输出。有错误的话会详细告诉你哪里出了问题。doctest也支持更细致的控制。比如你想忽略某些行的输出差异或者期望测试抛出异常。甚至可以测试包含随机值的示例只要用上# doctest: ELLIPSIS这样的指令。最佳实践这些年用了不少doctest总结出几条经验第一个原则用在合适的地方。doctest最适合那些简单的、有明确输出的函数。比如数据处理、字符串操作、数学计算这类。不适合用在那些涉及网络、数据库、随机数的地方——虽然也能测试但会让文档看起来臃肿。第二个原则保持示例简洁。每条doctest用例只测试一个场景。见过有人在文档里写了一大段代码测试了十几个不同的情况。这样做维护起来很痛苦而且读者看着也累。真正的好文档例子就该像教科书一样一个点一个例子。我自己的做法是重要函数写两到三个典型例子覆盖正常情况和边界情况。比如处理字符串的函数会写一个正常输入、一个空字符串、一个特殊字符的例子。第三个原则把doctest当作“文档”来维护。经常有人把doctest写成理解测试然后随意修改文档也不更新测试。实际上应该反过来——先想好文档里要展示什么例子再把它们变成测试。这样文档和测试就自然统一了。和同类技术对比说完doctest得聊聊它的竞争对手们。首先是unittest。这是Python标准库里的正统测试框架。它的优势在于能力更强可以做setUp、tearDown支持测试套件、断言方法等。但缺点也很明显——你需要写单独的测试类和方法对简单的小函数来说有点重。打个比方unittest像是给你的代码买了个保险柜什么都能锁但每次开锁都要费点功夫。doctest就像是贴了个防盗标签轻便快捷但只能防君子。再说pytest。这货现在是Python测试的事实标准。它的能力远超doctest支持参数化测试、fixture、插件系统等等。pytest也内置了对doctest的支持可以在pytest框架下运行doctest。有人问我为什么不用pytest完全替代doctest。我的回答是doctest的独特价值在于“文档即测试”的理念。pytest虽然也能测试文档中的例子但它做不到让代码示例和文档描述如此紧密地融合在一起。最后说说Sphinx的doctest扩展。如果你在写项目文档Sphinx可能是更好的选择。它能在构建文档时自动运行测试而且支持更丰富的输出格式。不过对于小项目来说标准库的doctest已经足够了。一点个人见解用了这么多年Python我感觉doctest最容易被忽视的价值不是测试本身而是它迫使你把代码的使用方式想得更清楚。当你需要在文档里写一个清晰可运行的例子时你会不自觉地开始思考这个API设计得是否直观命名是否合理用例是否覆盖了常见场景在这个意义上doctest更像是个设计工具而非测试工具。它让你在写代码的同时就在为未来的用户着想。这一点是unittest和pytest都做不到的。