在 gmock 对象上应用 ON_CALL 没有效果

No effect at applying of ON_CALL on a gmock object

出于一些有趣的目的,我正在编写俄罗斯方块游戏。我创建了两个 classes:“GameBackground”和“Tetromino”。由于“Tetromino”class 依赖于“GameBackground”class,我使用一个接口在构造函数中注入了这种依赖性,这样我就可以测试“Tetromino”class 独立于GameBackground class 通过模拟它。当我使用 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

项目顶层对应的文件:


    # 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      ""
    )


    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 文件夹的内容:


    cmake_minimum_required(VERSION 3.11.3)
    add_library(GameBackground_Lib STATIC GameBackground.cpp)
    add_library(Tetromino_Lib STATIC Tetromino.cpp)


    #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


    #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


    #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;
    }


    #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


    #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;
        }
    }

test 文件夹的内容


    cmake_minimum_required(VERSION 3.11.3)
    add_executable(TetrominoTest TetrominoTest.cpp)
    target_link_libraries(TetrominoTest gmock_main gtest_main Tetromino_Lib)


    #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},
                                            {0, 2},
                                            {0, 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));

在这种情况下,模拟方法总是 return 真,与给定参数无关。