我有一个简单的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
处理程序/信号处理程序?另一种模式也是受欢迎的。
答案 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()