Flask 测试客户端和单元测试:模拟全局对象?

Flask test client and unittest: mocking global object?

我有一个使用全局对象的 Flask 应用程序 data_loader

主要的 flask 文件(我们称之为 main.py)如下所示:

app = Flask('my app')
...
data_loader = DataLoader(...)

稍后,这个全局 data_loader 对象在网络服务器的路由方法中被调用:

class MyClass(Resource):
    def get(self):
      data_loader.load_some_data()
      # ... process data, etc

使用unittest, I want to be able to patch the load_some_data() method. I'm using the flask test_client:

from my_module.main import app

class MyTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        cls.client = app.test_client('my test client')

如何在 MyTest 的后续测试中修补 data_loader 方法?我已经尝试过这种方法,但它不起作用(尽管 data_loader 似乎在某些时候被替换):

 @unittest.mock.patch('my_module.main.DataLoader')
 def my_test(self, DataLoaderMock):
     
     data_loader = DataLoaderMock.return_value
     data_loader.my_method.return_value = 'new results (patched)'

     with app.test_client() as client:
         response = client.get(f'/some/http/get/request/to/MyTest/route', 
                               query_string={...})

     # ... some assertions to be tested ...

似乎 data_loader 在 Flask 应用程序中从未真正被取代。

此外,在 Flask 服务器中拥有一个全局变量是否被认为是“良好做法”,或者应用程序是否应该将其存储在其中?

谢谢

关于mockingpatch.object可用于修改对象属性:

@unittest.mock.patch.object(data_loader, 'my_method')
def my_test(self, my_method_mock):
    my_method_mock.return_value = 'new results (patched)'
    with app.test_client() as client:
        response = client.get(f'/some/http/get/request/to/MyTest/route', 
                              query_string={...})

        my_method_mock.assert_called() # ok!

我的解决方案具有有趣的见解:

import unittest
from unittest.mock import patch


class MyTest(unittest.TestCase):
    def test_get(self):
        client = app.test_client('my test client')
        patcher = patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1})
        patcher.start()
        self.assertDictEqual(client.get('/').json, {'status': 1})
        patcher.stop()
        # or
        with patch('{package_here}.{module_here}.DataLoader.load_some_data', return_value={'status': 1}):
            self.assertDictEqual(client.get('/').json, {'status': 1})

关于“良好做法”和全局变量。是的,我在各种项目中都看到了全局变量。但是我不推荐使用全局变量。因为:

  • 它会导致递归导入和依赖地狱。我曾使用大型 Flask 递归导入应用程序。真的很痛。而且你不可能在短时间内解决所有问题。
  • 假设您有一个 mocking 的测试。我认为当你有一个相当大的服务时,重构会更加困难。
  • 单独导入和初始化确实更简单,更可配置。在这种情况下,所有工作都在一个方向 import all dependencies -> load config -> initialization -> run。在其他情况下,您将有 import -> new instance -> new instance -> import -> ....
  • 内存泄漏的另一个原因。

也许全局变量对于独立的 packagesmodules 等来说是不错的方式,但对于项目来说则不然。我还想推荐使用 。这不仅可以让编写测试变得更容易,还可以让您省去一些麻烦。