Python单元测试防止潜在无限循环。

5

我有一个Python函数,如果传递了错误数据,就会陷入无限循环。我想编写一个单元测试来确认它是否能够优雅地处理错误参数。问题在于,如果它不能检测到错误参数,它将不会返回。

对于这种情况,使用线程编写测试是否可行?

import threading, unittest
import mymodule

class CallSuspectFunctionThread(threading.Thread):
    def __init__(self, func, *args):
        self.func = func
        self.args = args
        super(CallSuspectFunctionThread, self).__init__()
    def run(self):
        self.func(*self.args)

class TestNoInfiniteLoop(unittest.TestCase):
    def test_no_infinite_loop(self):
        myobj = mymodule.MyObject()
        bad_args = (-1,0)
        test_thread = CallSuspectFunctionThread(mybj.func_to_test, *bad_args)
        test_thread.daemon = True
        test_thread.start()
        # fail if func doesn't return after 8 seconds
        for i in range(32):
            test_thread.join(0.25)
            if not test_thread.is_alive():
                return
        self.fail("function doesn't return")

我看到的唯一问题是,如果测试失败,我会被卡在这个额外的线程上,可能会消耗CPU和内存资源,而其余的测试仍在执行。另一方面,我已经修复了代码,这里的回归概率非常小,所以我不知道是否有必要包括这个测试。

6个回答

12

您可以添加一个超时装饰器。将测试用例的逻辑与超时机制实现分离是很好的做法,这样可以使您的代码更易读和易于维护。

请参阅 http://pypi.python.org/pypi/timeout


1
我喜欢这个库使用SIGALRM,这似乎比线程更合适。但我注意到它似乎在Windows下无法工作。然而,这并没有直接回答问题,即进行这样的测试是否明智。 - Aryeh Leib Taurog
1
-1:总的来说不会很好用。有一天你的机器比平常更忙,超时时间变长,你需要调试整个系统的工作负载,而不是你的应用程序。这是无法通过测试完成的。 - S.Lott
4
最终用户并不关心程序为什么会挂起-它应尽可能优雅地处理所有情况,包括那些资源不足的情况。例如,对于带有GUI的应用程序而言,在提供反馈给用户操作时需要超过一秒钟是无法接受的。您不必设置一个与函数返回时间相同的超时时间。如果一个函数通常需要0.01秒返回结果,那么设置10秒的超时时间是可以接受的,只要该函数在这段时间内能够被视为可接受的。 - ArtOfWarfare

1
我会添加测试来检测无限循环,因为如果在测试期间出现挂起的情况,那么至少在生产之前就能捕获到错误。如果发生的几率很小(正如你所说),那么我不会担心复杂的测试,但我仍然会测试代码。

0

对于这种情况进行测试是个好主意。当代码经过测试后,你就可以更加自信地保证它的质量。

至于你创建用于测试函数的线程,这里有两种在Python中终止线程的方法。由于你在一个拥有无限循环的函数内部,你将不得不使用第二种方法来跳出循环。


我看到了,但那似乎有点过头了--请原谅我的双关语。如果我尽管S. Lott反对仍然保留测试,我可能会使用timeout装饰器,就像Michał Šrajer建议的那样。 - Aryeh Leib Taurog

0

实际上你不必捕获它。只要在你的测试中,如果出了问题,你肯定会注意到。


0

也许在你的情况下,你可以像被接受的答案所说的那样解决它。这对你来说很好。 然而,在某些情况下(用于集成和冒烟测试,而不是单元测试),你无法排除“永久挂起”的情况。对于这些情况,我喜欢这个超时的解决方案。


-1

你不可能测试这个。如果另一个线程只是“慢”或更糟的是“活锁”(即等待共享资源),那么你所做的任何假设都可能与现实相悖。

你不能耸耸肩说:“它总是在1.5秒内终止,但现在似乎需要更长时间。我会尝试2秒钟,看看是否通过。”这是不可接受的。

你必须始终证明所有循环的终止。所有循环。

任何不足的地方都只是一个设计,它应该永远不会见天日。

如果你不能防止无限循环,那么你的设计还没有完成。

这不是一件可以测试的事情。


好的观点。这就是单元测试的限制。但是如何证明终止呢?在我的情况下,我正在尝试确定一些数学函数的简单属性。边缘情况似乎是曲线描述不良行为的地方(即不收敛/无局部极值)。我承认我可能采取了懒惰的方法;编写测试并继续前进比找出它卡住的地方更容易。 - Aryeh Leib Taurog
1
@Aryeh Leib Taurog:这不是“单元测试”的限制,而是所有测试的限制。您无法测试终止。您必须证明它。如果您正在分析函数,则必须实际进行数学计算以确定哪些约束定义了收敛并拒绝所有无法收敛的初始条件。抱歉,但您必须做数学。 - S.Lott
3
并非所有问题都有闭合解。在这种情况下,确定输入的完整约束集不是一个现实的方法。我的意思只是使用一个循环计数器来停止计算,一旦计算超出了范围。但是,即使是知道什么时候超出了范围,也并不总是简单的事情。 - Aryeh Leib Taurog
@Aryeh Leib Taurog:一个无法被证明终止的循环是错误的。抱歉,这不是一个理论上的立场,而是一个简单的定义问题。你无法测试一个无法被证明终止的循环。有太多的混淆因素使它(a)在技术上是错误的,(b)在实践中是不切实际的。一个循环计数器是微不足道的,可以证明终止。只需添加循环计数器。拜托了。 - S.Lott
@S.Lott:单元测试的整个目的是测试函数应该为真的属性。大多数函数都隐含具有一点,那就是它们应该迅速返回。 - ArtOfWarfare
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接