<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【C++】GoogleTest進階之gMock

      gMock是什么

      當我們去寫測試時,有些測試對象很單純簡單,例如一個函數完全不依賴于其他的對象,那么就只需要驗證其輸入輸出是否符合預期即可。

      但是如果測試對象很復雜或者依賴于其他的對象呢?例如一個函數中需要訪問數據庫或者消息隊列,那么要想按照之前的思路去測試就必須創(chuàng)建好數據庫和消息隊列的客戶端實例,然后放在該函數內使用。很多時候這種操作是很麻煩的,此時Mock Object就能幫助我們解決這個問題。一個Mock Object實現與真實對象相同的接口,它可以替代真實對象去使用,而我們要做的就是制定好該Mock Object的行為(調用多少次、參數、返回值等等)

      參考文檔:
      gMock官方文檔

      安裝gMock

      gMock現在與gTest是組合使用的關系,因此在安裝gTest時默認就會安裝gMock,具體的安裝方式見github上的官方說明
      https://github.com/google/googletest/tree/main/googletest

      使用gMock的基本思路

      • 首先,使用一些簡單的gMock宏來描述想要模擬的接口,它們會實現你的mock類
      • 然后,創(chuàng)建一些mock object然后使用gMock提供的語法指定好它們的行為
      • 最后,運行需要使用這些mock object的代碼,gMock會在mock object的行為不符合預期的時候發(fā)現并指出

      gMock快速入門

      假設我們在做一個用戶的賬戶系統,一個用戶會有一個賬戶,用戶提供接口salary,賬戶提供接口add和getAccount,在用戶的salary內會調用賬戶的add和getAccount接口
      特別注意:此處的賬戶就是我們要mock的對象,它是用戶的一個依賴。要想模擬它,它內部必須有虛析構函數,各個接口也建議是虛函數乃至純虛函數。這里我的理解是,實際上mock object是對真實對象的代理/替換,在代理模式中比較常見的一種做法就是代理類和被代理類繼承自同一個父類/接口

      基本樣例

      User

      #ifndef USER_H
      #define USER_H
      #include <iostream>
      #include "account.h"
      
      class User{
      public:
          /// @brief User類的對象依賴于Account的對象
          /// @param account Account實例,被User所依賴 
          User(Account *account){
              account_ = account;
          }
          /// @brief 模擬發(fā)工資的場景
          /// @param money 發(fā)的錢數
          /// @return 賬戶余額
          int salary(int money){
              account_->add(money);
              return account_->getAccount();
          }
      
      private:
          Account *account_;
      };
      
      #endif //USER_H
      

      Account

      #ifndef ACCOUNT_H
      #define ACCOUNT_H
      
      class Account
      {
      public:
          virtual ~Account() {}
          virtual void add(int money) = 0;
          virtual int getAccount() = 0;
      };
      
      #endif //ACCOUNT_H
      

      mock類編寫

      我們要mock的是Account的一個對象,所以書寫mock類實現Account接口

      #ifndef MOCK_ACCOUNT_H
      #define MOCK_ACCOUNT_H
      #include "account.h"
      #include <gmock/gmock.h>
      
      class MockAccount : public Account
      {
      public:
          MOCK_METHOD(void, add, (int money), (override));
          MOCK_METHOD(int, getAccount, (), (override));
      };
      
      #endif // MOCK_ACCOUNT_H
      

      其中的關鍵部分在于MOCK_METHOD,很多老的教程中會使用MOCK_METHOD0、MOCK_METHOD1...這些宏,它們分別代表0參數、1參數、2參數的接口。在新的官方教程中沒有這種寫法,統一都是MOCK_METHOD,內部有四個參數

      • 接口返回值類型
      • 接口名
      • 接口形參列表,注意,如果有泛型,需要多加一層括號,例如MOCK_METHOD(void, funName, (int, (map<int,string>)),(override))
      • 為生成的mock object的方法添加關鍵字(如果是override這個參數其實可以不寫,但是如果接口是const的,就必須寫const關鍵字了)

      mock類放在哪

      按照google的建議,除非整個接口就是你自己持有的,否則mock類不要放在xx_test下,因為一旦Account接口被它的所有者改變,MockAccount也必須改變才能繼續(xù)使用
      一般來說,我們不應該mock不是自己持有的接口。如果真的需要mock不是自己持有的,mock對象的目錄或者testing的子目錄下創(chuàng)建一個.h文件和一個 cc_library with testonly=true,這樣一來,每個人都可以使用同一個地方定義的mock類

      mock的使用

      創(chuàng)建好mock類之后,要使用它一般分以下幾步

      • 創(chuàng)建Mock Object
      • 規(guī)定Mock Object的預期行為
      • 使用Mock Object測試業(yè)務代碼,業(yè)務代碼部分可以使用gTest的各種斷言
      • 一旦Mock Object的方法被調用的情況與前面規(guī)定的預期行為不符,測試就會不通過(在Mock Object被析構時也會再次檢查)
        其中比較核心代碼有兩部分:規(guī)定Mock Object的預期行為和業(yè)務代碼測試,前者將會在下面詳細展開,后者可以參考Google Test那篇文章
        google test入門指南

      樣例

      user_test.cc文件

      #include <gtest/gtest.h>
      #include <gmock/gmock.h>
      #include "user.h"
      #include "mock_account.h"
      
      using ::testing::AtLeast;
      using ::testing::Return;
      
      TEST(UserTest, SalaryIsOK)
      {
          MockAccount mAccount;//創(chuàng)建Mock Object
          EXPECT_CALL(mAccount, add(100)).Times(AtLeast(1));
          EXPECT_CALL(mAccount, getAccount()).Times(AtLeast(1));//規(guī)范Mock Object的行為,此處是說該mock對象的getAccount()方法至少被調用1次
          User user(&mAccount);//將Mock Obejct注入到user中使用(依賴注入)
          int res = user.salary(100);//測試User業(yè)務邏輯
          ASSERT_GE(res, 0);//gTest的斷言,res大于等于0則通過
      }
      

      編譯運行

      這里我使用CMake來做構建,注意gTest和gMock需要C++14及以上,在鏈接時直接鏈接gtest_main,這樣就不需要自己寫main方法了

      CMakeLists.txt

      cmake_minimum_required(VERSION 3.14)
      
      project(user LANGUAGES C CXX)
      
      set(CMAKE_CXX_STANDARD 14)
      
      enable_testing()
      
      find_package(GTest REQUIRED)
      
      add_executable(test_user "${PROJECT_SOURCE_DIR}/user_test.cc")
      target_link_libraries(test_user GTest::gtest_main gmock)
      
      include(GoogleTest)
      gtest_discover_tests(test_user)
      

      運行結果

      [==========] Running 1 test from 1 test suite.
      [----------] Global test environment set-up.
      [----------] 1 test from UserTest
      [ RUN      ] UserTest.SalaryIsOK
      [       OK ] UserTest.SalaryIsOK (0 ms)
      [----------] 1 test from UserTest (0 ms total)
      
      [----------] Global test environment tear-down
      [==========] 1 test from 1 test suite ran. (0 ms total)
      [  PASSED  ] 1 test.
      

      測試通過了

      設置預期行為

      使用Mock最核心的點就在于給一個Mock Object規(guī)定好預期行為。這部分也是我們需要斟酌的地方。預期行為是設置的嚴格一點還是松一點全看需求。

      一般語法

      在gMock中使用EXPECT_CALL()這個斷言宏去設置一個Mock Object的預期行為
      EXPECT_CALL(mock_object, mock_method(params))...
      其中有兩個核心參數,第一個是mock_object,第二個是mock_object中的方法,如果有參數同時要把參數傳進去,注意,不同參數的mock_method可以認為是不同的預期行為
      ...部分可以填寫很多鏈式調用的邏輯來指定該對象該方法的調用運行情況

      using ::testing::Return;
      ...
      EXPECT_CALL(mock_object, mock_method(params)).Times(5).WillOnce(Return(100)).WillOnce(Return(150)).WillRepeatedly(Return(200));
      

      在以上的栗子中,為該對象的該方法指定了四個預期行為:
      首先它會被調用5次,第一次返回100,第二次返回150,之后的每次都返回200

      關于方法的參數params

      不確定參數值

      很多時候我們不想讓參數值變得固定,這個時候可以使用::testing::_來表示任意參數值

      using ::testing::_;
      ...
      EXPECT_CALL(mock_object, mock_method(_))...
      

      如果參數有多個,而且全部都是不確定參數值,我們可以這樣寫:
      EXPECT_CALL(mock_object, mock_method)...

      參數值需要滿足某種條件

      對于傳入確切參數的情況,相當于是使用Eq(100),以下的前兩個寫法是等價的

      EXPECT_CALL(mock_object, mock_method(100))...
      EXPECT_CALL(mock_object, mock_method(Eq(100)))...
      EXPECT_CALL(mock_object, mock_method(Ge(50)))...//參數大于等于50的所有情況
      

      那么除了Eq之外,gMock還提供了其他的一些,可以自行探索

      預期調用的次數

      在預期行為部分,我們可以手動寫上Times(3)來指定它需要被調用3次,多或少都會導致測試不通過。
      AtLeast()是在次數預期里比較常用的一個方法,如果是Times(3),那方法必須調用且只能調用3次,但是如果是Times(AtLeast(3)),那么就是至少調用3次的意思了。
      我們也可以省略Times(),此時gMock會默認根據我們寫的鏈式調用情況添加Times(),具體規(guī)則見下面的部分。

      關于次數的預期,核心的方法有兩個,分別是WillOnce()和WillRepeatedly(),前者表示調用一次,后者表示重復調用,它們可以組合使用,使用的具體規(guī)則如下:

      • 如果沒有WillOnce和WillRepeatedly(),則默認添加Times(1)
      • 如果有n個WillOnce,沒有WillRepeatedly(),則默認添加Times(n)
      • 如果有n個WillOnce,有一個WillRepeatedly(),則默認添加Times(AtLeast(n)),這意味著WillRepeatedly可以匹配調用0次的情況

      預期發(fā)生的行為

      一個mock object的所有方法中都沒有具體的實現體,那么它的返回值情況是怎么樣設定預期的呢?
      默認情況下我們如果不設定返回值預期,也會有默認的返回值(只是我們不使用而已),bool會返回false,int等等的會返回0.
      如果需要它有指定的預期返回值,我們可以在次數預期中加入返回值預期

      using ::testing::Return;
      ...
      EXPECT_CALL(mock_object, mock_method(params))
      .Times(5)
      .WillOnce(Return(100))
      .WillOnce(Return(150))
      .WillRepeatedly(Return(200));
      

      在以上的栗子中,為該對象的該方法指定了四個預期行為:
      首先它會被調用5次,第一次返回100,第二次返回150,之后的每次都返回200
      如果去掉Times(5),那就是第一次返回100,第二次返回150,之后每次都返回200,調用次數不少于2次(WillRepeatedly可以調用0次)

      預期發(fā)生順序

      默認情況下,我們設定好一個mock對象的多個預期行為時,是不關心它們的發(fā)生順序的。例如以下代碼中,先調用PenDown()或者先調用了Forward(100)都是無所謂的,都能通過測試:

      EXPECT_CALL(turtle, PenDown());
      EXPECT_CALL(turtle, Forward(100));
      

      那么如果我們想指定預期發(fā)生順序,我們需要創(chuàng)建InSequence對象,該對象創(chuàng)建處的代碼塊(scope)內的所有預期行為都必須按照聲明順序發(fā)生。

      using ::testing::InSequence;
      ...
      TEST(FooTest, DrawsLineSegment) {
        ...
        {
          InSequence seq;
      
          EXPECT_CALL(turtle, PenDown());
          EXPECT_CALL(turtle, Forward(100));
          EXPECT_CALL(turtle, PenUp());
        }
        Foo();
      }
      

      一些需要注意的點

      預期行為的一次性寫入

      EXPECT_CALL()的鏈式調用中所有預期都會一次性寫入,這意味著不要在鏈式調用中寫運算,可能不會滿足預期需求。舉個栗子,以下并不能匹配返回100,101,102...而是只匹配返回100的情況,因為++是在預期行為被設定好之后才發(fā)生

      using ::testing::Return;
      ...
      int n = 100;
      EXPECT_CALL(turtle, GetX())
          .Times(4)
          .WillRepeatedly(Return(n++));
      

      mock對象方法的預期行為多重定義

      在前面,我們看到的都是單對象單方法僅有1種預期行為定義的情況,如果定義了多個呢?例如:

      using ::testing::_;
      ...
      EXPECT_CALL(turtle, Forward(_));  // #1
      EXPECT_CALL(turtle, Forward(10))  // #2
          .Times(2);
      

      假如我們在后面調用了三次Forwar(10),那么測試會報錯不通過。如果調用了兩次Forward(10),一次Forward(20),那么測試會通過。

      預期行為粘連問題

      gMock中的預期行為默認是粘連的,它們會一直保持存活狀態(tài)(哪怕它所規(guī)定的預期行為已經完全被匹配過了)
      例如以下的情況可能會出錯,這種寫法下可能最初想的是返回50、40、30、20、10的調用各一次,但是發(fā)生調用時就報錯了(例如第一次調用返回10,而第二次調用返回20時,預期返回10的那個也還存活著會報錯(不滿足Once了))

      using ::testing::Return;
      ...
      for (int i = 5; i > 0; i--) {
        EXPECT_CALL(turtle, GetX())
            .WillOnce(Return(10*i));
      }
      

      我的理解:所謂預期行為(Expectations),它所針對的是一個Mock對象的一個方法在某一種參數情況下的行為,如果不顯式的聲明讓它在被滿足后退休,它會一直存活,一直干活...
      要想解決上面的問題,可以顯式的聲明飽和退休

      using ::testing::Return;
      ...
      for (int i = n; i > 0; i--) {
        EXPECT_CALL(turtle, GetX())
            .WillOnce(Return(10*i))
            .RetiresOnSaturation();
      }
      

      在以上這種寫法下,每個.WillOnce()一旦被滿足就會退休,后面發(fā)生了什么它不會去管了,也就不會報錯了
      當然這也可以結合前面的預期發(fā)生順序來寫,以下的寫法意味著第一次調用返回10,第二次返回20.....

      using ::testing::InSequence;
      using ::testing::Return;
      ...
      {
        InSequence s;
      
        for (int i = 1; i <= n; i++) {
          EXPECT_CALL(turtle, GetX())
              .WillOnce(Return(10*i))
              .RetiresOnSaturation();
        }
      }
      

      后續(xù)可填坑

      gMock進階指南

      posted @ 2022-10-18 21:39  縉云燒餅  閱讀(7507)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 福利一区二区在线观看| 亚洲av日韩av综合在线观看| 久久久久无码国产精品一区| 丰满人妻熟妇乱精品视频| 亚洲精品在线二区三区| 精品国产一区av天美传媒| 亚洲av色图一区二区三区| 亚洲一区二区三区丝袜| 国产一级区二级区三级区| 野外做受三级视频| 亚洲欧洲精品一区二区| 精品国产成人亚洲午夜福利 | 99网友自拍视频在线| 99久久久国产精品免费蜜臀| 久久久久无码精品国产h动漫| 国产精品美人久久久久久AV| 漂亮的保姆hd完整版免费韩国| 在线 欧美 中文 亚洲 精品| 荣昌县| 久久久精品2019中文字幕之3| 亚洲色最新高清AV网站| 少妇高潮流白浆在线观看| 国産精品久久久久久久| 亚洲精品欧美综合二区| 久久精品国产99国产精品亚洲| 成年女人喷潮免费视频| 亚洲一区二区精品极品| 国产中文字幕精品喷潮| 免费午夜无码片在线观看影院| 坐盗市亚洲综合一二三区| 日韩人妻无码精品无码中文字幕 | 精品免费国产一区二区三区四区介绍 | 国产片AV国语在线观看手机版| 成人午夜无人区一区二区| 亚洲韩欧美第25集完整版| 国产成人99亚洲综合精品| 国产精品久久福利新婚之夜| 久久精品一本到99热免费| 亚洲精品久久麻豆蜜桃| 国产av永久无码天堂影院| 元码人妻精品一区二区三区9|