(单位)测试python信号处理程序

时间:2018-03-25 09:38:32

标签: python python-3.x unit-testing exception signals

我有一个简单的Python服务,其中有一个循环可以无限地执行某些操作。在各种信号上,调用sys.exit(0),这会导致SystemExit被提升,然后如果可以的话就会发生一些清理。

在测试中,即标准unittest.TestCase,我想测试这个清理是否发生并且循环退出。但是,我仍然坚持要触发信号/ SystemExit

# service.py
import signal
import sys
import time

def main():

    def signal_handler(signalnum, _):
        # How to get this to block to run in a test?
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    while True:
        try:
            print("Some action here")
            time.sleep(10)
        except SystemExit:
            # How to get this to block to run in a test?
            print("Some cleanup")
            break

if __name__ == '__main__':
    main()

代码如何在测试环境中进入SystemExit处理程序/信号处理程序?另一种模式也是受欢迎的。

2 个答案:

答案 0 :(得分:1)

让我们重构一下,以便更容易测试:

def loop():
    try:
        print("Some action here")
    except:
        # clean up and re-raise
        print("Some cleanup")
        raise

def main():

    def signal_handler(signalnum, _):
        # How to get this to block to run in a test?
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    while True:
        try:
            loop_body()
            time.sleep(10)
        except SystemExit:
            break

if __name__ == '__main__':
    main()

但这不允许轻松测试信号处理代码。但是,这个数量很小,很少改变,并且很大程度上取决于环境,手动测试是可能的,甚至可能更好。

为清楚起见,使用上下文处理程序可能很有用,这在设置/关闭代码时通常是个好主意。你没有提到设置代码,但我的Crystall Ball(tm)告诉我它存在。然后它可以这样调用:

try:
    with my_service() as service:
        while True:
            service.run()
            sleep(10)
except SystemExit:
    # perform graceful shutdown on signal
    pass

我会将该上下文管理器的实现留给您,但请查看contextlib,这样可以轻松有趣。

答案 1 :(得分:0)

您可以在主线程中收到一些延迟后从另一个线程触发SIGINT(或任何信号)。然后,您可以像在任何其他测试中一样断言其效果,如下所示。

import os
import signal
import time
import threading
import unittest
from unittest.mock import (
    Mock,
    patch,
)

import service

class TestService(unittest.TestCase):

    @patch('service.print')
    def test_signal_handling(self, mock_print):

        pid = os.getpid()

        def trigger_signal():
            while len(mock_print.mock_calls) < 1:
                time.sleep(0.2)
            os.kill(pid, signal.SIGINT)

        thread = threading.Thread(target=trigger_signal)
        thread.daemon = True
        thread.start()

        service.main()

        self.assertEqual(mock_print.mock_calls[1][1][0], 'Some cleanup')


if __name__ == '__main__':
    unittest.main()