6

我正在使用 Python 的内置unittest模块,我想编写一些不重要的测试。

我的意思是,如果我的程序通过了这样的测试,那就太好了!但是,如果它没有通过,那并不是真正的问题,程序仍然可以工作。

例如,我的程序设计为使用自定义类型“A”。如果它不能与“A”一起工作,那么它就坏了。然而,为了方便起见,它的大部分也应该与另一种类型“B”一起使用,但这不是强制性的。如果它不能与“B”一起工作,那么它不会被破坏(因为它仍然可以与“A”一起工作,这是它的主要目的)。无法使用“B”并不重要,我只会错过我可以拥有的“奖励功能”。

另一个(假设的)示例是在编写 OCR 时。该算法应该能识别出测试中的大多数图像,但如果其中一些图像失败也没关系。(不,我不是在写 OCR)

有没有办法在 unittest(或其他测试框架)中编写非关键测试?

4

11 回答 11

8

实际上,在这种情况下,我可能会使用 print 语句来指示失败。更正确的解决方案是使用警告:

http://docs.python.org/library/warnings.html

但是,您可以使用日志记录工具生成更详细的测试结果记录(即设置“B”类故障以将警告写入日志)。

http://docs.python.org/library/logging.html

编辑:

我们在 Django 中处理这个问题的方式是,我们有一些我们预计会失败的测试,还有一些我们会根据环境跳过的测试。由于我们通常可以预测测试应该失败还是通过(即,如果我们不能导入某个模块,系统没有它,因此测试将无法工作),我们可以智能地跳过失败的测试。这意味着我们仍然运行每一个将通过的测试,并且没有“可能”通过的测试。单元测试在可预测的情况下最有用,并且能够在我们运行之前检测测试是否应该通过使这成为可能。

于 2009-09-10T17:16:11.973 回答
4

单元测试中的断言是二元的:它们将工作或失败,没有中期。

鉴于此,要创建那些“非关键”测试,当您不希望测试失败时,您不应该使用断言。您应该谨慎执行此操作,以免影响测试的“有用性”。

我对您的 OCR 示例的建议是,您使用一些东西在测试代码中记录成功率,然后创建一个断言,例如:“assert success_rate > 8.5”,这应该会产生您想要的效果。

于 2009-09-10T17:27:20.130 回答
3

我不完全确定 unittest 是如何工作的,但大多数单元测试框架都有类似于类别的东西。我想您可以对此类测试进行分类,将它们标记为忽略,然后仅在您对它们感兴趣时才运行它们。但是我从经验中知道,忽略测试很快就会变成......只是忽略了没有人运行过的测试,因此编写它们是浪费时间和精力。

我的建议是让你的应用做,或者不做,没有尝试。

于 2009-09-10T17:17:30.217 回答
3

从您链接的单元测试文档中:

除了 unittest.main() 之外,还有其他方法可以运行测试,具有更好的控制级别、更少的简洁输出,并且不需要从命令行运行。例如,最后两行可以替换为:

suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)

在您的情况下,您可以TestSuite为关键和非关键测试创建单独的实例。您可以使用命令行参数控制将哪个套件传递给测试运行器。测试套件还可以包含其他测试套件,因此您可以根据需要创建大层次结构。

于 2009-09-10T20:16:41.317 回答
3

谢谢你的好答案。没有一个答案是真正完整的,所以我在这里写了所有对我有帮助的答案的组合。如果您喜欢这个答案,请投票支持对此负责的人。

结论

单元测试(或至少unittest模块中的单元测试)是二进制的。正如Guilherme Chapiewski 所说他们要么工作要么失败,没有中期。

因此,我的结论是单元测试并不完全适合这项工作。似乎单元测试更关心“保持一切正常,预计不会失败”,因此我不能(或者不容易)进行非二进制测试。

因此,如果我试图改进算法或实现,单元测试似乎不是正确的工具,因为单元测试无法告诉我一个版本与另一个版本相比有多好(假设它们都正确实现,那么两者都将通过所有单元测试)。

我的最终解决方案

我的最终解决方案基于ryber 的想法wcoenen answer中显示的代码。我基本上是在扩展默认设置TextTestRunner并使其不那么冗长。然后,我的主要代码调用了两个测试套件:使用标准TextTestRunner的关键套件,以及使用我自己的不太详细版本的非关键套件。

