集成测试:在`unittest.setUp`中启动阻塞服务器进行测试?

3 投票
3 回答
3176 浏览
提问于 2025-04-15 20:05

我正在用 Thrift 写一个服务,需要进行一些测试,以确保它能正常工作和响应。为了做到这一点,最可靠的方法似乎是使用 unittest 模块。

我想在单元测试的 setUp 方法中以“测试”模式启动服务(也就是在一个特定的“测试”端口上启动,使用“测试”数据等等),但是在这个时候调用 serve() 会阻塞,等待连接。

那么,启动服务的最佳方法是什么,以便测试可以执行,并且可以通过 tearDown 方法干净地关闭服务呢?

3 个回答

0

如果 serve() 这个函数会阻塞,也就是说它会一直占用资源不放,那么最好的办法是在 setUp 里开一个新线程来调用 serve()。这样一来,你就需要想办法在 tearDown 方法里停止 Thrift 的运行。

2

你可以创建一个上下文管理器,这样在后台运行服务器的同时就可以启动你的测试。

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)
7

在单元测试中,unittest提供的“完全隔离”效果非常好,因为它就是为这个目的设计的。但在集成测试中,这种隔离就不一定合适了。我理解为什么有人想把unittest也用在集成测试上,我自己也会这样做,因为可以利用一些特殊的测试工具等等。不过,我意识到把unittest用在集成测试上有点勉强,所以我会尝试用不同的方式来编写代码,而不是像写单元测试那样。

在进行集成测试时,如果需要启动一个服务器,我通常会在模块开始时就启动它,使用一个单独的进程——可以通过subprocess来实现,或者根据你的环境选择其他合适的方法。如果你想在一个独立的节点上运行它,或者其他什么方式——然后我会用atexit来注册一个终止代码,这样当我的测试模块完成后,就会向服务器发送终止请求。这种做法虽然没有单元测试那样“干净分离”,但我觉得对于集成测试来说已经足够好了,而且可以把启动和初始化服务器的开销分摊到多个测试中,这样会更高效。

即使你很想使用setUptearDown,我还是建议使用一个单独的进程(如果你有很多节点可以用,比如在“持续构建农场”这样的环境中)。像@Ned的回答提到的那样,在同一个进程中使用不同的线程,我觉得风险很大——这可能会导致服务器和测试之间产生不必要的交互,隐藏一些错误或者引发其他问题。

如果我理解得没错,你是想在同一个进程甚至同一个线程中运行服务器和测试,这在我看来绝对是个坏主意——当然,这样会阻塞所有操作,除非服务器的代码写得非常特别!-)

撰写回答