You need to enable JavaScript to run this app. 使用GoogleMock测试CardCollection类的依赖规避与Mock返回值设置
优惠活动
大模型
产品
解决方案
定价
更多

使用GoogleMock测试CardCollection类的依赖规避与Mock返回值设置

问题背景

你正在学习Mock单元测试,为隔离依赖为CardCollection类编写了测试替身MockCard,目前在测试编写中遇到了返回值不对、测试逻辑不清晰的问题,各模块代码实现如下:

接口与真实实现(Card.h)

class ICard {
protected:
    int rank_;
    std::string suit_;
public:
    ICard(int rank, std::string suit) : rank_(rank), suit_(std::move(suit)) {};

    [[nodiscard]] virtual int getRank() const = 0;
    [[nodiscard]] virtual std::string getSuit() const  = 0;

    friend std::ostream &operator<<(std::ostream &os, const ICard &c);
    bool operator>(const ICard &other) const;
    bool operator<(const ICard &other) const;
    bool operator<=(const ICard &other) const;
    bool operator>=(const ICard &other) const;
    bool operator==(const ICard &other) const;
    bool operator!=(const ICard &other) const;
};

class Card : public ICard {
public:
    Card(int r, std::string s);
    [[nodiscard]] int getRank() const override;
    [[nodiscard]] std::string getSuit() const override;
};

测试替身实现(MockCard.h)

#include "gmock/gmock.h"
class MockCard : public ICard {
public:
    MockCard(int rank, std::string suit) : ICard(rank, std::move(suit)){};
    MOCK_METHOD(int, getRank, (), (const, override));
    MOCK_METHOD(std::string, getSuit, (), (const, override));
};

被测类实现(CardCollection.h)

#include "Card.h"
class CardCollection {
protected:
    vector<ICard*> cards_;
public:
    CardCollection() = default;
    ~CardCollection() = default;
    explicit CardCollection(vector<ICard*> cards);
    ICard* pop();
    // ... 其他省略的成员
};

当前编写的测试用例

#include "gmock/gmock.h"
TEST(CardCollectionTests, TestPop) {
    MockCard card1(6, "C");
    std::vector<ICard*> cards({&card1});
    CardCollection cc(cards);

    // 将pop返回的ICard*强转为MockCard*
    auto* new_card = (MockCard*) cc.pop();
    ASSERT_EQ(6, new_card->getRank());
}

核心问题
  • 运行测试时,MockCard的getRank方法作为GMock的mock实现,默认返回int类型的零值0,哪怕构造MockCard时传入了rank为6,因为方法被重写为Mock实现,根本不会读取基类存储的rank_成员,最终导致断言失败。
  • 不清楚如何正确控制Mock方法的返回值,也不确定当前的测试思路是否存在设计层面的误区。

问题修正与测试逻辑优化

1. 给Mock方法指定返回值的正确方式

如果需要控制Mock方法的返回值,直接用GMock提供的ON_CALL宏绑定默认行为即可,不需要修改Mock类的实现。比如在创建MockCard实例后,直接声明对象的getRank方法默认返回6:

TEST(CardCollectionTests, TestPop) {
    MockCard card1(6, "C");
    // 给Mock方法绑定指定的返回值
    ON_CALL(card1, getRank())
        .WillByDefault(Return(6));
    ON_CALL(card1, getSuit())
        .WillByDefault(Return("C"));

    std::vector<ICard*> cards({&card1});
    CardCollection cc(cards);
    auto* new_card = (MockCard*) cc.pop();
    ASSERT_EQ(6, new_card->getRank()); // 此时断言可以正常通过
}

2. 当前测试思路的核心误区

你现在的测试逻辑从根本上搞错了单元测试的边界:

  • 单元测试的核心目标,是验证被测类(此处为CardCollection)自身的行为是否符合约定,不是验证它依赖的对象(此处为ICard/MockCard)的行为。
  • pop方法的契约是「把存在集合内部的ICard指针取出来、移除,原封不动返回给调用者」。这个行为和ICard实例的getRank返回什么值完全无关——CardCollection根本不关心ICard的rank是多少,它只负责存指针、取指针。
  • 你现在做的「把pop出来的指针强转成MockCard,再调用getRank验证值为6」,本质上是在验证MockCard的方法返回值对不对,完全偏离了测试CardCollection逻辑的目标。

3. 正确的pop方法测试逻辑

你完全不需要强转类型、也不需要调用ICard的任何方法,只需要验证两个核心点:

  • pop方法返回的指针,就是当初你塞到集合里的ICard实例的地址(集合只会存储你传入的指针,不会随意创建新对象)
  • pop方法执行后,集合内部存储的指针数量对应减少
    正确的测试用例写法:
TEST(CardCollectionTests, PopReturnsInsertedCardAndUpdatesCollectionSize) {
    // 构造Mock实例,构造函数传的参数在这里没有实际意义,因为测试过程中根本不会调用它的方法
    MockCard card1(6, "C");
    ICard* original_ptr = &card1; // 记录当初塞进去的指针地址
    std::vector<ICard*> cards{original_ptr};
    CardCollection cc(cards);

    ICard* returned_ptr = cc.pop();
    // 核心断言1:pop返回的就是当初塞进去的指针,证明集合的存取逻辑正确
    ASSERT_EQ(original_ptr, returned_ptr);
    // 核心断言2:pop之后集合的元素数正确(比如从1变为0)
    ASSERT_EQ(0, cc.GetSize()); // GetSize是CardCollection对外提供的、返回内部cards_大小的方法
}

4. Mock方法的合理使用场景

在当前的pop方法测试场景中,根本用不上设置Mock返回值——因为pop逻辑不需要和ICard的方法产生任何交互。只有当你测试CardCollection里需要调用ICard接口的逻辑时,Mock才会真正发挥作用,比如:

  • 如果CardCollection有GetHighestRank()方法,会遍历集合里所有卡片、调用每个卡片的getRank方法查找最大点数
  • 如果CardCollection有SortBySuit()方法,会调用每个卡片的getSuit方法做排序
    这时候你才需要通过ON_CALL给不同的MockCard实例设置不同的getRank、getSuit返回值,验证CardCollection和依赖对象的交互逻辑是否正确,而不是拿Mock方法的返回值当真实数据做校验。

内容的提问来源于stack exchange,提问作者CiaranWelsh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.10 15:54:02