繁体   English   中英

使用Python单元测试测试可以返回不确定结果的函数

[英]Testing a function that can return non-deterministic results using Python unittest

我正在用Python编写一个小型作业调度程序。 可以为调度程序提供一系列可调用项以及相关性,并且应运行该可调用项,以确保没有任何任务在其任何前任之前运行。

我试图遵循一种测试驱动的方法,并且遇到了测试依赖项处理的问题。 我的测试代码如下所示:

def test_add_dependency(self):
    """Tasks can be added with dependencies"""
    # TODO: Unreliable test, may work sometimes because by default, task
    #       running order is indeterminate.
    self.done = []
    def test(id):
        self.done.append("Test " + id)
    s = Schedule()
    tA = Task("Test A", partial(test, "A"))
    tB = Task("Test B", partial(test, "B"))
    s.add_task(tA)
    s.add_task(tB)
    s.add_dependency(tA, tB)
    s.run()
    self.assertEqual(self.done, ["Test B", "Test A"])

问题在于,甚至在我添加依赖项处理代码之前,该测试(有时)仍然有效。 这是因为规范没有说明必须按特定顺序运行任务。 因此,即使忽略了相关性信息,正确的顺序也是一个完全有效的选择。

有没有一种编写测试的方法来避免这种“偶然”的成功? 在我看来,这是一种相当普遍的情况,尤其是在采用测试驱动的“在测试失败之前不要编写代码”的方法时。

您正处于每位研究人员查看不完整数据的集合并试图说出有关其假设是否正确的情况。

如果两次运行之间的结果不同,那么多次运行将为您提供一个样本,您可以应用统计信息来确定其是否正常运行。 但是,如果一批运行将为您提供相似的结果,但在不同的日期运行另一批将为您提供不同的结果,则您的不确定性取决于程序本身之外的事件,因此您需要找到一种方法来控制它们,理想情况下是这样,以使它们最大化触发错误算法的机会。

这就是不确定性的代价; 您必须求助于统计信息,并且必须正确获取统计信息。 您需要能够以一定的置信度接受假设,并且还要拒绝原假设。 如果可以最大程度地提高结果的方差,则需要较少的样本。 具有变化的CPU负载或IO中断,或安排具有随机睡眠的任务。

无论如何,为了定义一个有价值的测试,建议找出这种调度程序受什么影响。

一种选择是出于测试目的而使用Schedule类的其他确定性版本(或添加选项以使现有版本具有确定性),但这可能会破坏单元测试的目的。

另一个选择是不要为不确定的结果而编写测试用例。


不过,总的来说,您问题的答案...

有没有一种编写测试的方法来避免这种“偶然”的成功?

...可能是“否”,除了编写它们时要特别警惕。 尽管如果您有足够的警惕性来避免编写有问题的测试用例,并且首先将这种警惕性应用于编写代码,那么可以说,您甚至不需要单元测试。 ;-)

如果单元测试的重点是检测代码中的错误,那么如何检测单元测试中的错误?

您可以为单元测试编写“元”单元测试,但是然后如何检测“元”单元测试中的错误? 等等...

现在,这并不是说单元测试没有用,但是孤立地证明它们还不足以“证明”代码是“正确的”。 在实践中,我发现基于对等的代码审查是检测代码缺陷的一种更为有效的方法。

这种策略在很多时候都有效:

首先,消除任何外部熵源(将线程池设置为使用单个线程;使用预植入的PRNG来模拟任何RNG等)。然后,重复进行测试以产生输出的每种组合,仅更改机器的输入在测试中:

from itertools import permutations
def test_add_dependency(self):
    """Tasks can be added with dependencies"""
    for p in permutations("AB"):
        self.done = []
        def test(id):
            self.done.append("Test " + id)
        s = Schedule(threads=1)
        tasks = {id: Task("Test " + id, partial(test, id)) for id in "AB"}
        s.add_task(tasks['A'])
        s.add_task(tasks['B'])
        s.add_dependency(tasks[p[0]], tasks[p[1]])
        s.run()
        self.assertEqual(self.done, ["Test " + p[1], "Test " + p[0]])

如果Schedule无法使用add_dependency的信息,则该测试将失败,因为这是add_dependency测试之间唯一不同的熵(即信息)来源。

我建议您在编写测试之前确定需要测试的内容。

在上面的代码示例中,正在测试的是调度程序生成了特定的任务序列,即使根据您对调度程序的描述,实际的序列是不确定的,因此该测试并不能真正保证代码:有时会通过,有时不会通过,当它通过时,只是偶然。

另一方面,更有价值的测试可能是在结果中断言任务的存在(或不存在),而不断言有关其位置的任何信息:“处于集合中”与“处于数组位置”

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM