集成测试:在`unittest.setUp`中启动阻塞服务器进行测试?
我正在用 Thrift 写一个服务,需要进行一些测试,以确保它能正常工作和响应。为了做到这一点,最可靠的方法似乎是使用 unittest
模块。
我想在单元测试的 setUp
方法中以“测试”模式启动服务(也就是在一个特定的“测试”端口上启动,使用“测试”数据等等),但是在这个时候调用 serve()
会阻塞,等待连接。
那么,启动服务的最佳方法是什么,以便测试可以执行,并且可以通过 tearDown
方法干净地关闭服务呢?
3 个回答
如果 serve()
这个函数会阻塞,也就是说它会一直占用资源不放,那么最好的办法是在 setUp
里开一个新线程来调用 serve()
。这样一来,你就需要想办法在 tearDown
方法里停止 Thrift 的运行。
你可以创建一个上下文管理器,这样在后台运行服务器的同时就可以启动你的测试。
if __name__ == '__main__':
with background_server():
print('Server loaded, launching the tests...')
unittest.main(exit=False)
我用来实现 background_server
的代码是:
@contextlib.contextmanager
def background_server():
# Launching the server
pid_server = os.fork()
if not pid_server: # Child code
launch_server() # Blocking call (until signal.SIGINT)
print('Interuption detected, server closed...')
sys.exit() # Closing the process
# HACK: Wait for the server to be launched
while True:
try:
requests.get("http://localhost:5000/", timeout=0.5)
break
except requests.exceptions.ConnectionError:
pass
time.sleep(0.3)
try:
yield
finally:
# Closing the server
os.kill(pid_server, signal.SIGINT)
在单元测试中,unittest
提供的“完全隔离”效果非常好,因为它就是为这个目的设计的。但在集成测试中,这种隔离就不一定合适了。我理解为什么有人想把unittest
也用在集成测试上,我自己也会这样做,因为可以利用一些特殊的测试工具等等。不过,我意识到把unittest
用在集成测试上有点勉强,所以我会尝试用不同的方式来编写代码,而不是像写单元测试那样。
在进行集成测试时,如果需要启动一个服务器,我通常会在模块开始时就启动它,使用一个单独的进程——可以通过subprocess
来实现,或者根据你的环境选择其他合适的方法。如果你想在一个独立的节点上运行它,或者其他什么方式——然后我会用atexit来注册一个终止代码,这样当我的测试模块完成后,就会向服务器发送终止请求。这种做法虽然没有单元测试那样“干净分离”,但我觉得对于集成测试来说已经足够好了,而且可以把启动和初始化服务器的开销分摊到多个测试中,这样会更高效。
即使你很想使用setUp
和tearDown
,我还是建议使用一个单独的进程(如果你有很多节点可以用,比如在“持续构建农场”这样的环境中)。像@Ned的回答提到的那样,在同一个进程中使用不同的线程,我觉得风险很大——这可能会导致服务器和测试之间产生不必要的交互,隐藏一些错误或者引发其他问题。
如果我理解得没错,你是想在同一个进程甚至同一个线程中运行服务器和测试,这在我看来绝对是个坏主意——当然,这样会阻塞所有操作,除非服务器的代码写得非常特别!-)