5

我正在用 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"])

问题是这个测试(有时)甚至在我添加依赖处理代码之前就已经工作了。这是因为规范没有说明任务必须以特定的顺序运行。所以即使依赖信息被忽略,正确的顺序也是一个完全有效的选择。

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

4

4 回答 4

2

您处于每个研究人员都在查看一组不完善的数据并试图说出有关它的假设是否正确的情况。

如果运行之间的结果不同,那么多次重新运行将为您提供一个样本,您可以应用统计数据来确定它是否有效。但是,如果一批运行会给你类似的结果,但不同日期的不同批次会给你不同的结果,那么你的不确定性取决于程序本身之外的事件,你需要找到一种方法控制它们,理想情况下,使它们最大限度地提高错误算法的可能性。

这是非确定性的代价;你必须求助于统计数据,你必须得到正确的统计数据。您需要能够以一定的置信度接受假设,并拒绝原假设。如果您可以最大化结果的方差,这需要更少的样本;有不同的 CPU 负载,或 IO 中断,或安排随机休眠的任务。

无论如何,为了定义一个有价值的测试,找出这样的调度程序受到什么影响可能是明智的。

于 2013-04-12T14:05:42.547 回答
1

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

另一种选择是不为非确定性结果编写测试用例。


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

有没有办法编写测试来避免这种“意外”成功?

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

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

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

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

于 2013-04-12T14:03:13.510 回答
1

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

首先,消除任何外部熵源(将您的线程池设置为使用单个线程;使用预先播种的 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,因为这是在测试运行之间不同的熵(即信息)的唯一来源。

于 2013-04-12T14:45:32.733 回答
1

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

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

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

于 2013-04-12T15:55:26.363 回答