Gmock多序列重复参数EXPECT_CALL问题:海龟绘图测试配置求助
我在为海龟图形接口编写测试时遇到了问题。我简化了仅用于绘制线条的接口,希望验证draw_grid函数确实绘制了所有网格线条,且线条绘制顺序不做要求,但必须保证move_to和line_to的调用是正确配对的。
我尝试使用Google Test的InSequence来实现,但遇到了核心问题:部分move_to和line_to调用会被复用在多条线条中。比如move_to(0,0)既用于绘制网格顶部边缘,又用于第一个单元格的左边界,这导致测试中两个不同的序列里都定义了EXPECT_CALL(sot, move_to(0,0)),而它们默认都被设置为Times(1),从而引发冲突。每行的左/上边界、右/下边界的line_to调用也存在同样的问题。
我也曾尝试用After来调整调用顺序,但还是出现其他测试错误。请问有没有合适的方式来定义我需要的行为?感谢帮助!
using testing::InSequence; struct ITurtle { virtual void move_to(int x, int y) = 0; virtual void line_to(int x, int y) = 0; void line(int x0, int y0, int x1, int y1) { move_to(x0, y0); line_to(x1, y1); } }; class TurtleMock : public ITurtle { public: MOCK_METHOD(void, move_to, (int x, int y), (override)); MOCK_METHOD(void, line_to, (int x, int y), (override)); }; void draw_grid(ITurtle &t) { for (int r = 0; r < 100; r += 10) { // top t.line(0,r,100,r); for (int c = 0; c < 100; c += 10) { // left t.line(c,r,c,r+10); } // right t.line(100,r,100,r+10); } // bottom t.line(0,100,100,100); } TEST(TurtleTest, lines) { TurtleMock sot; for (int r = 0; r < 100; r += 10) { { InSequence s; EXPECT_CALL(sot, move_to(0, r)); EXPECT_CALL(sot, line_to(100, r)); } for (int c = 0; c < 100; c += 10) { InSequence s; EXPECT_CALL(sot, move_to(c,r)); EXPECT_CALL(sot, line_to(c,r+10)); } { InSequence s; EXPECT_CALL(sot, move_to(100, r)); EXPECT_CALL(sot, line_to(100, r + 10)); } } { InSequence s; EXPECT_CALL(sot, move_to(0, 100)); EXPECT_CALL(sot, line_to(100, 100)); } draw_grid(sot); }
编辑
针对Sedenion提出的解决方案进行扩展,以支持多边形绘制逻辑:先执行一次初始move_to,再执行一系列line_to语句。
void move_to(int x, int y) final { m_move_to_data = {x, y}; } void line_to(int x, int y) final { ASSERT_TRUE(m_move_to_data.has_value()); auto const [x0, y0] = *m_move_to_data; line_mock(x0, y0, x, y); m_move_to_data = {x, y}; }
核心思路是绕开对move_to和line_to单独调用的验证,转而直接验证最终的线条绘制结果——因为你真正关心的是所有网格线条都被正确绘制,而非底层move_to/line_to的调用顺序或次数。
方法一:封装Mock逻辑,直接验证线条
修改TurtleMock,内部维护上一次move_to的坐标,在line_to被调用时直接验证完整线条(从上次移动的坐标到当前line_to的坐标)。这样无需单独验证move_to,只需确认所有需要的网格线都被触发即可。
修改后的Mock类示例:
class TurtleMock : public ITurtle { public: MOCK_METHOD(void, line_mock, (int x0, int y0, int x1, int y1), ()); void move_to(int x, int y) override { last_x = x; last_y = y; } void line_to(int x, int y) override { line_mock(last_x, last_y, x, y); // 支持多边形绘制:更新坐标为当前line_to的终点 last_x = x; last_y = y; } private: int last_x = 0; int last_y = 0; };
对应的测试代码只需验证所有网格线:
TEST(TurtleTest, lines) { TurtleMock sot; // 验证所有横向网格线 for (int r = 0; r <= 100; r += 10) { EXPECT_CALL(sot, line_mock(0, r, 100, r)).Times(1); } // 验证所有纵向网格线 for (int c = 0; c <= 100; c += 10) { for (int r = 0; r < 100; r += 10) { EXPECT_CALL(sot, line_mock(c, r, c, r + 10)).Times(1); } } draw_grid(sot); }
这种方式完全规避了move_to重复调用的问题,直接聚焦测试核心目标。
方法二:调整原有测试,明确调用次数(不推荐)
如果一定要单独验证move_to和line_to的配对,需手动统计每个坐标的调用次数,显式指定Times(n)。比如move_to(0,0)会被调用两次,就要设置EXPECT_CALL(sot, move_to(0,0)).Times(2)。但这种方式维护成本极高,网格规模变大时极易出错,因此不推荐。
适配多边形绘制的扩展
上述Mock方案天然支持多边形绘制逻辑:每次line_to后会更新起点坐标,后续line_to会以上一次的终点作为新起点,完全符合“初始move_to定位,连续line_to绘边”的需求。
内容的提问来源于stack exchange,提问作者Feuerteufel

