哪种 C++ 设计模式可用于在生产代码和测试代码之间交换函数模板的行为?

What C++ design pattern is available for swapping behavior of a function template between production code and testing code?

我正在重构一个组件以使其更易于单元测试。第一步是为一些核心组件代码启用单元测试。不幸的是,这个核心组件代码调用了一些组件内的实用函数,这些函数直接与其他组件的具体交互 类,如果没有生产环境,这些代码很难实例化。对于普通实用程序函数,我可以利用依赖注入将它们包装为实用程序接口中的虚函数,并在单元测试期间换入重写。但是,还有实用函数模板,我不能应用相同的技术,因为我们不能有虚函数模板。是否有已知的模式来处理此问题?

举一个具体的例子,考虑下面的代码。有没有办法在单元测试期间切换 utilIsNameValid 函数模板的行为?一些限制适用:

  1. 我需要直接将 utilIsNameValid 中的逻辑与测试其使用代码隔离开来。这阻止了我仅仅通过依赖注入来切换 utilGetNames 的行为,并希望它会间接地引导 utilIsNameValid 我想要的方式。
  2. 我无法更改 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(和所有生产案例)保持不变。