python 模块的单元测试基础设施

发布于 2024-09-09 07:49:11 字数 405 浏览 9 评论 0原文

我正在编写一个 python 模块,我想对其进行单元测试。我是 python 新手,对可用的选项有些困惑。

目前,我想将我的测试编写为 doctests 因为我喜欢声明式而不是命令式风格(但是,如果我的这种偏好被误导,请随时纠正我)。然而,这提出了一些问题:

  1. 我应该把测试放在哪里?在与他们正在测试的代码相同的文件中(或在文档测试的文档字符串中)?或者将它们分开到自己的目录中是否被认为更好?
  2. 如何从命令行一次性运行整个模块中的所有测试?
  3. 如何报告测试套件的代码覆盖率?
  4. 对于 python 中的单元测试,我还应该了解其他最佳实践吗?

I'm writing a python module and I would like to unit test it. I am new to python and somewhat bamboozled by the options available.

Currently, I would like to write my tests as doctests as I like the declarative rather than imperative style (however, feel free to disabuse me of this preference if it is misinformed). This raises a few questions, however:

  1. Where should I put the tests? In the same file as the code they are testing (or in docstrings for doctests)? Or is it considered better to separate them out into their own directory?
  2. How can I run all the tests in the whole module from the command-line in one go?
  3. How can I report the code coverage of the test suite?
  4. Any other best-practices I should be aware of for unit testing in python?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

起风了 2024-09-16 07:49:11

请随意纠正我的观点
如果信息有误,则偏好

我比任何其他开源开发人员更广泛地使用了 doctest(方式 扩展了其预期使用边界),至少在单个项目中是这样 - < em>所有我的gmpy项目中的测试都是文档测试。在 gmpy 刚开始时它是全新的,这似乎是一个很棒的小技巧,如果某件事值得做,那么就值得过度做 - 对吗?-)

错了。除了 gmpy 之外,将所有内容重做为正确的单元测试会带来太多的返工,我再也没有犯过这样的错误:这些天,我使用单元测试作为单元测试,而文档测试只是为了检查我的文档,因为它们一直是用来使用的。文档测试所做的(将预期结果与实际结果进行比较——仅此而已)并不是构建可靠的测试套件的良好或合理的基础。它从来没有其他意图。

我建议您查看nose。新的 Python 2.7 中的 unittest 模块更丰富、更好,如果您坚持使用 2.4、2.5 或 2.6,您仍然可以通过 unittest2 您可以下载并安装; nose 很好地补充了 unittest

如果你无法忍受单元测试(但是——尝试一下,它会在你身上成长!-),也许可以尝试 py.test,一个具有完全不同理念的替代包。

但是,,不要使用 doctest 来测试文档中示例以外的内容!精确相等的比较经常会阻碍你,因为我不得不以我的(隐喻性的;-)代价在gmpy中学习......

feel free to disabuse me of this
preference if it is misinformed

I believe I used doctest more extensively (way stretching its intended use boundaries) than any other open source developer, at least within a single project -- all the tests in my gmpy project are doctests. It was brand new at the time gmpy was starting, it seemed a great little trick, and if something is worth doing it's worth doing in excess -- right?-)

