如何仅覆盖一个EXPECT_CALL的默认ON_CALL操作,稍后再返回到默认操作

How to override the default ON_CALL action for just one EXPECT_CALL and go back to the default action later

提问人:pptaszni 提问时间:7/4/2018 更新时间:7/20/2018 访问量:8081

问:

我想测试我的系统的方法,其返回值部分取决于对某种连接接口的调用的返回值。在大多数情况下,我希望在对它的方法进行任何形式的调用时返回。除了一种情况,当我显式测试连接失败的条件时。IConnectiontrueopen(_, _)

例:

/*
 * Some kind of network interface with method `open`
 */
class IConnection {
public:
    IConnection() = default;
    virtual ~IConnection() = default;
    virtual bool open(const std::string& address, int port) = 0;
};

class ConnectionMock: public IConnection {
public:
    MOCK_METHOD2(open, bool(const std::string& address, int port));
};

class MySystem {
public:
    MySystem() = delete;
    MySystem(std::shared_ptr<IConnection> connection): connection_(connection) {}
    bool doSth() {
        /*
         * Do some things, but fail if connection fails
         */
        bool connectionStatus = connection_->open("127.0.0.1", 6969);
        if (!connectionStatus) {
            return false;
        }
        // do other things
        return true;
    }
private:
    std::shared_ptr<IConnection> connection_;
};

TEST(MySystemShould, returnFalseIfFailedToOpenConnectionAndTrueIfSucceeded) {
    auto connectionMock = std::make_shared<NiceMock<ConnectionMock> >();
    ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
    MySystem system(connectionMock);
    // if I don't specify Times test fill fail, because WillOnce automatically sets Times(1)
    EXPECT_CALL(*connectionMock, open(_, _)).Times(AnyNumber()).WillOnce(Return(false));
    /*
     * Commented code below is not a good solution - after expectation retires
     * the test will fail upon subsequent calls
     */
    //EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).RetiresOnSaturation();
    ASSERT_FALSE(system.doSth());
    /*
     * Code bellow allows me to avoid the warning
     */
    //EXPECT_CALL(*connectionMock, open(_, _)).WillRepeatedly(Return(true));
    ASSERT_TRUE(system.doSth());
}

我当前解决方案的问题是,当覆盖变得饱和时,即使 gmock 返回到 上指定的默认操作,每次后续调用都会导致以下警告:EXPECT_CALLON_CALLopen(_, _)

GMOCK WARNING:
/whatever.cpp:105: Actions ran out in EXPECT_CALL(*connectionMock, open(_, _))...
Called 2 times, but only 1 WillOnce() is specified - taking default action specified at:
/whatever.cpp:103:

即使我正在使用 .我可以通过指定 来摆脱警告,但这是我在 中的代码的重复。NiceMockEXPECT_CALLWillRepeatedly(Return(true))ON_CALL

我想知道,我怎样才能覆盖仅为一次调用指定的默认操作,然后返回默认值,而不会导致 gmock 打印警告。完美的解决方案类似于:ON_CALLIConnection::open

EXPECT_CALL(*connectionMock, open(_, _)).WillOnce(Return(false)).DisableExpectationAfterSaturation();

但它并不存在。 无法如我所愿,因为它在饱和后无法通过测试(与指定的操作不匹配)。RetiresOnSaturationON_CALL

C++ 谷歌模拟

评论


答:

8赞 Mike van Dyke 7/19/2018 #1

编辑 2
DoDefault() - 功能接近问题中提出的问题。它指定 中的操作应返回到以下项指定的默认操作:
EXPECT_CALLON_CALL

using ::testing::DoDefault;

// Default action
ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));

// returns true once and then goes back to the default action
EXPECT_CALL(*connectionMock, open(_, _)
  .WillOnce(Return(false))
  .WillRepeatedly(DoDefault());

初始答案

如果返回值取决于参数,则可以指定两次,但使用不同的参数(或者更确切地说是参数而不是占位符):IConnection::openON_CALL

ON_CALL(*connectionMock, open(_, _)).WillByDefault(Return(true));
ON_CALL(*connectionMock, open("BAD_ADDRESS", 20)).WillByDefault(Return(false));

因此,每当使用参数“BAD_ADDRESS”和 20 调用模拟方法时,它将返回 false,否则返回 true。open

下面是一个简单的示例:

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Return;

class A {
 public:
  virtual bool bla(int a) = 0;
};

class MOCKA : public A {
 public:
  MOCK_METHOD1(bla, bool(int));
};

TEST(ABC, aBABA) {
  MOCKA a;
  ON_CALL(a, bla(_)).WillByDefault(Return(false));
  ON_CALL(a, bla(1)).WillByDefault(Return(true));

  EXPECT_CALL(a, bla(_)).Times(AnyNumber());

  EXPECT_TRUE(a.bla(1));
  EXPECT_TRUE(a.bla(1));
  EXPECT_TRUE(a.bla(1));
  EXPECT_FALSE(a.bla(2));
  EXPECT_FALSE(a.bla(3));
  EXPECT_FALSE(a.bla(4));
}

编辑 1我想现在我明白了这个问题,如果我明白了,那么解决方案非常简单:

EXPECT_CALL(*connectionMock, open(_, _))
      .Times(AnyNumber())
      .WillOnce(Return(true))
      .WillRepeatedly(Return(false));

何时会在它内部调用一次,然后无论参数是什么,都会始终返回。在这种情况下,您也不需要指定 .或者你肯定需要指定操作而不是?ConnectionMock::openMySystem::doSthtruefalseON_CALLON_CALLEXPECT_CALL

评论

0赞 pptaszni 7/19/2018
谢谢你的回答。是的,我知道可以为不同的模拟方法参数匹配器指定不同的操作。但它不适用于我的情况。我的系统总是使用正确的 address:port,目标是测试在打开连接系统失败后是否仍处于良好状态,以便重试并稍后成功。
0赞 Mike van Dyke 7/19/2018
@Ptaq666请检查我的回答中的编辑。这就是你需要的行为吗?
0赞 pptaszni 7/19/2018
是的,我也知道一个解决方案,即使用单个指定第一个操作和后续操作。但这与覆盖 中的默认操作不同。通常的做法是在测试夹具构造函数中指定(我知道您也可以为此使用 AnyNumber)默认操作(因为也许我的系统会调用,也许不会),如果需要覆盖默认值,则稍后在特定操作中使用。能够做到这一点对我来说似乎是 gtest 中的理想行为。EXPECT_CALLWillOnceWillRepeatedlyON_CALLON_CALLEXPECT_CALLopenEXPECT_CALLTEST_F
1赞 Mike van Dyke 7/20/2018
@Ptaq666 好的,现在我明白了。但真的不知道该怎么做。我想到的最接近的可能是:' EXPECT_CALL(*connectionMock, open(_, _))。WillOnce(返回(true))。WillRepeatedly(DoDefault());' 这将返回一次,然后返回到 指定的默认操作。DoDefault()trueON_CALL
0赞 pptaszni 7/20/2018
O!正是这个()。我在 gmock 文档中找不到此功能。它回答了我问题的“稍后返回默认操作”部分。您可以将其发布在您的答案中。DoDefault