Правильный способ написания модульных тестов для метода экземпляра с pytest - PullRequest
1 голос
/ 24 апреля 2020

Я хочу написать юнит-тесты для класса с несколькими методами преобразования данных.

Высокий уровень:

class my_class:

    def __init__(self, file):
        # read data out of .yml config file
        config = read_data_from_yml_config(file)
        self.value1 = config["value1"]
        self.value2 = config["value2"]

    def get_and_transform(self):
        data_dict = self.get_data()
        transformed_data = self.transform_data(data_dict)

        return transformed_data

    def get_data(self):
        data_dict = request_based_on_value1(self.value1)
        return data_dict

    def transform_data(self, data_dict):
        trnsf = transform1(data_dict, self.value2)

        return trnsf

Здесь у меня есть несколько вопросов. Здесь главное проверить my_class.transform_data(). Он принимает dict в качестве входных данных, читает его как pandas фрейм данных и выполняет некоторые преобразования.

В моем понимании мне нужно несколько приборов d1, d2, d3, ... (как различные значения для data_dict), которые представляют различные входные данные тестового примера для my_class.transform_data ( ). Поскольку я хочу убедиться, что выходные данные соответствуют ожидаемым, я также определю ожидаемый результат:

o1 # expected output for transform_data(d1)
o2, o3, ... # respectively

Несколько вопросов к этому:

  1. Правильно ли подходит этот подход?
  2. Как и где я могу указать d1, d2, ... и o1, o2, ....? Я мог бы сделать это в test_my_class.py -файле или сохранить d1_sample.pkl, ... в папке tests/. Здесь я бы выбрал минимальный пример для d и o
  3. Поскольку преобразование в transform_data также зависит от атрибута self.value2, как бы я передавал различные значения для value2 без создать экземпляр my_class?

В общем, мне также не совсем ясно, буду ли я тестировать на уровне «объекта» или на уровне «метода». Выше я описал подход «метод» (потому что меня в основном интересуют результаты transform_data). Альтернативой может быть предоставление различных файлов .yml и, таким образом, создание различных тестовых экземпляров my_class.

def yml1():
    config = read_in_yml1()
    return config

# and so on for different configurations.

, а затем для теста:

@pytest.mark.parametrize("test_input, expected", [(yml1, ???), (yml2, ???)])
def test_my_class():
    test_class = my_class(file)

    assert test_class.transform_data == expected

Однако в качестве функции ввода my_class.transform_data() не зависит (напрямую) от содержания yml1, а скорее от ответа my_class.get_data(), что, кажется, не имеет большого смысла. Как бы я проверил для различных входных значений data_dict?

Как правильно написать модульные тесты в этом сценарии?

1 Ответ

1 голос
/ 24 апреля 2020

Я вообще не эксперт, но нахожу вашу проблему интересной, поэтому я постараюсь опубликовать ответ, который является одновременно осторожным и конструктивным:

  1. Ваш подход к проверке что transform_data ведет себя так, как и ожидалось в отношении пар входов и выходов, мне кажется правильным. Модульные тесты подтверждают, что наименьшие компоненты («единицы») в вашем исходном коде ведут себя как положено, и я бы сказал, что поведение ваших трех методов достаточно различно, чтобы сделать эти методы единицами.
  2. Если вы не не знаю, нужно ли объявлять пары входных словарей и ожидаемых выходных данных в тестовом файле или во внешних файлах .pkl, я предполагаю, что эти пары ввода-вывода либо большие (по размеру), либо много (по количеству):
    • В первом случае вы можете объявить входной словарь по умолчанию data_dict и ожидаемый вывод по умолчанию в виде двух приборов, которые вы можете позже monkeypatch внутри тестовой функции. Вы можете параметризовать тестовую функцию с другими значениями, которые соответствуют элементам в словаре data_dict, а затем monkeypatch элементы стандартного data_dict прибора с этими параметризованными значениями.
    • In во втором случае, я думаю, было бы целесообразно сократить количество тестовых случаев до нескольких релевантных и попытаться сохранить их спецификации ввода / вывода в тестовом файле.
  3. You может передать другие значения для value2 путем (опять же) обезьяньего исправления свойства value2 экземпляра my_class. Однако вам необходимо объявить такой экземпляр хотя бы один раз (либо внутри, либо вне вашей функции).

Как я писал выше, тестирование выполняется на уровне метода, а не на объекте. уровень "мне кажется действительным и для объектно-ориентированного программного обеспечения. Предоставление разных файлов .yml и создание разных экземпляров вашего класса (поэтому тестирование на «объектном уровне», как вы сказали) выглядит для меня как первый шаг к выполнению интеграционных тестов. Я могу ошибаться в этом последнем пункте, но, надеюсь, кто-то может улучшить мой ответ по этому или другим пунктам:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...