Ответ
Главная проблема — глобальное разделяемое состояние, которое нарушает изоляцию модульных тестов. Состояние синглтона, измененное одним тестом, влияет на все последующие, делая их недетерминированными и зависимыми от порядка выполнения.
Конкретные проблемы в C++:
- Неизолированность тестов: Тесты нельзя запускать параллельно.
- Сложность сброса состояния: После каждого теста нужно вернуть синглтон в исходное состояние, что может быть нетривиально, если у него нет явного метода сброса (
reset()). - Зависимость от времени жизни (Lifetime): Статический локальный объект в методе
getInstance()(Meyers' Singleton) уничтожается только при завершении программы. Его деструктор может конфликтовать с уже освобожденными ресурсами.
Пример проблемного кода:
class ConfigManager { // Singleton
static ConfigManager& getInstance() {
static ConfigManager instance;
return instance;
}
std::string configPath;
// ...
public:
void setPath(const std::string& p) { configPath = p; }
const std::string& getPath() const { return configPath; }
};
// TEST 1
TEST(ConfigTest, SetsPath) {
ConfigManager::getInstance().setPath("/test/path");
ASSERT_EQ("/test/path", ConfigManager::getInstance().getPath());
}
// TEST 2 (Зависит от выполнения первого!)
TEST(ConfigTest, DefaultPath) {
// Может упасть, если тесты запускаются в порядке SetsPath -> DefaultPath
ASSERT_TRUE(ConfigManager::getInstance().getPath().empty());
}
Стратегии решения для C++:
- Отказ от Singleton в пользу Dependency Injection: Это наиболее чистое решение. Зависимость передается в класс через конструктор или сеттер.
- Добавление метода для сброса (только для тестов):
class TestableSingleton { static std::unique_ptr<TestableSingleton> instance; public: static TestableSingleton& getInstance() { if (!instance) instance.reset(new TestableSingleton); return *instance; } static void resetForTesting() { // Только для unit-тестов! instance.reset(); } }; // В каждом тесте вызывать resetForTesting() в SetUp()/TearDown(). - Использование заглушки (Mock) через интерфейс: Сделать Singleton реализацией абстрактного интерфейса. В тестах подменять его на mock-объект.