如何使用gmock模拟具体类中的非虚拟方法



我以某种方式从donsoft.io的例子中扩展了gmock测试用例,并使其如下:

coinflipper/
├── BUILD
├── WORKSPACE
├── coinflipper.cc
├── coinflipper.h
├── rng.cc
└── rng.h

好吧,我把Rng类作为CoinFlipper构造函数的参数,而是在CoinFlipper::flipCoin()方法中初始化它。

我想知道在这种情况下如何从Rng模拟generate()

coinflipper.cc

#include "coinflipper.h"
#include <iostream>
CoinFlipper::CoinFlipper() {}
CoinFlipper::Result CoinFlipper::flipCoin() const {
Rng d_rng;
const double val = d_rng.generate(0.0, 1.0);
return (val < 0.5) ? HEADS : TAILS;
}
int main(int argc, char** argv) {
CoinFlipper cf;
CoinFlipper::Result res = cf.flipCoin();
if (res == 0) {
std::cout << "head" << std::endl;
} else {
std::cout << "tail" << std::endl;
}
return 0;
}

coinfipper.h

#include "rng.h"
class CoinFlipper {
public:
enum Result { HEADS = 0, TAILS = 1 };
explicit CoinFlipper();
Result flipCoin() const;
};

rng.h

#ifndef RNG_H
#define RNG_H
class Rng {
public:
Rng() {};
~Rng() {};
double generate(double min, double max);
};
#endif

rng.cc

#include "rng.h"
double Rng::generate(double min, double max) {    
return 0.75; //Just for test
}

构建

load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library")
cc_library(
name = "rng",
srcs = ["rng.cc"],
hdrs = ["rng.h"],
)
cc_binary(
name = "coinflipper",
srcs = ["coinflipper.cc", "coinflipper.h"],    
deps = [
":rng",
],
)
cc_test(
name = "coinflipper_test",
size = "small",
srcs = ["coinflipper_test.cc", "rng.h","coinflipper.h","coinflipper.cc"],
deps = ["@com_google_googletest//:gtest_main"],
)

coinflipper_test.cc

#include "coinflipper.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
class MockRng {
public:  
MOCK_METHOD2(generate, double(double, double));
};
TEST(CoinFlipper, ShouldReturnHeadsIfRandValueIsLessThanProbability) {
MockRng rng;
EXPECT_CALL(rng, generate(::testing::DoubleEq(0.0), ::testing::DoubleEq(1.0)))
.Times(::testing::Exactly(1))
.WillOnce(::testing::Return(0.25));
CoinFlipper coinFlipper;
auto result = coinFlipper.flipCoin<MockRng>(rng);
EXPECT_EQ(CoinFlipper::HEADS, result);
}

Bazel输出:

$ bazel test --test_output=all //:coinflipper_test
DEBUG: Rule 'rules_cc' indicated that a canonical reproducible form can be obtained by modifying arguments sha256 = "56ac9633c13d74cb71e0546f103ce1c58810e4a76aa8325da593ca4277908d72"
DEBUG: Repository rules_cc instantiated at:
/Users/pvd/Downloads/toys/cpp/bazelgtest/coinflipper/WORKSPACE:9:13: in <toplevel>
Repository rule http_archive defined at:
/private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/external/bazel_tools/tools/build_defs/repo/http.bzl:336:31: in <toplevel>
DEBUG: Rule 'com_google_googletest' indicated that a canonical reproducible form can be obtained by modifying arguments sha256 = "5cf189eb6847b4f8fc603a3ffff3b0771c08eec7dd4bd961bfd45477dd13eb73"
DEBUG: Repository com_google_googletest instantiated at:
/Users/pvd/Downloads/toys/cpp/bazelgtest/coinflipper/WORKSPACE:3:13: in <toplevel>
Repository rule http_archive defined at:
/private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/external/bazel_tools/tools/build_defs/repo/http.bzl:336:31: in <toplevel>
INFO: Analyzed target //:coinflipper_test (0 packages loaded, 0 targets configured).
INFO: Found 1 test target...
FAIL: //:coinflipper_test (see /private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/execroot/__main__/bazel-out/darwin-fastbuild/testlogs/coinflipper_test/test.log)
INFO: From Testing //:coinflipper_test:
==================== Test output for //:coinflipper_test:
dyld: lazy symbol binding failed: Symbol not found: __ZN3Rng8generateEdd
Referenced from: /private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/sandbox/darwin-sandbox/72/execroot/__main__/bazel-out/darwin-fastbuild/bin/coinflipper_test.runfiles/__main__/coinflipper_test
Expected in: flat namespace
dyld: Symbol not found: __ZN3Rng8generateEdd
Referenced from: /private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/sandbox/darwin-sandbox/72/execroot/__main__/bazel-out/darwin-fastbuild/bin/coinflipper_test.runfiles/__main__/coinflipper_test
Expected in: flat namespace
================================================================================
Target //:coinflipper_test up-to-date:
bazel-bin/coinflipper_test
INFO: Elapsed time: 0.429s, Critical Path: 0.14s
INFO: 2 processes: 2 darwin-sandbox.
INFO: Build completed, 1 test FAILED, 2 total actions
//:coinflipper_test                                                      FAILED in 0.1s
/private/var/tmp/_bazel_pvd/3077b447e558b1418694504407cbcb45/execroot/__main__/bazel-out/darwin-fastbuild/testlogs/coinflipper_test/test.log
INFO: Build completed, 1 test FAILED, 2 total actions

定义依赖项(此处为随机生成器(,因为不建议使用局部变量,所以更难进行依赖项注入(否则就不可能了(,所以我将函数Rng_t更改为模板函数,并将Rng作为参数传递。

在实践中构造一个随机生成可能是繁重的工作,它需要初始化它的内部状态,每次构造它调用函数flipCoin都是浪费。

非虚拟函数可以被模拟,最常用的策略之一是使用模板,在这里我们将类CoinFlipper的成员函数作为模板函数,然后我们可以用我们的MockRng测试依赖性。

请注意,对于模板函数,我们需要在头文件中定义成员函数。

coinflipper.h:

#pragma once
#include "rng.h"
class CoinFlipper {
public:
enum Result { HEADS = 0, TAILS = 1 };
template <typename Rng_t>
Result flipCoin(Rng_t& rng) {
const double val = rng.generate(0.0, 1.0);
return (val < 0.5) ? HEADS : TAILS;
}
};

测试文件部分MockRng现在没有继承任何内容。我们在这里使用的测试成员函数的类型是CoinFlipper::flipCoin<MockRng>。对于生产代码:我们使用类型CoinFlipper::flipCoin<Rng>

//#include "mockrng.h"
#include "coinflipper.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
class MockRng {
public:
MOCK_METHOD2(generate, double(double, double));
};
TEST(CoinFlipper, ShouldReturnHeadsIfRandValueIsLessThanProbability) {
MockRng rng;
EXPECT_CALL(rng, generate(::testing::DoubleEq(0.0), ::testing::DoubleEq(1.0)))
.Times(::testing::Exactly(1))
.WillOnce(::testing::Return(0.25));
CoinFlipper coinFlipper;
auto result = coinFlipper.flipCoin<MockRng>(rng);
EXPECT_EQ(CoinFlipper::HEADS, result);
}

请参阅此处的相关问题:

模拟非虚拟方法C++(gmock(

官方文件:

https://chromium.googlesource.com/external/github.com/google/googletest/+/refs/tags/release-18.8.0/googlemock/docs/CookBook.md

相关内容

最新更新