如何通过测试修改class数据?

How to modify class data through tests?

我有这个 luigi 任务,它需要的输入是 'test_file.txt'。我想通过测试 class 来更改所需的输入。我需要更改输入,以便我可以使用文件测试 class 的功能。我尝试了下面的代码,但打印的结果仍然是初始路径-'test_file.txt'。如何仅在测试中更改路径? (得到 'data.json' 作为打印结果。

import pytest
import luigi


class LuigiToBeTested(luigi.ExternalTask):
    def requires(self):
        return luigi.LocalTarget("test_file.txt")


def test_Luigi():
    class_instance = LuigiToBeTested()
    class_instance.requires().path = 'data.json'
    print('/////', class_instance.requires().path) #to get data.json

这里有 3 个解决方案供您选择

  1. test_luigi_1 - 拦截(通过函数)所有调用以初始化 luigi.LocalTarget 的新对象并替换输入文件名。

  2. test_luigi_2 - 与 1 相同,但通过子类

  3. test_luigi_3 - 不要像 1 和 2 中那样进行修补,而是通过允许 dependency injection 重新设计您的源代码以使其可测试。这是更可扩展的方式,也是更适合未来的方式。

    一个。 Ask for things, Don't look for things (aka Dependency Injection / Law of Demeter)

    b。 Fixing the Client API: Dependency Injection

    c。还有更多关于为什么这是首选方式的参考资料。

import luigi
import pytest


class LuigiToBeTested(luigi.ExternalTask):
    def requires(self):
        return luigi.LocalTarget("test_file.txt")


class LuigiToBeTested2(luigi.ExternalTask):
    def requires(self, file_name):
        return luigi.LocalTarget(file_name)


@pytest.fixture
def amend_local_target_func_based(mocker):  # The <mocker> fixture is from pytest-mock. Install via <pip install pytest-mock>.
    orig = luigi.LocalTarget

    def fake(file_name):
        print(f"Intercepted initialization of luigi.LocalTarget via function. The file {file_name} might be replaced.")

        # If you only want to replace 'test_file.txt' but not the other files, then use this.
        # Otherwise, remove this if and just read from 'data.json' always.
        if file_name == 'test_file.txt':
            file_name = 'data.json'

        return orig(file_name)

    mocker.patch('luigi.LocalTarget', new=fake)



@pytest.fixture
def amend_local_target_class_based(mocker):
    class LocalTargetStub(luigi.LocalTarget):
        def __init__(self, file_name):
            print(f"Intercepted initialization of luigi.LocalTarget via subclass. The file {file_name} might be replaced.")

            # If you only want to replace 'test_file.txt' but not the other files, then use this.
            # Otherwise, remove this if and just read from 'data.json' always.
            if file_name == 'test_file.txt':
                file_name = 'data.json'

            super().__init__(file_name)

    mocker.patch('luigi.LocalTarget', new=LocalTargetStub)


def test_luigi_1(amend_local_target_func_based):
    class_instance = LuigiToBeTested()
    print('/////', class_instance.requires().path) #to get data.json


def test_luigi_2(amend_local_target_class_based):
    class_instance = LuigiToBeTested()
    print('/////', class_instance.requires().path) #to get data.json


def test_luigi_3():
    class_instance = LuigiToBeTested2()
    print('/////', class_instance.requires('data.json').path) #to get data.json
$ pytest -q -rP
================================================================================================= PASSES ==================================================================================================
______________________________________________________________________________________________ test_luigi_1 _______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Intercepted initialization of luigi.LocalTarget via function. The file test_file.txt might be replaced.
///// data.json
______________________________________________________________________________________________ test_luigi_2 _______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Intercepted initialization of luigi.LocalTarget via subclass. The file test_file.txt might be replaced.
///// data.json
______________________________________________________________________________________________ test_luigi_3 _______________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
///// data.json
3 passed, 1 warning in 0.07s