如何解决在gmock对象上应用ON_CALL无效
出于有趣的目的,我正在编写一个俄罗斯方块游戏。我创建了两个类:“ GameBackground”和“ Tetromino”。由于“ Tetromino”类对“ GameBackground”类具有依赖关系,因此我使用接口将该依赖项注入到构造函数中,这样我就可以通过模拟它来独立于GameBackground类来测试“ Tetromino”类。当我使用gmock创建“ GameBackgroundMock”时,测试失败。当我使用自己的模拟游戏
class MyOwnMock : public IGameBackground {
public:
MyOwnMock() : IGameBackground() {
};
bool RequestSpaceOnGrid(TetrominopositionType requested_coordinates) override{
return true;
}
};
然后测试通过。显然,我没有以正确的方式使用gmock。我会很感激使用gmock进行绿色测试的任何建议。
有关实现的详细信息,请参见以下代码基础。
我的初始文件夹结构如下:
[项目结构] [1] [1]:https://i.stack.imgur.com/7h8zT.png
项目顶级中的相应文件:
- CMakeLists.txt.in :
# see https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONfigURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
- CMakeLists.txt :
cmake_minimum_required(VERSION 3.11.3)
project(Test)
set(CMAKE_CXX_STANDARD 17)
# Download and unpack googletest at configure time
# (see https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project)
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "CMake step for googletest Failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "Build step for googletest Failed: ${result}")
endif()
# Add googletest directly to our build. This defines the gtest and gtest_main targets.
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
add_subdirectory(src)
add_subdirectory(test)
src文件夹的内容:
- CMakeLists.txt :
cmake_minimum_required(VERSION 3.11.3)
add_library(GameBackground_Lib STATIC GameBackground.cpp)
add_library(Tetromino_Lib STATIC Tetromino.cpp)
- IGameBackground.h
#ifndef TEST_IGAMEBACKGROUND_H
#define TEST_IGAMEBACKGROUND_H
#include <utility>
#include <vector>
#include <iostream>
using TetrominopositionType = std::vector<std::pair<int,int>>;
class IGameBackground {
public:
virtual ~IGameBackground() = default;
virtual bool RequestSpaceOnGrid(TetrominopositionType) = 0;
};
#endif //TEST_IGAMEBACKGROUND_H
- GameBackground.h
#ifndef TEST_GAMEBACKGROUND_H
#define TEST_GAMEBACKGROUND_H
#include "IGameBackground.h"
#include <tuple>
#include <utility>
#include <vector>
class GameBackground : public IGameBackground {
public:
GameBackground(int,int);
bool RequestSpaceOnGrid(TetrominopositionType) override;
private:
int m_horizontal_grid_size;
int m_vertical_grid_size;
int m_nr_buttomlines_filled{};
std::vector<std::vector<bool>> m_occupancy_grid{};
};
#endif //TEST_GAMEBACKGROUND_H
- GameBackground.cpp
#include "GameBackground.h"
GameBackground::GameBackground(int vertical_grid_size,int horizontal_grid_size)
: m_horizontal_grid_size{horizontal_grid_size},m_vertical_grid_size{vertical_grid_size} {
m_occupancy_grid.resize(vertical_grid_size);
for (auto &row : m_occupancy_grid) {
row.resize(horizontal_grid_size);
}
}
bool GameBackground::RequestSpaceOnGrid(
TetrominopositionType requested_coordinates) {
bool is_every_coordinate_within_bounds{true};
for (const auto &position : requested_coordinates) {
int pos_x{position.first},pos_y{position.second};
if (pos_x < 0 || pos_x >= m_vertical_grid_size || pos_y < 0 ||
pos_y >= m_horizontal_grid_size) {
is_every_coordinate_within_bounds = false;
break;
}
}
bool is_request_successfull{false};
if (is_every_coordinate_within_bounds) {
try {
bool is_occupied{false};
for (const auto &position : requested_coordinates) {
int row{position.first},column{position.second};
is_occupied |= m_occupancy_grid.at(row).at(column);
}
if (!is_occupied) {
for (const auto &position : requested_coordinates) {
int row{position.first},column{position.second};
m_occupancy_grid.at(row).at(column) = true;
}
is_request_successfull = true;
}
} catch (std::out_of_range const &e) {
std::cerr << e.what() << '\n';
}
}
return is_request_successfull;
}
- Tetromino.h
#ifndef TEST_TETROMINO_H
#define TEST_TETROMINO_H
#include <memory>
#include <utility>
#include <vector>
#include "IGameBackground.h"
enum class Direction { left,right,down };
using TetrominopositionType = std::vector<std::pair<int,int>>;
class Tetromino {
public:
Tetromino(IGameBackground&,TetrominopositionType);
TetrominopositionType getPosition();
void setPosition(TetrominopositionType);
void moveOnestep(Direction);
private:
TetrominopositionType m_position;
IGameBackground& m_game_background;
};
#endif //TEST_TETROMINO_H
- Tetromino.cpp
#include "Tetromino.h"
#include <utility>
Tetromino::Tetromino(IGameBackground &game_background,TetrominopositionType init_position)
: m_game_background{game_background},m_position{std::move(init_position)} {};
TetrominopositionType Tetromino::getPosition() { return m_position; }
void Tetromino::setPosition(TetrominopositionType position) {
m_position = std::move(position);
}
void Tetromino::moveOnestep(Direction direction) {
TetrominopositionType position = getPosition();
switch (direction) {
case Direction::down:
for (auto &pos : position) {
++pos.first;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
case Direction::left:
for (auto &pos : position) {
--pos.second;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
case Direction::right:
for (auto &pos : position) {
++pos.second;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
}
}
测试文件夹
的内容- CMakeLists.txt
cmake_minimum_required(VERSION 3.11.3)
add_executable(TetrominoTest TetrominoTest.cpp)
target_link_libraries(TetrominoTest gmock_main gtest_main Tetromino_Lib)
- TetrominoTest.cpp
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "../src/IGameBackground.h"
#include "../src/Tetromino.h"
//class MyOwnMock : public IGameBackground {
//public:
// MyOwnMock() : IGameBackground() {
// };
// bool RequestSpaceOnGrid(TetrominopositionType requested_coordinates) override{
// return true;
// }
//};
class GameBackgroundMock : public IGameBackground {
public:
GameBackgroundMock() : IGameBackground() {
};
MOCK_METHOD(bool,RequestSpaceOnGrid,(TetrominopositionType requested_coordinates),(override));
};
class MoveTetromino : public ::testing::Test {
protected:
MoveTetromino() : unit_under_test(a_mock,init_position) {};
TetrominopositionType init_position{{0,0},{0,1},2},3}};
Tetromino unit_under_test;
GameBackgroundMock a_mock;
//MyOwnMock a_mock;
};
TEST_F(MoveTetromino,move_right) {
TetrominopositionType current_position{init_position};
TetrominopositionType expected_position{init_position};
for (auto &elem : expected_position) {
elem.second++;
}
ON_CALL(a_mock,RequestSpaceOnGrid(current_position)).WillByDefault(::testing::Return(true));
unit_under_test.moveOnestep(Direction::right);
TetrominopositionType actual_position = unit_under_test.getPosition();
EXPECT_EQ(expected_position,actual_position);
}
解决方法
您正在使用ON_CALL
来设置在调用模拟方法时要返回的默认值。如果您不在乎测试中给定方法将被调用多少次,而您只是将默认值设置为可以返回任意次数(这是很长的话),这很有用。
在您的情况下,最好使用EXPECT_CALL
来明确说明您预期的动作并验证它们:
EXPECT_CALL(a_mock,RequestSpaceOnGrid(current_position)).WillOnce(::testing::Return(true));
如果您将使用此方法,则gmock会告诉您测试出了什么问题:RequestSpaceOnGrid
被调用,但是不是通过current_position
而是通过expected_position
进行的。不知道是否可以预期,但这就是测试中发生的情况。
通过以下任一方式通过测试
ON_CALL(a_mock,RequestSpaceOnGrid(expected_position)).WillByDefault(::testing::Return(true));
或Quarra建议
EXPECT_CALL(a_mock,RequestSpaceOnGrid(expected_position)).WillOnce(::testing::Return(true));
关键点是为模拟的RequestSpaceOnGrid方法提供 expected_position 而不是 current_position 。
另一种可能性是使测试变为绿色,以使用::testing::_
调用模拟方法
ON_CALL(a_mock,RequestSpaceOnGrid(::testing::_)).WillByDefault(::testing::Return(true));
在这种情况下,模拟方法将始终返回true,而与给定参数无关。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。