Модульное тестирование трубопроводов для сбора данных, состоящих из однопоточных функций

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, чтобы убедиться, что вы ничего не забыли.

Во всяком случае, на ваш код. Ваш второй пример кода кажется более конвейерным. Но это само по себе не дает преимуществ функционального программирования. На практике функциональное программирование означает обеспечение совместимости функций с другими функциями путем определения их совместимости только с точки зрения их входов и выходов. Если внутри функции есть скрытые побочные эффекты, то, несмотря на то, что ее вход / выход совпадают с другой функцией, вы не можете знать, совместимы ли они до времени выполнения. Однако, если две функции не имеют побочных эффектов и соответствуют выводу-вводу, вы можете конвейеризовать или скомпоновать их, не беспокоясь о неожиданных результатах.

Кейси Спикман
источник