Static-Casting Base class to Derived class 为了在测试中调用析构函数(用于测试)有多可怕?
How horrible is Static-Casting Base class to Derived class in order to call destructor in tests (for tests)?
前言: 我知道下面是一个非常肮脏的 hack。我们将重构事物(因此我们可以编写非 hacky 测试,使用 Mocks 甚至可能摆脱单例),但首先我们需要测试覆盖率...
如前所述,我们的项目中有大量 "Singleton" 类型的 classes,即类似的东西。
class SomeSingleton
{
public:
static SomeSingleton& getSingleton()
{
if (m_singletonInstance == nullptr)
m_singletonInstance = new SomeSingleton();
return *m_singletonInstance;
}
protected:
SomeSingleton();
virtual ~SomeSingleton();
static SomeSingleton* m_singletonInstance = nullptr;
}
为了正确地运行我们的单元测试,单例需要"reset"(或者我们以前测试的数据可能会保留在单例中,影响当前测试)。不幸的是,没有可行的方法来做到这一点(Singleton 实例和析构函数受到保护)。
我们不希望更改我们的生产代码以包含仅测试功能(即不转发声明 ::test::SomeSingletonResetter
作为 SomeSingleton 中的好友,不引入 public void resetSingleton_forTestUseOnly()
功能等)- 我们还希望在我们有适当的测试覆盖率之前避免对生产代码进行任何重大更改。
所以,这是我们正在考虑的肮脏黑客:
在测试项目中,我们从 Singleton 派生了一个非常简单的包装器 class(即没有成员),其中包含一个静态函数 delete
s singleton - 然而,即使从派生的 class(它是受保护的)我们将 static_cast 派生的单例 class 并删除:
class SomeSingletonWrapper : public SomeSingleton
{
public:
SomeSingletonWrapper();
~SomeSingletonWrapper() override;
static void reset()
{
//can't delete/call destructor on SomeSingleton since it is protected
delete static_cast<SomeSingletonWrapper*>(m_singletonInstance);
m_singletonInstance = nullptr;
}
}
我们的想法是,classes 的大小基本相同,派生 classes 的析构函数将调用基础析构函数,因此 class 被正确销毁。这行得通吗(直到我们可以重构东西)或者它会在我们面前可怕地爆炸吗?有没有更好的(更简单的)方法来归档这个(而不主要修改生产代码)?
编辑:
我知道另一种选择是不调用析构函数(只设置 m_singletonInstance = nullptr
),但这会更糟,因为我现在每次测试都会泄漏内存,而且那些单例会一直浮动直到测试应用程序终止,做天知道什么... brrr ....
根据标准5.3.5.2:
In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined.
由于您要删除的是超级 class,而不是子 class,并且该指针之前不是作为该超级 class 的实例创建的,所以您是在不确定的领域。所以你可能会很幸运,它有时会在某些带有某些编译器的系统上工作,但不能保证。
你能覆盖 getSingleton()
吗?它不是给定代码中的 virtual
或 static
,但它可能应该是其中之一。如果是前者,则有一个更简单的解决方案(在包装器中覆盖它)。如果没有,您可以考虑尝试用包装器替换创建的值:
class SomeSingletonWrapper : public SomeSingleton
{
public:
static void injectWrapper()
{
// Should be null or else someone used it before we replaced it
if( m_singletonInstance != nullptr )
{
throw std::exception( "Singleton wrapping failed!" );
}
m_singletonInstance = new SomeSingletonWrapper();
}
static void resetWrapper()
{
auto wrapper = dynamic_cast<SomeSingletonWrapper*>( m_singletonInstance );
if( wrapper == nullptr )
{
throw std::exception( "Singleton wrapping failed in the reset!" );
}
delete wrapper;
m_singletonInstance = nullptr;
}
}
然后在你的测试中,在任何人使用它之前替换它(但要注意 static order initialization fiasco 其他东西可能首先获得旧实例):
class MyTest
{
public:
void testSetup()
{
SomeSingletonWrapper::injectWrapper();
}
void testCleanup()
{
SomeSingletonWrapper::resetWrapper();
}
void testMySingletonUse()
{
auto& single = SomeSingleton::getInstance();
ASSERT( dynamic_cast<SomeSingletonWrapper*>( &single ) ); // Yay! It worked!
// More testy stuff using 'single'
}
}
前言: 我知道下面是一个非常肮脏的 hack。我们将重构事物(因此我们可以编写非 hacky 测试,使用 Mocks 甚至可能摆脱单例),但首先我们需要测试覆盖率...
如前所述,我们的项目中有大量 "Singleton" 类型的 classes,即类似的东西。
class SomeSingleton
{
public:
static SomeSingleton& getSingleton()
{
if (m_singletonInstance == nullptr)
m_singletonInstance = new SomeSingleton();
return *m_singletonInstance;
}
protected:
SomeSingleton();
virtual ~SomeSingleton();
static SomeSingleton* m_singletonInstance = nullptr;
}
为了正确地运行我们的单元测试,单例需要"reset"(或者我们以前测试的数据可能会保留在单例中,影响当前测试)。不幸的是,没有可行的方法来做到这一点(Singleton 实例和析构函数受到保护)。
我们不希望更改我们的生产代码以包含仅测试功能(即不转发声明 ::test::SomeSingletonResetter
作为 SomeSingleton 中的好友,不引入 public void resetSingleton_forTestUseOnly()
功能等)- 我们还希望在我们有适当的测试覆盖率之前避免对生产代码进行任何重大更改。
所以,这是我们正在考虑的肮脏黑客:
在测试项目中,我们从 Singleton 派生了一个非常简单的包装器 class(即没有成员),其中包含一个静态函数 delete
s singleton - 然而,即使从派生的 class(它是受保护的)我们将 static_cast 派生的单例 class 并删除:
class SomeSingletonWrapper : public SomeSingleton
{
public:
SomeSingletonWrapper();
~SomeSingletonWrapper() override;
static void reset()
{
//can't delete/call destructor on SomeSingleton since it is protected
delete static_cast<SomeSingletonWrapper*>(m_singletonInstance);
m_singletonInstance = nullptr;
}
}
我们的想法是,classes 的大小基本相同,派生 classes 的析构函数将调用基础析构函数,因此 class 被正确销毁。这行得通吗(直到我们可以重构东西)或者它会在我们面前可怕地爆炸吗?有没有更好的(更简单的)方法来归档这个(而不主要修改生产代码)?
编辑:
我知道另一种选择是不调用析构函数(只设置 m_singletonInstance = nullptr
),但这会更糟,因为我现在每次测试都会泄漏内存,而且那些单例会一直浮动直到测试应用程序终止,做天知道什么... brrr ....
根据标准5.3.5.2:
In the first alternative (delete object), the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject (1.8) representing a base class of such an object (Clause 10). If not, the behavior is undefined.
由于您要删除的是超级 class,而不是子 class,并且该指针之前不是作为该超级 class 的实例创建的,所以您是在不确定的领域。所以你可能会很幸运,它有时会在某些带有某些编译器的系统上工作,但不能保证。
你能覆盖 getSingleton()
吗?它不是给定代码中的 virtual
或 static
,但它可能应该是其中之一。如果是前者,则有一个更简单的解决方案(在包装器中覆盖它)。如果没有,您可以考虑尝试用包装器替换创建的值:
class SomeSingletonWrapper : public SomeSingleton
{
public:
static void injectWrapper()
{
// Should be null or else someone used it before we replaced it
if( m_singletonInstance != nullptr )
{
throw std::exception( "Singleton wrapping failed!" );
}
m_singletonInstance = new SomeSingletonWrapper();
}
static void resetWrapper()
{
auto wrapper = dynamic_cast<SomeSingletonWrapper*>( m_singletonInstance );
if( wrapper == nullptr )
{
throw std::exception( "Singleton wrapping failed in the reset!" );
}
delete wrapper;
m_singletonInstance = nullptr;
}
}
然后在你的测试中,在任何人使用它之前替换它(但要注意 static order initialization fiasco 其他东西可能首先获得旧实例):
class MyTest
{
public:
void testSetup()
{
SomeSingletonWrapper::injectWrapper();
}
void testCleanup()
{
SomeSingletonWrapper::resetWrapper();
}
void testMySingletonUse()
{
auto& single = SomeSingleton::getInstance();
ASSERT( dynamic_cast<SomeSingletonWrapper*>( &single ) ); // Yay! It worked!
// More testy stuff using 'single'
}
}