GoogleTest 模拟对象方法调用失败

GoogleTest Mocking object method call fail

大家好,这是我第一次使用 googleTest Mocking。该项目的一般设置是 coffeeMachine 有一个对象 coffeeStorage 和 coffeeStockService,它从 coffeeMachine 方法调用方法。我正在尝试为对象 coffeeStorage 和 coffeeStockService 创建模拟测试,但是当我将模拟对象分配给 coffeeMachine 实例时它无法识别预期调用。

尝试实现 GoogleTest 的模拟测试框架,但它无法识别 EXPECTED_CALL 并且也不让我使用 mockedCoffeeStorage 作为 mockedCoffeeStockService 方法调用的参数。StockService 预期调用是因为 CoffeeMachine 也在调用它但我将 coffeeStorage 作为参数传入。模拟对象是否可能导致使用另一个模拟对象作为参数的方法并且仍然期望调用该方法?既然我已经声明了 CofeeStorage 和 CoffeeStockService 对象,我是否应该重构并且不包含对 CoffeeMachine 方法的所有对象引用?非常感谢

class coffeeMachine {
    public:
        coffeeStorage* CoffeeStorage;
        coffeeStockService* CoffeeStockService;
        bool coffeeServed = false;
        coffeeMachine(coffeeStorage* CoffeeStorage, coffeeStockService* CoffeeStockService);
        void start(string langIn);
        void stop();
        void descale();
        void showSettings();
        void hideSettings();
        map<string, string> getSetting();
        string message(coffeeStorage& CoffeeStorage);
        void takeCoffee(coffeeStorage& CoffeeStorage);
        void fillTank(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService);
        void fillBeans(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService);
        void emptyGrounds(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService);
        bool isDescalingNeeded();

class coffeeStorage {
    private:
        int waterStorage;
        int beanStorage;
        int trashStorage;

    public:
        //virtual ~coffeeStorage(){}
        coffeeStorage();
        virtual void takeCoffeeStorage();
        virtual void setWaterStorage(int amount);
        virtual void setBeansStorage(int amount);
        virtual void emptyTrashStorage();
        virtual int checkWater();
        virtual int checkBeans();
        virtual int checkTrashStorage();
};

class mockCoffeeStorage : public coffeeStorage {
    private:
        int waterStorage;
        int beanStorage;
        int trashStorage;

    public:

    MOCK_METHOD(void, takeCoffeeStorage, (), (override));
    MOCK_METHOD(void, setWaterStorage, (int amount), (override));
    MOCK_METHOD(void, setBeansStorage, (int amount), (override));
    MOCK_METHOD(void, emptyTrashStorage, (), (override));
    MOCK_METHOD(int, checkWater, (), (override));
    MOCK_METHOD(int, checkBeans, (), (override));
    MOCK_METHOD(int, checkTrashStorage, (), (override));
};

coffeeMachine::coffeeMachine(coffeeStorage* CoffeeStorage_, coffeeStockService* CoffeeStockService_){

    waterHardness = "2";
    grinder = "medium";
    started = false;
    coffeeServed = false;
    settingsDisplayed = false;

    CoffeeStorage = CoffeeStorage_;
    CoffeeStockService = CoffeeStockService_;

    message(*CoffeeStorage_);
    descale();
    fillTank(*CoffeeStorage_, *CoffeeStockService_);
    fillBeans(*CoffeeStorage_, *CoffeeStockService_);
    emptyGrounds(*CoffeeStorage_, *CoffeeStockService_);
}

string coffeeMachine::message(coffeeStorage& CoffeeStorage){
        if(!started) return "";

        if (settingsDisplayed) return i18n("settings");
        if (CoffeeStorage.checkWater() <= 10) return i18n("tank");
        if (CoffeeStorage.checkBeans() < 3) return i18n("beans");
        if (CoffeeStorage.checkTrashStorage() >= 30) return i18n("grounds");
        if (isDescalingNeeded()) return i18n("descale");
        return i18n("ready");
}

void coffeeMachine::takeCoffee(coffeeStorage& CoffeeStorage){
       if (CoffeeStorage.checkWater() == 0 || CoffeeStorage.checkBeans() == 0) {
            coffeeServed = false;
        } else {
            coffeeServed = true;
            CoffeeStorage.takeCoffeeStorage();
            countdownToDescale -= 1;
        }
}

void coffeeMachine::fillTank(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService){
    CoffeeStockService.restockWater(CoffeeStorage);
}

void coffeeMachine::fillBeans(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService){
    CoffeeStockService.restockBeans(CoffeeStorage);
}

void coffeeMachine::emptyGrounds(coffeeStorage& CoffeeStorage, coffeeStockService& CoffeeStockService){
    CoffeeStockService.emptyTrash(CoffeeStorage);
}

TEST(MockObjTest, TestObjectCallsWithMock) {

    mockCoffeeStorage mockedCoffeeStorage;
    mockCoffeeStockService mockedCoffeeStockService;

    EXPECT_CALL(mockedCoffeeStorage, checkWater())
        .Times(AtLeast(1));

    EXPECT_CALL(mockedCoffeeStorage, checkBeans())
        .Times(AtLeast(1));

    EXPECT_CALL(mockedCoffeeStorage, takeCoffeeStorage())
        .Times(AtLeast(1));

    coffeeMachine coffeeTest = coffeeMachine(&mockedCoffeeStorage, &mockedCoffeeStockService);

    std::cout<< "Address of mockedCoffeeStorage: " << &mockedCoffeeStorage << "\n";
    std::cout<< "Address of mockedCoffeeStockService: " << &mockedCoffeeStockService << "\n";

    // EXPECT_CALL(mockedCoffeeStockService, restockWater(mockedCoffeeStorage))
    //     .Times(AtLeast(1));
    // EXPECT_CALL(mockedCoffeeStockService, restockBeans(mockedCoffeeStorage))
    //     .Times(AtLeast(1));
    // EXPECT_CALL(mockedCoffeeStockService, emptyTrash(mockedCoffeeStorage))
    //     .Times(AtLeast(1));

    coffeeTest.start("en");



       for(int i = 0; i < 10; i++)
    {
        if(coffeeTest.getBeansContent(mockedCoffeeStorage) < 5)
            coffeeTest.fillBeans(mockedCoffeeStorage, mockedCoffeeStockService);
        if(coffeeTest.getGroundsContent(mockedCoffeeStorage) > 20)
            coffeeTest.emptyGrounds(mockedCoffeeStorage, mockedCoffeeStockService);
        if(coffeeTest.getTankContent(mockedCoffeeStorage) < 15)
            coffeeTest.fillTank(mockedCoffeeStorage, mockedCoffeeStockService);

        coffeeTest.takeCoffee(mockedCoffeeStorage);
    }
}
  1. 设置 expect calls 一个 mock 并将另一个 mock 作为参数是完全没问题的。
  2. 无需将对象传递给每个方法 - 您可以在函数内部访问它们,因为它们是成员。这种技术称为 'dependency injection'.

查看此代码(使用 GMock 1.8,因此 mock 定义略有不同):

// pure virt. interface, remember about virtual dtor
class coffeeStorage {
public:
    virtual ~coffeeStorage() = default;
    virtual void takeCoffeeStorage() = 0;
    virtual void setWaterStorage(int amount) = 0;
    virtual void setBeansStorage(int amount) = 0;
    virtual void emptyTrashStorage() = 0;
    virtual int checkWater() = 0;
    virtual int checkBeans() = 0;
    virtual int checkTrashStorage() = 0;
};

class coffeeStockService {
public:
    virtual ~coffeeStockService() = default;
    virtual void restockWater(coffeeStorage& storeage) = 0;
};

class coffeeMachine {
public:
    coffeeMachine(coffeeStorage* CoffeeStorage, coffeeStockService* CoffeeStockService);

