#python #pandas #pytest
#python #pandas #pytest
Вопрос:
Каков наилучший способ тестирования цепочки обработки фреймов данных pandas? Я удалил файл сценария и тестовый файл ниже, чтобы вы могли понять, что я имею в виду.
Я запутался в передовой практике, моя единственная руководящая интуиция — сделать тесты так, чтобы они могли выполняться в любом порядке, ограничить количество раз, когда csv загружается с диска, а также убедиться, что каждая точка в цепочке не изменяет приспособление. Каждый шаг процесса зависит от предыдущих шагов, поэтому модульное тестирование каждого узла похоже на тестирование накопления обработки до этой точки в конвейере. Пока я выполняю миссию, но, похоже, происходит много дублирования кода, потому что я постепенно создаю конвейер в каждом тесте.
Каков способ тестирования такого скрипта на Python?
Это отключенный файл обработки данных:
#main_script.py
def calc_allocation_methodology(df_row):
print('calculating allocation methodoloyg')
return 'simple'
def flag_data_for_the_allocation_methodology(df):
allocation_methodology = df.apply(calc_allocation_methodology, axis=1)
df.assign(allocation_methodology=allocation_methodology)
print('flagging each row for the allocation methodoloyg')
return df
def convert_repeating_values_to_nan(df):
'keep one value and nan the rest of the values'
print('convert repeating values to nan')
return df
def melt_and_drop_accounting_columns(df):
print('melt and drop accounting columns')
print(f'colums remaining: {df.shape[0]}')
return df
def melt_and_drop_engineering_columns(df):
print('melt and drop engineering columns')
print(f'colums remaining: {df.shape[0]}')
return df
def process_csv_to_tiny_format(df):
print('process the entire pipeline')
return (df
.pipe(flag_data_for_the_allocation_methodology)
.pipe(convert_repeating_values_to_nan)
.pipe(melt_and_drop_accounting_columns)
.pipe(melt_and_drop_engineering_columns)
)
Это тестовый файл, который был удален
#test_main.py
from pytest import fixture
import main_script as main
import pandas as pd
@fixture(scope='session')
def df_from_csv()
return pd.load_csv('database_dump.csv')
@fixture
def df_copy(df_from_csv):
df = df_from_csv.copy()
return df
def test_expected_flag_data_for_the_allocation_methodology(df_copy)
df = df_copy
node_to_test = df.pipe(main.flag_data_for_the_allocation_methodology)
assert True
def test_convert_repeating_values_to_nan(df_copy)
df = df_copy
node_to_test = df.pipe(main.flag_data_for_the_allocation_methodology).pipe(main.convert_repeating_values_to_nan)
assert True
def test_melt_and_drop_accounting_columns(df_copy)
df = df_copy
node_to_test = (df
.pipe(main.flag_data_for_the_allocation_methodology)
.pipe(main.convert_repeating_values_to_nan)
.pipe(main.melt_and_drop_accounting_columns))
assert True
def test_melt_and_drop_engineering_columns(df_copy)
df = df_copy
node_to_test = (df
.pipe(main.flag_data_for_the_allocation_methodology)
.pipe(main.convert_repeating_values_to_nan)
.pipe(main.melt_and_drop_accounting_columns)
.pipe(main.melt_and_drop_engineering_columns))
assert True
def test_process_csv_to_tiny_format(df_from_csv):
df = df_from_csv.copy()
tiny_data = main.process_csv_to_tiny_format(df)
assert True
Комментарии:
1. IMO, модульные тесты отделены от данных, которые вы действительно используете. Они должны выполняться на небольших, предопределенных и неизменных данных, которые в идеале охватывают все возможные крайние случаи. Вы запускаете свои функции на этих конкретных входных данных и проверяете правильность всех выходных данных. Если он проходит этот небольшой тестовый набор, это дает уверенность в том, что ваша функция будет правильно обрабатывать новые данные, которые вы обрабатываете через свой конвейер.
pandas
в их библиотеке есть целый набор тестов . Вы можете видеть, что они вводят входные данные вручную2. предположим, что модульные тесты отделены от данных, и я тестирую только небольшой, заранее определенный, неизменный набор данных, который фиксирует все возможные варианты использования. Согласитесь ли вы, что тестовые заглушки расположены правильно? Или это запах кода для копирования кода канала из одного теста в другой, только добавляя новый канал в конце.
3. Итак, я хотел сказать, что вам не нужны все эти каналы, потому что, например, при тестировании
melt_and_drop_engineering_columns
вы не должны отправлять своиdf
данные через все эти другие процессы или использовать вывод, который зависит от их запуска в первую очередь. В принципеmelt_and_drop_engineering_columns
, должна быть возможность запускать независимо без ссылки на что-либо еще. Вы можете подумать, что это странно, потому что, возможноmelt_and_drop_engineering_columns
, требуется какой-то очень специфический вывод из предыдущей функции. Но опять же, у вас будет модульный тест для этой предыдущей функции4. И поэтому модульный тест для предыдущей функции (т. Е.
melt_and_drop_accounting_columns
) Должен гарантировать, что ваш вывод точно соответствует форме, которая должна быть для следующей функции5. хорошо, похоже, мне действительно нужна только вспомогательная функция, которая загружает ожидаемый df из плоского файла, а затем сравнивает ожидаемый df с результирующим df