我在正确设置海龟图形界面的测试时遇到了问题。
作为示例,我有一个简化的界面,只用于绘制一条线。
现在我想写一个测试来绘制一些网格draw_grid
,并确保网格的每一条线都被绘制出来,但绘制线的实际顺序无关紧要。我只需要确保对move_to
和line_to
的方法调用正确配对。我在InSequence
中尝试过,但这导致了一些问题,因为两条线路使用了几个line_to
和move_to
调用。
例如,move_to(0,0)
用于网格的上边缘和第一个单元格的左边界。因此,测试将在两个不同的序列中生成两个EXPACT_CALL(sot, move_to(0,0))
,但每个序列都隐含有Times(1)
。所以我想这是主要的问题。这将发生在一行的每个左边界和上边界,类似地发生在line_to
的一行的右边界和下边界。
我还尝试将After
用于EXPACT_CALL
,但这只会导致不同的测试错误。
有什么好的方法可以指定请求的行为吗?谢谢你的帮助!
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};
}
考虑以下仅绘制2条线的简化示例(实际示例(:
void draw_two_lines(ITurtle &t)
{
t.line(0, 0, 10, 0);
t.line(0, 0, 0, 10);
}
TEST(TurtleTest, two_lines)
{
TurtleMock sot;
{
InSequence s;
EXPECT_CALL(sot, move_to(0, 0)); // (2)
EXPECT_CALL(sot, line_to(10, 0));
}
{
InSequence s;
EXPECT_CALL(sot, move_to(0, 0)); // (1)
EXPECT_CALL(sot, line_to(0, 10));
}
draw_two_lines(sot);
}
暂时忘记了InSequence
,Google Mock默认按相反的顺序(从下到上(匹配期望值。手册报价:
默认情况下,当调用mock方法时,gMock将按定义的相反顺序搜索期望值,并在找到与参数匹配的活动期望值时停止(您可以将其视为"新规则覆盖旧规则"(。
现在,我们确实有InSequence
,即两组。然而,这两个群体本身并不是";"按顺序";。这意味着gMock将把对move_to(0, 0)
的第一个调用与第二个组(在上面的代码中用(1)
标记(相匹配。因此,之后,预期line_to(0, 10)
,但调用line_to(10, 0)
,导致测试失败。如果你交换两个InSequence组的顺序,测试就会通过。然而,这并不值得,因为你的目标是使顺序独立。
你想要的基本上是指定一个";原子";所有4个参数的匹配。我不知道有什么方法可以用GoogleMock的InSequence
或After
机器直接表达这一点。因此,我建议采取另一种方法,将对move_to
的调用存储在一个临时变量中,并在对line_to
的调用中,将记住的两个值和两个给定值调用一个专用的模拟函数(实际示例(:
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:
std::optional<std::pair<int, int>> move_to_data;
virtual void move_to(int x, int y) final {
ASSERT_FALSE(move_to_data.has_value());
move_to_data = {x, y};
}
virtual void line_to(int x1, int y1) final {
ASSERT_TRUE(move_to_data.has_value());
auto const [x0, y0] = *move_to_data;
line_mock(x0, y0, x1, y1);
move_to_data.reset();
}
MOCK_METHOD(void, line_mock, (int x0, int y0, int x1, int y1));
};
void draw_two_lines(ITurtle &t)
{
t.line(0, 0, 10, 0);
t.line(0, 0, 0, 10);
}
TEST(TurtleTest, two_lines)
{
TurtleMock sot;
EXPECT_CALL(sot, line_mock(0, 0, 10, 0));
EXPECT_CALL(sot, line_mock(0, 0, 0, 10));
draw_two_lines(sot);
}
这允许在一个"帧"中指定所有4个参数;原子";匹配,使得InSequence
的整个东西变得不必要。不管draw_two_lines()
中line()
调用的顺序如何,上述示例都通过。
您的draw_grid()
测试将变为(实际示例(:
TEST(TurtleTest, grid)
{
TurtleMock sot;
for (int r = 0; r < 100; r += 10)
{
EXPECT_CALL(sot, line_mock(0, r, 100, r));
for (int c = 0; c < 100; c += 10) {
EXPECT_CALL(sot, line_mock(c, r, c, r+10));
}
EXPECT_CALL(sot, line_mock(100, r, 100, r + 10));
}
EXPECT_CALL(sot, line_mock(0, 100, 100, 100));
draw_grid(sot);
}
注意:此解决方案假定您不能或不想使ITurtle::line()
虚拟化。如果是的话,您当然可以放弃辅助move_to_data
和line_mock()
,而直接模拟line()
。