Тестування блоку для трубопроводів для обміну даними, що складається з однолінійних функцій


10

Читаючи практичне вступ до функціонального програмування Мері Роуз Кук , вона наводить як приклад антидіаграму

def format_bands(bands):
    for band in bands:
        band['country'] = 'Canada'
        band['name'] = band['name'].replace('.', '')
        band['name'] = band['name'].title()

з тих пір

  • функція виконує більше ніж одне
  • назва не є описовим
  • це має побічні ефекти

В якості запропонованого рішення вона пропонує конвеєрувати анонімні функції

pipeline_each(bands, [call(lambda x: 'Canada', 'country'),
                      call(lambda x: x.replace('.', ''), 'name'),
                      call(str.title, 'name')])

Однак, мені здається, це є і меншою мірою бути ще менш перевіряемою; принаймні format_bands може мати тест одиниці, щоб перевірити, чи він робить те, що призначено, але як протестувати трубопровід? Або ідея про те, що анонімні функції настільки зрозумілі, що їх не потрібно перевіряти?

Моя реальна програма для цього полягає у намаганні зробити свій pandasкод більш функціональним. У мене часто є якийсь трубопровід всередині функції "заглушки"

def munge_data(df)
     df['name'] = df['name'].str.lower()
     df = df.drop_duplicates()
     return df

Або переписування в стилі конвеєра:

def munge_data(df)
    munged = (df.assign(lambda x: x['name'].str.lower()
                .drop_duplicates())
    return munged

Будь-які пропозиції щодо найкращих практик у подібній ситуації?


4
Ці індивідуальні лямбда-функції занадто малі для одиничного тесту. Перевірте кінцевий результат. Інакше кажучи, анонімні функції не перевіряються одиницею, тому не записуйте функцію як анонімну функцію, якщо ви плануєте індивідуально перевірити її.
Роберт Харві

Відповіді:


1

Я думаю, ви пропустили, мабуть, більш важливу частину виправленого прикладу книги. Більш фундаментальна зміна коду полягає в методі, що діє на всіх значеннях у списку, до роботи над одним елементом.

Вже є такі функції, як iter(у даному випадку названі pipeline_foreach), які виконують задану операцію над усіма елементами списку. Не потрібно було дублювати це forциклом. Також використання відомої операції зі списком робить ваш намір зрозумілим. З mapвами перетворюєте цінності. З iterвами виконуються побічні ефекти з кожним елементом. З forциклом ви ... ну, ви насправді не знаєте, поки не переглянете це.

Приклад виправленого коду все ще не дуже функціональний, тому що він (наскільки я можу сказати) мутує значення у списку, не повертаючи їх, запобігаючи подальшій конфігурації або складі функції. Функціонально кращий метод mapстворив би новий список діапазонів з оновленими countryта name. Тоді ви можете передати цей вихід на наступну функцію або скласти mapіншу функцію, яка взяла список діапазонів. З iter, це як трубопровідний глухий кут.

Я думаю, що код кінцевого результату має невеликі функції, які занадто банальні, щоб не турбувати тестування. Зрештою, вам не потрібно буде писати одиничні тести проти replaceабо title. Тепер, можливо, ви хочете скласти їх разом у свою власну функцію та тестовий блок, щоб бажане поєднання було досягнуто на одному елементі. Сам я, певно, просто змінив format_bandsби format_bandсингулярне, скинув цикл for і подзвонив pipeline_each(bands, format_band). Тоді ви можете протестувати format_band, щоб переконатися, що ви щось не забули.

У будь-якому випадку, на ваш код. Ваш другий приклад коду здається більш "конвеєрним". Але це одне не дає переваг функціонального програмування. На практиці функціональне програмування означає забезпечення сумісності функцій з іншими функціями, визначаючи їх сумісність лише з точки зору їх входів та виходів. Якщо всередині функції є приховані побічні ефекти, то, незважаючи на те, що вхід / вихід вибудовується з іншою функцією, ви не можете знати, чи сумісні вони вони до часу запуску. Якщо, однак, дві функції не мають побічних ефектів і збігаються з виводу на вхід, тоді ви можете скласти конвеєр або скласти їх, не турбуючись про несподівані результати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.