我正在用Python编写一个小作业调度程序。调度程序可以被赋予一系列callables和依赖项,并且应该运行callables,确保在任何前任之前没有任务运行。
我正在尝试遵循测试驱动的方法,并且我遇到了测试依赖项处理的问题。我的测试代码如下所示:
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"])
问题是这个测试(有时)甚至在我添加依赖项处理代码之前就已经工作了。这是因为规范没有说明必须以特定顺序运行任务。因此,即使忽略依赖关系信息,正确的顺序也是完全有效的选择。
有没有办法编写测试以避免这种“意外”的成功?在我看来,这是一种相当普遍的情况,特别是在采用测试驱动的“不用没有测试失败的方法编写代码”的方法时。
答案 0 :(得分:2)
每个研究人员都在查看不完整数据的集合,并试图说出关于它的假设是否正确。
如果结果在运行之间有所不同,那么多次重新运行将为您提供一个样本,您可以应用统计数据来确定它是否正常工作。但是,如果一批运行会给你类似的结果,但是不同日期的不同批次会给你一个不同的结果,那么你的非决定论取决于程序本身以外的事件,你需要找到一种方法控制它们,理想情况下,它们最大限度地提高了绊倒坏算法的机会。
这是非决定论的代价;你必须求助于统计数据,你必须得到正确的统计数据。您需要能够接受具有一定置信水平的假设,并且还拒绝零假设。如果您可以最大化结果的方差,则需要更少的样本;具有不同的CPU负载或IO中断,或安排具有随机休眠的任务。
为了定义一个有价值的测试,找出这样的调度程序受影响的可能是可取的。
答案 1 :(得分:1)
一种选择是使用Schedule
类的不同的确定性版本(或添加选项以使现有版本具有确定性)用于测试目的,但这可能会破坏单元测试的目的。
另一个选择是不要为非确定性结果编写测试用例。
总的来说,问题的答案是......
有没有办法编写测试以避免这种“偶然”的成功?
......可能是“不”,其他在写作时特别警惕。虽然如果你有能力保持警惕以避免编写有问题的测试用例,并且你首先应用警惕编写代码,那么,可以说,你甚至不需要单元测试。 ; - )
如果单元测试的目的是检测代码中的错误,那么如何检测单元测试中的错误?
您可以为单元测试编写'meta'单元测试,但是如何检测'meta'单元测试中的错误?等等...
现在,这并不是说单元测试不是很有用,但它们并不足以“证明”代码是“正确的”。在实践中,我发现基于对等的代码审查是检测代码中缺陷的更有效方法。
答案 2 :(得分: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
中的信息,则此测试将失败,因为这是测试运行之间不同的唯一熵(即信息)来源。
答案 3 :(得分:1)
我建议您在编写测试之前确定需要测试的内容。
在上面的代码示例中,正在测试的是调度程序生成特定的任务序列,即使实际序列根据您对调度程序的描述是不确定的,因此测试实际上并不提供任何关于代码的保证:有时它会通过,有时它不会通过,当它发生时,它只是偶然的。
另一方面,更有价值的测试可能是在结果中断言任务的存在(或不存在)而不断言他们的位置:“在集合中”vs“在阵列位置”