Dependency injection 对gmock对象应用ON_调用时没有影响

Dependency injection 对gmock对象应用ON_调用时没有影响,dependency-injection,googlemock,Dependency Injection,Googlemock,为了好玩,我正在写一个俄罗斯方块游戏。我创建了两个类:“GameBackground”和“Tetromino”。由于“Tetromino”类依赖于“GameBackground”类,因此我使用接口将此依赖注入构造函数中,这样我就可以通过模拟来测试独立于GameBackground类的“Tetromino”类。当我使用gmock创建“GameBackgroundMock”时,测试失败。当我用我自己的模仿 class MyOwnMock : public IGameBackground {

为了好玩,我正在写一个俄罗斯方块游戏。我创建了两个类:“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
  • CMakeLists.txt
src文件夹的内容
  • CMakeLists.txt
  • IGameBackground.h
  • TetrominoTest.cpp

您正在使用
ON_CALL
来设置调用模拟方法时要返回的默认值。如果您不关心给定的方法在测试中将被调用多少次,并且只设置一个默认值以返回任意次数(但是,expect调用首先被匹配,这是一个较长的过程),那么这非常有用

在您的案例中,最好使用
EXPECT\u CALL
明确说明您预期的操作并验证它们:

EXPECT_CALL(a_mock, RequestSpaceOnGrid(current_position)).WillOnce(::testing::Return(true));

如果您将使用此选项,gmock将让您知道测试的错误:调用了
RequestSpaceOnGrid
,但不是使用
当前位置调用,而是使用
预期位置调用。不确定这是否是预期的,但这是测试中发生的情况。

通过调用

ON_调用(一个_mock,RequestSpaceOnGrid(预期的_位置)).WillByDefault(::testing::Return(true))

或者按照夸拉的建议

EXPECT_调用(一个_mock,RequestSpaceOnGrid(expected_position)).WillOnce(::testing::Return(true))

关键的一点是给模拟的RequestSpaceOnGrid方法提供预期位置,而不是当前位置

另一种可能是让测试变为绿色,用
::testing::

ON_调用(a_mock,RequestSpaceOnGrid(::testing::)).WillByDefault(::testing::Return(true))


在这种情况下,mock方法将始终返回true,与给定参数无关。

Hi,欢迎使用stackoverflow!关于你的问题有一点评论:我想它包含了太多与你所面临的技术问题无关的细节和代码,请阅读以了解如何改进它的细节。我知道,如果你不确定什么工作不好,可能很难判断什么是相关的,但经验法则是,你应该尽量减少你发布的代码量,使其尽可能少,并且代码中仍然显示问题。嗨,夸拉,谢谢你的回答,正如你在上一段中已经注意到的,错误是使用
预期位置调用模拟方法,而不是使用
当前位置调用模拟方法。

    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)


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


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

EXPECT_CALL(a_mock, RequestSpaceOnGrid(current_position)).WillOnce(::testing::Return(true));