    void fillTank() {
        CoffeeStockService->restockWater(*CoffeeStorage);
    }

    void takeCoffee() {
        if (CoffeeStorage->checkWater() == 0 || CoffeeStorage->checkBeans() == 0) {
            coffeeServed = false;
        }
        else {
            coffeeServed = true;
            CoffeeStorage->takeCoffeeStorage();
        }
    }

    bool isCoffeeServed() {
        return coffeeServed;
    }

private:
    coffeeStorage* CoffeeStorage;
    coffeeStockService* CoffeeStockService;
    bool coffeeServed{false};
};

coffeeMachine::coffeeMachine(coffeeStorage* CoffeeStorage_, coffeeStockService* CoffeeStockService_)
        : CoffeeStorage{CoffeeStorage_}
        , CoffeeStockService{CoffeeStockService_} {}

class mockCoffeeStorage : public coffeeStorage {
public:
    MOCK_METHOD0(takeCoffeeStorage, void());
    MOCK_METHOD1(setWaterStorage, void(int amount));
    MOCK_METHOD1(setBeansStorage, void(int amount));
    MOCK_METHOD0(emptyTrashStorage, void());
    MOCK_METHOD0(checkWater, int());
    MOCK_METHOD0(checkBeans, int());
    MOCK_METHOD0(checkTrashStorage, int());
};

class mockCoffeeStockService : public coffeeStockService {
public:
    MOCK_METHOD1(restockWater, void(coffeeStorage& storeage));
};

struct MockObjTest : public testing::Test {
    mockCoffeeStorage mockedCoffeeStorage;
    mockCoffeeStockService mockedCoffeeStockService;

    coffeeMachine coffeeTest = coffeeMachine(&mockedCoffeeStorage, &mockedCoffeeStockService);
};

TEST_F(MockObjTest, When_FillingTank_Then_RestocksWater) {
    // assert
    EXPECT_CALL(mockedCoffeeStockService, restockWater(testing::Ref(mockedCoffeeStorage)));

    // act
    coffeeTest.fillTank();
}

TEST_F(MockObjTest, Given_NoWater_When_TakingCoffee_Then_CoffeeNotServed) {
    // arrange
    EXPECT_CALL(mockedCoffeeStorage, checkWater()).WillOnce(testing::Return(0));

    // act
    coffeeTest.takeCoffee();

    // assert
    ASSERT_FALSE(coffeeTest.isCoffeeServed());
}

TEST_F(MockObjTest, Given_EnoughWaterWaterAndBeans_When_TakingCoffee_Then_takeCoffeeStorageAndCoffeeServed) {
    // arrange
    EXPECT_CALL(mockedCoffeeStorage, checkWater()).WillOnce(testing::Return(1));
    EXPECT_CALL(mockedCoffeeStorage, checkBeans()).WillOnce(testing::Return(1));
    EXPECT_CALL(mockedCoffeeStorage, takeCoffeeStorage()); // this is more of the 'assert' part, but it cannot be moved to the end of the test

    // act
    coffeeTest.takeCoffee();

    // assert
    ASSERT_TRUE(coffeeTest.isCoffeeServed());
}

这是您的代码的精简版,但它应该能让您 运行。始终尝试从一些 v. 简单、最小的测试开始,并从中增加更多的复杂性。如果测试变得太 complex/complicated,可能是时候将代码的某些部分分离到新的 class 并在分离中测试此 class。