哪种 C++ 设计模式可用于在生产代码和测试代码之间交换函数模板的行为?
What C++ design pattern is available for swapping behavior of a function template between production code and testing code?
我正在重构一个组件以使其更易于单元测试。第一步是为一些核心组件代码启用单元测试。不幸的是,这个核心组件代码调用了一些组件内的实用函数,这些函数直接与其他组件的具体交互 类,如果没有生产环境,这些代码很难实例化。对于普通实用程序函数,我可以利用依赖注入将它们包装为实用程序接口中的虚函数,并在单元测试期间换入重写。但是,还有实用函数模板,我不能应用相同的技术,因为我们不能有虚函数模板。是否有已知的模式来处理此问题?
举一个具体的例子,考虑下面的代码。有没有办法在单元测试期间切换 utilIsNameValid
函数模板的行为?一些限制适用:
- 我需要直接将
utilIsNameValid
中的逻辑与测试其使用代码隔离开来。这阻止了我仅仅通过依赖注入来切换 utilGetName
s 的行为,并希望它会间接地引导 utilIsNameValid
我想要的方式。
- 我无法更改
MyClass
的界面,因为它是遗留的 API,具有广泛的影响。 @Jarod42 的答案近乎完美,但需要 MyClass
才能成为模板,这可能会导致调用站点适配更改。
mycomponent/util/MyUtil.hpp (in-component utility code)
#include <string>
class TheirClassA;
class TheirClassB;
std::string utilGetName(TheirClassA* a); // a normal utility function
std::string utilGetName(TheirClassB* b); // a normal utility function
template<class T>
bool utilIsNameValid(T obj) { // a utility function template
auto name = utilGetName(obj);
// ... perform the generic name checking ...
}
mycomponent/util/MyUtil.cpp (in-component utility code)
#include "MyUtil.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
std::string utilGetName(TheirClassA* a) {
return std::string{a->getNameInAsWayThatReturnsCharPtr()};
}
std::string utilGetName(TheirClassB* b) {
return b->getNameInBsWayThatReturnsString();
}
mycomponent/core/MyClass.hpp (core component code)
class TheirClassA;
class TheirClassB;
class MyClass {
public:
bool canProcess(TheirClassA* a) const;
bool canProcess(TheirClassB* b) const;
};
mycomponent/core/MyClass.cpp (core component code)
#include "MyClass.hpp"
#include "mycomponent/util/MyUtil.hpp"
bool MyClass::canProcess(TheirClassA* a) const {
return utilIsNameValid(a) && /* some other conditions for TheirClassA objects*/;
}
bool MyClass::canProcess(TheirClassB* b) const {
return utilIsNameValid(b) && /* some other conditions for TheirClassB objects*/;
}
production/main.cpp
#include "mycomponent/core/MyClass.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
int main (){
// ... tedious steps to set up TheirClassA and TheirClassB objects ...
MyClass processor;
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
mycomponent/unittest/Test.cpp
#include "mycomponent/core/MyClass.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
int main (){
// ... cannot afford instantiating actual objects ...
TheirClassA* a = nullptr;
TheirClassB* b = nullptr;
// ... need someway for utilIsNameValid to simply return
// true or false to test out the rest of the code in
// MyClass::canProcess(...) ...
MyClass processor;
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
您可以制作 class 模板,例如:
// myclass.hpp
template <typename Utility>
class MyClassT {
public:
bool canProcess(TheirClassA* a) const;
bool canProcess(TheirClassB* b) const;
};
class UtilityProd; // Class where you have your static methods
using MyClass = MyClassT<UtilityProd>;
// myclass.inl
template <typename Utility>
bool MyClassT::canProcess(TheirClassA* a) const
{
return Utility::utilIsNameValid(a) && /* some other conditions for TheirClassA objects*/;
}
template <typename Utility>
bool MyClassT::canProcess(TheirClassB* b) const
{
return Utility::utilIsNameValid(b) && /* some other conditions for TheirClassB objects*/;
}
// myclass.cpp
#include "MyClass.hpp"
#include "MyClass.inl"
#include "UtilityProd.h"
// explicit instantiation to keep separation header/cpp
template class MyClassT<UtilityProd>;
// test.cpp
#include "MyClass.hpp"
#include "MyClass.inl"
#include "UtilityTest.h"
using MyClassTest = class MyClassT<class UtilityTest>;
int main (){
// ... cannot afford instantiating actual objects ...
TheirClassA* a = nullptr;
TheirClassB* b = nullptr;
MyClassTest processor;
UtilityTest::setResult(..); // ...
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
production/main.cpp(和所有生产案例)保持不变。
我正在重构一个组件以使其更易于单元测试。第一步是为一些核心组件代码启用单元测试。不幸的是,这个核心组件代码调用了一些组件内的实用函数,这些函数直接与其他组件的具体交互 类,如果没有生产环境,这些代码很难实例化。对于普通实用程序函数,我可以利用依赖注入将它们包装为实用程序接口中的虚函数,并在单元测试期间换入重写。但是,还有实用函数模板,我不能应用相同的技术,因为我们不能有虚函数模板。是否有已知的模式来处理此问题?
举一个具体的例子,考虑下面的代码。有没有办法在单元测试期间切换 utilIsNameValid
函数模板的行为?一些限制适用:
- 我需要直接将
utilIsNameValid
中的逻辑与测试其使用代码隔离开来。这阻止了我仅仅通过依赖注入来切换utilGetName
s 的行为,并希望它会间接地引导utilIsNameValid
我想要的方式。 - 我无法更改
MyClass
的界面,因为它是遗留的 API,具有广泛的影响。 @Jarod42 的答案近乎完美,但需要MyClass
才能成为模板,这可能会导致调用站点适配更改。
mycomponent/util/MyUtil.hpp (in-component utility code)
#include <string>
class TheirClassA;
class TheirClassB;
std::string utilGetName(TheirClassA* a); // a normal utility function
std::string utilGetName(TheirClassB* b); // a normal utility function
template<class T>
bool utilIsNameValid(T obj) { // a utility function template
auto name = utilGetName(obj);
// ... perform the generic name checking ...
}
mycomponent/util/MyUtil.cpp (in-component utility code)
#include "MyUtil.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
std::string utilGetName(TheirClassA* a) {
return std::string{a->getNameInAsWayThatReturnsCharPtr()};
}
std::string utilGetName(TheirClassB* b) {
return b->getNameInBsWayThatReturnsString();
}
mycomponent/core/MyClass.hpp (core component code)
class TheirClassA;
class TheirClassB;
class MyClass {
public:
bool canProcess(TheirClassA* a) const;
bool canProcess(TheirClassB* b) const;
};
mycomponent/core/MyClass.cpp (core component code)
#include "MyClass.hpp"
#include "mycomponent/util/MyUtil.hpp"
bool MyClass::canProcess(TheirClassA* a) const {
return utilIsNameValid(a) && /* some other conditions for TheirClassA objects*/;
}
bool MyClass::canProcess(TheirClassB* b) const {
return utilIsNameValid(b) && /* some other conditions for TheirClassB objects*/;
}
production/main.cpp
#include "mycomponent/core/MyClass.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
int main (){
// ... tedious steps to set up TheirClassA and TheirClassB objects ...
MyClass processor;
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
mycomponent/unittest/Test.cpp
#include "mycomponent/core/MyClass.hpp"
#include "theircomponent/TheirClassA.hpp"
#include "theircomponent/TheirClassB.hpp"
int main (){
// ... cannot afford instantiating actual objects ...
TheirClassA* a = nullptr;
TheirClassB* b = nullptr;
// ... need someway for utilIsNameValid to simply return
// true or false to test out the rest of the code in
// MyClass::canProcess(...) ...
MyClass processor;
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
您可以制作 class 模板,例如:
// myclass.hpp
template <typename Utility>
class MyClassT {
public:
bool canProcess(TheirClassA* a) const;
bool canProcess(TheirClassB* b) const;
};
class UtilityProd; // Class where you have your static methods
using MyClass = MyClassT<UtilityProd>;
// myclass.inl
template <typename Utility>
bool MyClassT::canProcess(TheirClassA* a) const
{
return Utility::utilIsNameValid(a) && /* some other conditions for TheirClassA objects*/;
}
template <typename Utility>
bool MyClassT::canProcess(TheirClassB* b) const
{
return Utility::utilIsNameValid(b) && /* some other conditions for TheirClassB objects*/;
}
// myclass.cpp
#include "MyClass.hpp"
#include "MyClass.inl"
#include "UtilityProd.h"
// explicit instantiation to keep separation header/cpp
template class MyClassT<UtilityProd>;
// test.cpp
#include "MyClass.hpp"
#include "MyClass.inl"
#include "UtilityTest.h"
using MyClassTest = class MyClassT<class UtilityTest>;
int main (){
// ... cannot afford instantiating actual objects ...
TheirClassA* a = nullptr;
TheirClassB* b = nullptr;
MyClassTest processor;
UtilityTest::setResult(..); // ...
if (processor.canProcess(a)) {
// ... process a ....
}
if (processor.canProcess(b)) {
// ... process b ....
}
}
production/main.cpp(和所有生产案例)保持不变。