pytest - 如果在方法内部调用了 class 的方法,如何断言

pytest - how to assert if a method of a class is called inside a method

我想知道如何知道 class 的方法是否在方法内部被调用。

单元测试代码如下:

# test_unittes.py file

def test_purge_s3_files(mocker):
    args = Args()
    

    mock_s3fs = mocker.patch('s3fs.S3FileSystem')
    segment_obj = segments.Segmentation()
    segment_obj.purge_s3_files('sample')

    mock_s3fs.bulk_delete.assert_called()

purge_s3_file 方法中 bulk_delete 被调用,但是在断言时它说该方法应该被调用但没有被调用!

mocker = <pytest_mock.plugin.MockerFixture object at 0x7fac28d57208>

    def test_purge_s3_files(mocker):
        args = Args()
    
        mock_s3fs = mocker.patch('s3fs.S3FileSystem')
        segment_obj = segments.Segmentation(environment='qa',
                        verbose=True,
                        args=args)
        segment_obj.purge_s3_files('sample')
    
>       mock_s3fs.bulk_delete.assert_called()
E       AssertionError: Expected 'bulk_delete' to have been called.

我不知道如何测试这个以及如果调用该方法如何断言!


您可以在下面找到正在测试的方法:

# segments.py file

import s3fs

def purge_s3_files(self, prefix=None):
    bucket = 'sample_bucket'
    files = []
    fs = s3fs.S3FileSystem()
   
    if fs.exists(f'{bucket}/{prefix}'):
        files.extend(fs.ls(f'{bucket}/{prefix}'))
    else:
        print(f'Directory {bucket}/{prefix} does not exist in s3.')
    
    print(f'Purging S3 files from {bucket}/{prefix}.')
    print(*files, sep='\n')
    fs.bulk_delete(files)

Python 模拟测试取决于模拟的使用位置。所以你在导入的地方模拟了函数调用。

例如

app/r_executor.py

def r_execute(file):
  # do something

但实际的函数调用发生在另一个命名空间中 ->

analyse/news.py

from app.r_executor import r_execute

def analyse():
  r_execute(file)

为了模拟这个我应该使用

mocker.patch('analyse.news.r_execute')
# not mocker.patch('app.r_executor.r_execute')

您面临的问题是您正在设置的模拟正在模拟 class,并且您没有使用 实例 来使用和检查您的嘲笑。简而言之,这个 应该 解决你的问题(下面可能有另一个问题进一步解释):

m = mocker.patch('s3fs.S3FileSystem')
mock_s3fs = m.return_value  # (or mock_s3())

可能第二个问题是您没有引用正确的路径到您想要模拟的内容。

根据您的项目根目录(考虑您的评论 ),您的 mock 需要被相应地引用:

mock('app.segments.s3fs.S3FileSystem')

根据经验,您总是希望 mock where you are testing

如果您能够使用调试器(或输出到您的控制台),您将(希望 :))看到您预期的调用计数将在模拟对象的 return_value 内。这是我的调试器使用您的代码的片段:

您会看到 call_count 属性设置为 1。回到我在答案开头提到的内容,通过进行更改,您现在可以使用预期的 mock_s3fs.bulk_delete_assert_called().

将它们放在一起,经过修改的工作测试按预期运行(请注意,您还应该设置预期的行为并断言您在其中调用的其他 fs 方法):

def test_purge_s3_files(mocker):
    m = mocker.patch("app.segments.s3fs.S3FileSystem")
    mock_s3fs = m.return_value  # (or m())

    segment_obj = segments.Segmentation(environment='qa',
                    verbose=True,
                    args=args)
    segment_obj.purge_s3_files('sample')
    
    mock_s3fs.bulk_delete.assert_called()