使用 NVI Idiom 模拟非虚拟函数问题

问题描述 投票:0回答:1

我在班级设计中遵循了 Herb Sutter 的 NVI idiom,因为它有好处。我的抽象类简而言之如下。

class Communicator {
public:
    bool Connect(void) {
        // some preoperations
        return DoConnect();
    }
    // 3 more similar public member functions

private:
    virtual bool DoConnect(void) = 0
    // 3 more similar pure virtual member functions
}

我将 Communicator 类注入到我想要测试的类中,如下所示(我从Google docs找到了这个方法):

template <typename CommunicatorClass> // There will be some constraints
class TestingClass {
public:
    TestingClass(std::unique_ptr<CommunicatorClass> comm) : comm_{std::move(comm)} { }
private:
    std::unique_ptr<CommunicatorClass> comm_ = nullptr;
}

我的模拟课:

class MockCommunicator : public Communicator {
public:
    MOCK_METHOD(bool, Connect, ());
    // 3 more mocks
}

我的测试套件设置:

// some code ..
auto mock = std::make_unique<MockCommunicator>();
TestingClass<MockCommunicator>(std::move(mock));

你可以猜到,因为我没有重写 4 个私有虚函数,所以 MockCommunicator 变得抽象,并且编译器给出错误。我想到的选项:

  1. 重写 MockCommunicator 中的虚拟函数。 (看起来很难看,但我最接近这种方法)
  2. 我可以将纯虚函数转换为虚函数,但我不是唯一拥有代码库的人。我想强迫其他开发人员实现他们自己的实现。
  3. 使 MockCommunicator 成为 Communicator 类的朋友并模拟私有虚函数。 (我认为这是绝对错误的,是吗?)
  4. 放弃 NVI,因为它对代码库的复杂性

有什么推荐的方法来处理这种情况吗?

注意:我使用 C++20

c++ c++20 googletest googlemock non-virtual-interface
1个回答
0
投票

与其嘲笑你的公共

Connect
,不如嘲笑虚拟私有
DoConnect
(当然,以及所有其他纯虚函数,它并不难看,如果你嘲笑,你会嘲笑所有接口,而不是其中的一小部分) ):

class MockCommunicator : public Communicator {
public:
    MOCK_METHOD(bool, DoConnect, (), (override));
    // 3 more mocks
};

请注意,在模拟中

DoConnect
是公共的,因此您可以在
EXPECT_CALL
宏中使用它,但它不会破坏您的界面,在使用指向基类的指针的
TestingClass
中,它们仍然具有私有可见性。然后假设您的
TestingClass
有一些简单的方法来调用
Communicator
可观察行为:

    bool run() {
        return comm_->Connect();
    }

你可以写一个简单的测试:

TEST(xxx, yyy) {
    auto mock = std::make_unique<MockCommunicator>();
    auto* rawMock = mock.get();
    auto sut = TestingClass<MockCommunicator>(std::move(mock));
    EXPECT_CALL(*rawMock, DoConnect()).WillRepeatedly(testing::Return(true));
    ASSERT_TRUE(sut.run());
}

有了这个,你甚至不需要你的

TestingClass
成为模板:

class TestingClass {
public:
    TestingClass(std::unique_ptr<Communicator> comm) : comm_{std::move(comm)} { }
    bool run() {
        return comm_->Connect();
    }
private:
    std::unique_ptr<Communicator> comm_;
};

顺便说一句,你的

class Communicator
必须有一个虚拟析构函数。

神箭演示

© www.soinside.com 2019 - 2024. All rights reserved.