class _TerseTextTestResult(unittest._TextTestResult):
    def printErrorList(self, flavour, errors):
        for test, err in errors:
            #self.stream.writeln(self.separator1)
            self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
            #self.stream.writeln(self.separator2)
            #self.stream.writeln("%s" % err)


class TerseTextTestRunner(unittest.TextTestRunner):
    def _makeResult(self):
        return _TerseTextTestResult(self.stream, self.descriptions, self.verbosity)


if __name__ == '__main__':
    sys.stderr.write("Running non-critical tests:\n")
    non_critical_suite = unittest.TestLoader().loadTestsFromTestCase(TestSomethingNonCritical)
    TerseTextTestRunner(verbosity=1).run(non_critical_suite)

    sys.stderr.write("\n")

    sys.stderr.write("Running CRITICAL tests:\n")
    suite = unittest.TestLoader().loadTestsFromTestCase(TestEverythingImportant)
    unittest.TextTestRunner(verbosity=1).run(suite)

可能的改进

了解是否有任何具有非二进制测试的测试框架应该仍然有用,就像Kathy Van Stone 建议的那样。可能我不会在这个简单的个人项目中使用它,但它可能对未来的项目有用。

于 2009-09-14T19:42:08.257 回答
2

Python 2.7(和 3.1)增加了对跳过某些测试方法或测试用例的支持,以及将某些测试标记为预期失败

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

标记为预期失败的测试不会被视为 TestResult 上的失败。

于 2010-10-22T18:41:31.357 回答
1

有一些测试系统允许警告而不是失败,但 test_unit 不是其中之一(我不知道哪些可以,顺便说一句),除非你想扩展它(这是可能的)。

您可以进行测试,以便它们记录警告而不是失败。

处理此问题的另一种方法是分离测试并仅运行它们以获取通过/失败报告并且没有任何构建依赖项(这取决于您的构建设置)。

于 2009-09-10T18:08:33.433 回答
0

看看鼻子:http ://somethingaboutorange.com/mrl/projects/nose/0.11.1/

有很多命令行选项可用于选择要运行的测试,并且您可以保留现有的单元测试测试。

于 2009-09-10T18:33:06.743 回答
0

另一种可能性是创建一个“B”分支(您正在使用某种版本控制,对吗?)并在那里对“B”进行单元测试。这样,您可以保持发布版本的单元测试干净(看,所有点!),但仍然有针对 B 的测试。如果您使用的是现代版本控制系统,如 git 或 mercurial(我偏爱 mercurial),分支/克隆和合并是微不足道的操作,所以我建议这样做。

但是,我认为您正在将测试用于他们不打算做的事情。真正的问题是“'B' 起作用对你来说有多重要?” 因为您的测试套件中应该只包含您关心它们是否通过或失败的测试。测试,如果它们失败,则意味着代码已损坏。这就是为什么我建议只在“B”分支中测试“B”,因为那将是您正在开发“B”功能的分支。

如果您愿意,可以使用记录器或打印命令进行测试。但是,如果您不太关心它在您的单元测试中被标记,那么我会严重质疑您是否足够关心来测试它。此外,这增加了不必要的复杂性(设置调试级别的额外变量,完全独立但在同一空间内运行的多个测试向量,导致潜在的冲突和错误等)。除非你正在开发“Hello, World!” 应用程序,我怀疑您的问题集已经足够复杂,而不会增加额外的不必要的复杂性。

于 2009-09-10T20:36:18.890 回答
0

使用子测试:

with self.subTest('description of subtest here'):
   self.assertEqual(non_critical_value, 3)

# lines below will be executed even if the assertion above fails

我很惊讶之前没有提到它。我没有看到很多人使用它,这是一个很棒的功能。

于 2021-06-08T15:08:48.733 回答
-1

您可以编写测试,以便他们计算成功率。使用 OCR,您可以输入代码 1000 幅图像并要求 95% 是成功的。

如果您的程序必须与类型 A 一起工作,那么如果失败,则测试失败。如果不需要和 B 一起工作,那么做这样的测试有什么价值?

于 2009-09-10T17:19:34.393 回答