Wrong. Except for gmpy, where redoing everything as proper unit tests would be too much rework, I've never made that mistake again: these days, I use unit tests as unit tests, and doctests just to check my docs, as they've always been meant to be used. What doctests do (compare an expected with an actual result for equality -- that's all) is just not a good or sound basis to build a solid test suite on. It was never intended otherwise.

I would recommend you look at nose. The unittest module in the new Python 2.7 is much richer and nicer, and if you're stuck on 2.4, 2.5 or 2.6 you can still use the new features with the unittest2 which you can download and install; nose complements unittest quite well.

If you can't stand unittest (but -- give it a try, it grows on you!-), maybe try py.test, an alternative package with a pretty different philosophy.

But, please, don't stretch doctest to test stuff other than examples in docs! The exact-equality comparison will stand in your way far too often, as I've had to learn at my (metaphorical;-) expense in gmpy...

零時差 2024-09-16 07:49:11

我不喜欢文档测试,原因如下:

  • 您无法运行测试的子集。当测试失败时,仅运行一个测试很有用。 Doctest 没有提供这样做的方法。
  • 如果在文档测试过程中发生故障,整个事情就会停止。我宁愿查看所有结果来决定如何解决破损问题。
  • 编码风格是程式化的,并且必须具有可打印的结果。
  • 您的代码以特殊方式执行,因此更难推理它将如何执行,更难添加帮助程序,更难围绕测试进行编程。

此列表取自我的博客文章 关于 doctest 我不喜欢的事情,其中还有更多,还有一长串评论讨论这些观点。

关于覆盖率:我不认为 Python 中有一个覆盖率工具可以测量文档测试中的覆盖率。但由于它们只是很长的语句列表,没有分支或循环,这是一个问题吗?

I don't like doctests for these reasons:

  • You can't run a subset of the tests. When a test fails, it's useful to run just one test. Doctest provides no way to do that.
  • If a failure happens in the middle of the doctest, the whole thing stops. I'd rather see all the results to decide how to tackle a breakage.
  • The coding style is stylized, and has to have printable results.
  • Your code is executed in a special way, so it's harder to reason about how it will be executed, harder to add helpers, and harder to program around the tests.

This list was taken from my blog post Things I don't like about doctest, where there's more, and a long thread of comments debating the points.

About coverage: I don't believe there's a coverage tool for Python that will measure coverage within doctests. But since they are simply long lists of statements with no branches or loops, is that a problem?

记忆之渊 2024-09-16 07:49:11

我怀疑 Alex 在程序员的曲线上可能比我领先一些,但如果你想要有一些 Python 经验的人的观点(作为“用户”而不是专家或传播者),但又不一样联盟,我对单元测试的发现几乎相同。

一开始,文档测试对于简单的测试来说可能听起来很棒,我在家里的一些个人项目中朝着这个方向发展,因为它在其他地方被推荐了。
在工作中我们使用鼻子(尽管如此封闭和包裹,我的印象是我们直到不久前才使用 pyUnit),几个月前我在家也开始使用鼻子。

初始设置时间和管理开销,以及与实际代码的分离,一开始似乎是不必要的,特别是当您测试代码库不是那么大的东西时,但从长远来看,我发现文档测试越来越以我想做的每一次重构或重组的方式,相当难以维护,几乎不可能扩展,并且很快就抵消了最初的节省。是的,我知道单元测试与集成测试不同,但文档测试往往会过于严格地为您定义单元。
如果您认为它是有效的草图工具或开发模型,它们也不太适合基于单元的敏捷。

您可能需要花一些时间来计划,然后按照 pyUnit 或鼻子引导您的方式完善您的单元测试,但即使在短期内,您也可能会发现它实际上在很多层面上都在帮助您。我知道它对我有用,而且我对这些天正在处理的代码库的复杂性和规模还比较陌生。前几周你必须咬紧牙关。

I have this suspicion that Alex might be a fair bit ahead of me on the programmer's curve, but if you want the perspective of somebody with some Python experience (as a "user" rather than an expert or evangelist), yet not in the same league, my findings about unit testing have been pretty much the same.

Doctests might sound great for simple testing in the beginning, and I went in that direction for some personal project at home because it had been recommended elsewhere.
At work we use nose (although so canned and wrapped up I was under the impression we'd been using pyUnit until not long ago), and a few months back I moved to nose at home too.

The initial setup time and management overhead, and the separation from the actual code, might seem unnecessary in the beginning, especially when you're testing something that isn't that large a codebase, but in the long run I've found doctests getting in the way of every single refactoring or restructuring I wanted to do, rather hard to maintain, practically impossible to scale, and offsetting the initial savings very quickly. And yes, I'm aware that unit testing isn't the same as integration testing, but doctests tend to define your units for you rather too strictly.
They're also not well suited to unit based agile if you ever decide it's a valid sketching tool or dev model.

It might take you a bit to plan and then refine your unit tests the way pyUnit or nose steer you towards, but chances are that even in the short term you'll find it's actually helping you out on many levels. I know it did for me, and I'm relatively new to the complexity and scale of the codebase I'm working on these days. Just have to clench your teeth for the first few weeks.

雪花飘飘的天空 2024-09-16 07:49:11

有关覆盖范围,请查看优秀的 coverage.py

除此之外,亚历克斯·马尔泰利写的所有内容都非常切题。

For coverage, check out the excellent coverage.py.

Otherwise, everything Alex Martelli wrote is very much on point.

下壹個目標 2024-09-16 07:49:11

doctests 非常适合快速、小型的单元测试,它描述了相关对象的一些基本用法(因为它们出现在文档字符串中,因此还有帮助(无论如何)等)。

我个人发现使用unittest模块进行广泛和更彻底的测试会更有效,现在2.7模块(向后移植到unittest2)具有更方便的断言。您可以使用单元测试框架设置测试套件和任意复杂的场景,并一次性覆盖所有不同的测试(命令行)

coverage.py,由 Ned Batchelder 和 @bstpierre 提到的将与其中任何一个一起使用,我推荐它来查看您对代码进行了测试以及什么没有。您可以将其添加到 CI 系统(即 Hudson 或任何您喜欢使用的系统)中,以了解已覆盖和未覆盖的内容,并且 HTML 报告非常适合查看测试覆盖范围内未命中的内容。 Coverage 支持 Junit xml 输出,许多 CI 系统都知道如何提供图表化的持续结果,让您看到构建随着时间的推移变得更好或更差。

doctests are great for quick, minor unit tests that describe what some of the basic usages of the objects in question, (as they show up in docstrings, and hence help(whatever), etc).

I've personally found extensive and more thorough testing to be more effective using the unittest module, and now the 2.7 module (back ported to unittest2) has even more handy assertions. You can set up test suites and as complex a scenario as you like with the unit testing framework and cover whole swaths of different tests in one go (command-line wise)

coverage.py, by Ned Batchelder and as @bstpierre mentions will work with either of these, and I recommend it for seeing what you've got tested of the code and what doesn't. You can add it into a CI system (i.e. Hudson or whatever you like to use) to keep up on what's covered and not, and the HTML reports are fantastic for seeing what hasn't been hit with testing coverage. Coverage supports Junit xml output, which many CI systems know how to provide charted on-going results to let you see the build getting better or worse over time.

有深☉意 2024-09-16 07:49:11

我同意上述关于 doctest 不扩展的所有观点,并且我更喜欢坚持使用 unittest。

我可以贡献的一个技巧是从处理 __name__ == "__main__ 的代码中调用单元测试,因此如果测试文件作为脚本运行,它将运行其测试。

例如:

#!/usr/bin/env python

"""
Unit tests for the GetFiles.py utility
"""

import unittest
from FileUtilities import getTree

class TestFileUtilities(unittest.TestCase):

   def testGetTree(self):
      """
      Tests that a known tree is found and incidentally confirms
      that we have the tree we expected to use for our current
      sample extraction.
      """
      found = getTree('./anzmeta-dtd', '.pen')
      expected_path_tail = ['ISOdia.pen',
                       'ISOgrk1.pen',
                       'ISOtech.pen']
      for i, full_path in enumerate(found):
         assert full_path.endswith( expected_path_tail[i] ), expected_path_tail[i]

# other tests elided         

if __name__ == "__main__":
   # When this module is executed from the command-line, run all its tests
   unittest.main()

I agree with all the above points raised about doctest not scaling and I prefer to stick with unittest.

One tip I can contribute is to invoke the unit tests from the code handling __name__ == "__main__ so if the test file is run as a script it will run its tests.

eg:

#!/usr/bin/env python

"""
Unit tests for the GetFiles.py utility
"""

import unittest
from FileUtilities import getTree

class TestFileUtilities(unittest.TestCase):

   def testGetTree(self):
      """
      Tests that a known tree is found and incidentally confirms
      that we have the tree we expected to use for our current
      sample extraction.
      """
      found = getTree('./anzmeta-dtd', '.pen')
      expected_path_tail = ['ISOdia.pen',
                       'ISOgrk1.pen',
                       'ISOtech.pen']
      for i, full_path in enumerate(found):
         assert full_path.endswith( expected_path_tail[i] ), expected_path_tail[i]

# other tests elided         

if __name__ == "__main__":
   # When this module is executed from the command-line, run all its tests
   unittest.main()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文