Должны ли мы изменить сигнатуру функции для модульного тестирования?

Предположим, у нас есть функция add(), как показано ниже:

void add(int a, int b) {
    int sum=a+b;
    cout<<sum;
    sendSumToStorage(sum);
}

Эта простая функция прибавляет к входным значениям, выводит сумму в консоль и также отправляет ее во внешнее хранилище (скажем, в файл). Вот как мы в идеале хотим, чтобы это было в приложении (то есть мы не хотим, чтобы это было вернуть что-нибудь).

Допустимо ли (с точки зрения дизайна) для целей модульного тестирования, если мы изменим сигнатуру функции, чтобы она возвращала sum? Тогда у нас может быть такой тест:

bool checkAdd() {
    int res=add(3, 4);
    if (res==7) return true;
    else return false;
}

Еще лучше, это (возвращение значения) единственный способ, которым мы могли бы протестировать его? Есть ли способ действительный, с помощью которого мы могли бы протестировать функцию add() без изменения подписи функции?

Было бы более нормально как-то наблюдать за значением, отправленным в хранилище, возможно, с помощью фиктивного класса.

Alan Birtles 30.05.2019 08:49

@AlanBirtles, не могли бы вы уточнить?

user8386434 30.05.2019 08:58

Если вы добавите минимальный воспроизводимый пример, я смогу

Alan Birtles 30.05.2019 09:04
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
215
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Функция, подобная той, что в вашем примере, считается очень плохой практикой.

Почему я говорю это?

Итак, у вас есть метод Добавить, который складывает два числа, а А ТАКЖЕ вызывает что-то еще. По сути, ваш метод делает не одну вещь, а две, что нарушает принцип единой ответственности.

Это значительно усложняет тестирование, потому что вы не можете протестировать только метод add изолированно.

Таким образом, вы бы разделили это на два метода с хорошими именами, которые отражают то, что они делают, и протестировали бы их отдельно.

Если вы не хотите иметь проблем с состоянием между вашими методами, вам придется начать возвращать результаты там, где это имеет смысл.

Я горячо согласен с тем, что вы говорите; но обратите внимание, что приведенное выше только что является примером. Было бы полезнее думать, что у функции есть только две задачи (как у транспортера) — отправить на консоль, а также в хранилище. Выполняемые здесь расчеты — это лишь некоторые параметры конфигурации, связанные с отправкой данных. И именно эти параметры конфигурации мы хотим протестировать.

user8386434 30.05.2019 08:55

Игнорируя тот факт, что этот пример имеет плохой дизайн.

В таких случаях, когда вы хотите проверить какое-то внутреннее поведение вместо API, вам следует попробовать использовать некоторые библиотеки тестирования, такие как gtest и gmock. Это позволяет вам описывать более сложные ожидания, чем просто результат функции.

Например, с помощью макроса EXPECT_CALL можно задать ожидание вызова какого-либо метода во время выполнения кода.

Подробнее здесь: https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md

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

Изменение дизайна кода для улучшения тестируемости очень распространено и обычно считается хорошей практикой. Очевидно, что не все такие изменения обязательно являются реальными улучшениями — иногда существуют лучшие решения.

В вашем случае код сложно протестировать, поскольку он сочетает вычисления (сложение) с взаимодействием с зависимыми компонентами (вывод и сохранение данных). В вашем случае (как указал Андрей) функция также нарушает SRP, но смешивание вычислений и взаимодействий обычно затрудняет тестирование, даже в тех случаях, когда SRP не нарушается.

Я понимаю, вы бы изменили функцию так, чтобы она возвращала вычисленное значение в дополнение к его печати и записи в хранилище. Это позволит вам протестировать вычислительную часть функции. Однако в этом случае функция будет протестирована лишь частично. И цель функции будет дополнительно запутана.

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

Другие вопросы по теме