Я работаю с устаревшей кодовой базой C++ и хочу протестировать некоторые методы класса DependsOnUgly
, у которого есть зависимость, которую нелегко сломать в большом классе (Ugly
) с большим количеством внешних зависимостей от файловой системы и т. д. Я хочу получить хотя бы некоторые тестируемые методы DependsOnUgly
, при этом как можно меньше модифицируя существующий код. Невозможно создать шов фабричным методом, параметром метода или параметром конструктора без большого количества модификаций кода; Ugly
- это конкретный класс, от которого напрямую зависит без какого-либо абстрактного базового класса, и он имеет большое количество методов, некоторые из которых или ни один из которых не помечены virtual
, поэтому полное издевательство над ним было бы очень утомительным. У меня нет фиктивной среды, но я хочу протестировать DependsOnUgly
, чтобы внести изменения. Как я могу сломать внешние зависимости Ugly
для модульного тестирования методов на DependsOnUgly
?
Как выполнить модульное тестирование класса с неприятными зависимостями без фиктивного фреймворка?
Ответы (1)
Используйте то, что я называю препроцессорным макетом, макет, введенный через шов препроцессора.
Впервые я опубликовал эту концепцию в этом вопросе на сайте Programmers.SE, и по ответам на него я понял, что это не очень известный шаблон, поэтому я подумал, что должен поделиться им. Мне трудно поверить, что никто не делал что-то подобное раньше, но поскольку я не смог найти это документально, я решил поделиться этим с сообществом.
Вот условные реализации Ugly
и NotAsUgly
для примера.
DependsOnUgly.hpp
#ifndef _DEPENDS_ON_UGLY_HPP_
#define _DEPENDS_ON_UGLY_HPP_
#include <string>
#include "Ugly.hpp"
class DependsOnUgly {
public:
std::string getDescription() {
return "Depends on " + Ugly().getName();
}
};
#endif
Уродливый.hpp
#ifndef _UGLY_HPP_
#define _UGLY_HPP_
struct Ugly {
double a, b, ..., z;
void extraneousFunction { ... }
std::string getName() { return "Ugly"; }
};
#endif
Есть две основные вариации. Во-первых, только определенные методы Ugly
вызываются DependsOnUgly
, и вы уже хотите издеваться над этими методами. Второй
Метод 1: заменить все поведение Ugly
, используемое DependsOnUgly
Я называю этот метод Частичная имитация препроцессора, поскольку макет реализует только необходимые части интерфейса моделируемого класса. Используйте охранники включения с тем же именем, что и у рабочего класса, в файле заголовка для фиктивного класса, чтобы производственный класс никогда не определялся, а был фиктивным. Не забудьте включить макет перед DependsOnUgly.hpp
.
(Обратите внимание, что мои примеры тестового файла не являются самопроверяемыми; это просто для простоты и для того, чтобы не зависеть от среды модульного тестирования. Основное внимание уделяется директивам в верхней части файла, а не самому фактическому методу тестирования. .)
test.cpp
#include <iostream>
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
std::cout << DependsOnUgly().getDescription() << std::endl;
}
NotAsUgly.hpp
#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly { // Once again, duplicate name is deliberate
std::string getName() { return "not as ugly"; } // All that DependsOnUgly depends on
};
#endif
Техника 2: Замените часть поведения Ugly
, используемую DependsOnUgly
Я называю это мока-подклассом на месте, потому что в этом случае Ugly
является подклассом и необходимые методы переопределяются, в то время как другие все еще доступны для использования, но имя подкласса по-прежнему Ugly
. Директива определения используется для переименования Ugly
в BaseUgly
; затем используется директива undefine и фиктивные Ugly
подклассы BaseUgly
. Обратите внимание, что для этого может потребоваться пометить что-то в Ugly
как виртуальное в зависимости от конкретной ситуации.
test.cpp
#include <iostream>
#define Ugly BaseUgly
#include "Ugly.hpp"
#undef Ugly
#include "NotAsUgly.hpp"
#include "DependsOnUgly.hpp"
int main() {
std::cout << DependsOnUgly().getDescription() << std::endl;
}
NotAsUgly.hpp
#ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately!
#define _UGLY_HPP_
struct Ugly: public BaseUgly { // Once again, duplicate name is deliberate
std::string getName() { return "not as ugly"; }
};
#endif
Обратите внимание, что оба эти метода немного ненадежны и должны использоваться с осторожностью. От них следует отказаться, поскольку тестируется большая часть кодовой базы, и заменить их более стандартными средствами разрыва зависимостей, если это возможно. Обратите внимание, что они оба потенциально могут оказаться неэффективными, если директивы включения в устаревшей кодовой базе будут достаточно запутанными. Однако я успешно использовал их оба для реальных устаревших систем, так что я знаю, что они могут работать.
_UGLY_PP
. Таким образом, #ifndef
в строке 1 в NotAsUgly.hpp должно оцениваться как false
и должно произойти другое.
- person wesanyer; 22.07.2017