使用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
相关产品推荐
相关产品推荐

