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

时间:2021-07-23 10:39:16

标签: flask mocking python-unittest

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

主要的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,我希望能够修补 load_some_data() 方法。我正在使用烧瓶 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')

如何在 data_loader 的后续测试中修补 MyTest 方法?我已经尝试过这种方法,但它不起作用(尽管 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 ...

在 Flask 应用中似乎从未真正取代 data_loader

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

谢谢

1 个答案:

答案 0 :(得分:1)

关于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 一个 global variables 的测试。我认为当您拥有相当大的服务时,重构会更加困难。
  • 单独的导入和初始化确实更简单且更易于配置。在这种情况下,所有工作都在一个方向 import all dependencies -> load config -> initialization -> run。在其他情况下,您将拥有 import -> new instance -> new instance -> import -> ...
  • 内存泄漏的另一个原因。

对于独立的 packagesmodules 等,也许全局变量不是坏方法,但对于项目则不是。我也想推荐使用 some additional tools。这不仅会让编写测试变得更容易,而且还能让你省去麻烦。