You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 14:20:35