Trompeloeil 模拟的简明 RAII
Concise RAII for Trompeloeil mocks
我有class这样的:
/* "Things" can be "zarked", but only when opened, and they must be closed afterwards */
class ThingInterface {
public:
// Open the thing for exclusive use
virtual void Open();
// Zark the thing.
virtual void Zark(int amount);
// Close the thing, ready for the next zarker
virtual void Close();
};
/* RAII zarker - opens Things before zarking, and closes them afterwards */
class Zarker {
public:
Zarker(Thing& thing): m_thing(thing) {
m_thing.Open();
}
~Zarker() {
m_thing.Close();
}
void ZarkBy(int amount) {
m_thing.Zark(amount);
}
private:
Thing& m_thing;
}
然后我有一个 Trompeloeil 模拟 class 实现接口:
struct MockThing: public ThingInterface {
MAKE_MOCK0(Open, void(), override);
MAKE_MOCK1(Zark, void(int), override);
MAKE_MOCK0(Close, void(), override);
};
然后我想测试 42: 的“zarking”,包括(关键)Open
和 Close
程序。
MockThing mockedThing;
trompeloeil::sequence sequence;
REQUIRE_CALL(mockedThing, Open())
.IN_SEQUENCE(sequence));
REQUIRE_CALL(mockedThing, Zark(42))
.IN_SEQUENCE(sequence));
REQUIRE_CALL(mockedThing, Close())
.IN_SEQUENCE(sequence));
Zarker zarker(mockedThing);
zarker.ZarkBy(42);
这很好用,并证明了 Zarker
实际上会在使用后关闭其 Thing
(如果不这样做,就会发生坏事)。
现在,我还有很多测试要做,我想保持 DRY 状态,避免重复对 Open
和 Close
调用的模拟期望。在现实生活中,这些期望实际上不仅仅是一对函数,还有其他设置和拆卸操作必须按照仔细的顺序进行,并且在每次测试中都必须重复这些操作会非常烦人。
由于这是 RAII 惯用语,因此将其用于期望似乎很自然。但是,由于 Trompeloeil 期望是有范围的,因此您必须使用 NAMED_REQUIRE_CALL
并保存 unique_ptr<trompeloeil::expectations>
:
class RaiiThingAccessChecker {
public:
/* On construction, append the setup expectation(s) */
RaiiThingAccessChecker(
MockThing& thing,
trompeloeil::sequence& sequence,
std::vector<std::unique_ptr<trompeloeil::expectation>>& expectations
):
m_thing(thing),
m_sequence(sequence),
m_expectations(expectations) {
m_expectations.push_back(
NAMED_REQUIRE_CALL(m_thing, Open())
.IN_SEQUENCE(m_sequence)
);
}
/* On wrapper destruction, append the teardown expectation(s) */
~RaiiThingAccessChecker() {
m_expectations.push_back(
NAMED_REQUIRE_CALL(m_thing, Close())
.IN_SEQUENCE(m_sequence)
);
}
private:
MockThing& m_thing,
trompeloeil::sequence& m_sequence,
std::vector<std::unique_ptr<trompeloeil::expectation>>& m_expectations
}
那么你可以这样使用它:
MockThing mockedThing;
trompeloeil::sequence seq;
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
{
RaiiThingAccessChecker checker(mockedThing, seq, expectations);
mockExpectations.push_back(
NAMED_REQUIRE_CALL(mockedThing, Zark(42))
.IN_SEQUENCE(seq)
);
}
Zarker zarker(mockedThing));
zarker.ZarkBy(42);
所以这已经足够实用了,但它似乎相当冗长 - 你必须坚持
期望指针的容器,RaiiThingAccessChecker
之外的序列
而且你还必须将它们全部传入,并在内部存储引用。
有没有更简洁的方法,或者其他惯用的方法来实现这种
“期望重用”?我有很多“模块化”的期望可以像这样建模。
我倾向于创建函数而不是 RAII class 那里:
std::unique_ptr<std::pair<MockThing, trompeloeil::sequence>>
MakeMockedThing(std::function<void(MockThing&, trompeloeil::sequence&)> inner)
{
auto res = std::make_unique<std::pair<MockThing, trompeloeil::sequence>>();
auto& [mock, sequence] = *res;
REQUIRE_CALL(mock, Open()).IN_SEQUENCE(sequence));
inner(mock, sequence);
REQUIRE_CALL(mock, Close()).IN_SEQUENCE(sequence));
return res;
);
然后
auto p = MakeMockedThing([](MockThing& thing, trompeloeil::sequence& sequence)
{
REQUIRE_CALL(thing, Zark(42)).IN_SEQUENCE(sequence));
});
auto& [mock, sequence] = *p;
Zarker zarker(mock);
zarker.ZarkBy(42);
注意:std::pair<MockThing, trompeloeil::sequence>
可能会被模板 class 取代以获得更好的语法。
我有class这样的:
/* "Things" can be "zarked", but only when opened, and they must be closed afterwards */
class ThingInterface {
public:
// Open the thing for exclusive use
virtual void Open();
// Zark the thing.
virtual void Zark(int amount);
// Close the thing, ready for the next zarker
virtual void Close();
};
/* RAII zarker - opens Things before zarking, and closes them afterwards */
class Zarker {
public:
Zarker(Thing& thing): m_thing(thing) {
m_thing.Open();
}
~Zarker() {
m_thing.Close();
}
void ZarkBy(int amount) {
m_thing.Zark(amount);
}
private:
Thing& m_thing;
}
然后我有一个 Trompeloeil 模拟 class 实现接口:
struct MockThing: public ThingInterface {
MAKE_MOCK0(Open, void(), override);
MAKE_MOCK1(Zark, void(int), override);
MAKE_MOCK0(Close, void(), override);
};
然后我想测试 42: 的“zarking”,包括(关键)Open
和 Close
程序。
MockThing mockedThing;
trompeloeil::sequence sequence;
REQUIRE_CALL(mockedThing, Open())
.IN_SEQUENCE(sequence));
REQUIRE_CALL(mockedThing, Zark(42))
.IN_SEQUENCE(sequence));
REQUIRE_CALL(mockedThing, Close())
.IN_SEQUENCE(sequence));
Zarker zarker(mockedThing);
zarker.ZarkBy(42);
这很好用,并证明了 Zarker
实际上会在使用后关闭其 Thing
(如果不这样做,就会发生坏事)。
现在,我还有很多测试要做,我想保持 DRY 状态,避免重复对 Open
和 Close
调用的模拟期望。在现实生活中,这些期望实际上不仅仅是一对函数,还有其他设置和拆卸操作必须按照仔细的顺序进行,并且在每次测试中都必须重复这些操作会非常烦人。
由于这是 RAII 惯用语,因此将其用于期望似乎很自然。但是,由于 Trompeloeil 期望是有范围的,因此您必须使用 NAMED_REQUIRE_CALL
并保存 unique_ptr<trompeloeil::expectations>
:
class RaiiThingAccessChecker {
public:
/* On construction, append the setup expectation(s) */
RaiiThingAccessChecker(
MockThing& thing,
trompeloeil::sequence& sequence,
std::vector<std::unique_ptr<trompeloeil::expectation>>& expectations
):
m_thing(thing),
m_sequence(sequence),
m_expectations(expectations) {
m_expectations.push_back(
NAMED_REQUIRE_CALL(m_thing, Open())
.IN_SEQUENCE(m_sequence)
);
}
/* On wrapper destruction, append the teardown expectation(s) */
~RaiiThingAccessChecker() {
m_expectations.push_back(
NAMED_REQUIRE_CALL(m_thing, Close())
.IN_SEQUENCE(m_sequence)
);
}
private:
MockThing& m_thing,
trompeloeil::sequence& m_sequence,
std::vector<std::unique_ptr<trompeloeil::expectation>>& m_expectations
}
那么你可以这样使用它:
MockThing mockedThing;
trompeloeil::sequence seq;
std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
{
RaiiThingAccessChecker checker(mockedThing, seq, expectations);
mockExpectations.push_back(
NAMED_REQUIRE_CALL(mockedThing, Zark(42))
.IN_SEQUENCE(seq)
);
}
Zarker zarker(mockedThing));
zarker.ZarkBy(42);
所以这已经足够实用了,但它似乎相当冗长 - 你必须坚持
期望指针的容器,RaiiThingAccessChecker
之外的序列
而且你还必须将它们全部传入,并在内部存储引用。
有没有更简洁的方法,或者其他惯用的方法来实现这种 “期望重用”?我有很多“模块化”的期望可以像这样建模。
我倾向于创建函数而不是 RAII class 那里:
std::unique_ptr<std::pair<MockThing, trompeloeil::sequence>>
MakeMockedThing(std::function<void(MockThing&, trompeloeil::sequence&)> inner)
{
auto res = std::make_unique<std::pair<MockThing, trompeloeil::sequence>>();
auto& [mock, sequence] = *res;
REQUIRE_CALL(mock, Open()).IN_SEQUENCE(sequence));
inner(mock, sequence);
REQUIRE_CALL(mock, Close()).IN_SEQUENCE(sequence));
return res;
);
然后
auto p = MakeMockedThing([](MockThing& thing, trompeloeil::sequence& sequence)
{
REQUIRE_CALL(thing, Zark(42)).IN_SEQUENCE(sequence));
});
auto& [mock, sequence] = *p;
Zarker zarker(mock);
zarker.ZarkBy(42);
注意:std::pair<MockThing, trompeloeil::sequence>
可能会被模板 class 取代以获得更好的语法。