Google Mock - Originale Methode wird aufgerufen, trotz vorhandender Mock Methode



  • Hallo liebe Community,

    Ich schreibe gerade eine Layer Klasse basierend auf Funktionalitäten der socket.h Bibliothek. Weil ich beim Testen meiner eigenen Layer Klasse jedoch nicht die Funktionen jener Header mit testen möchte und das auch die Testausführung verlangsamen würde, beschäftige ich mich gerade mit Google Mock. In meiner besagten Layer Klasse wird die connect Funktion aus der socket.h abstrahiert:

    Socket.hpp

    // ...
    
    class LS_STD_DYNAMIC_GOAL Socket : public ls::std::core::Class
    {
      public:
    
        explicit Socket(const ls::std::network::SocketParameter& _parameter);
        ~Socket() override = default;
    
        [[nodiscard]] bool connect();
        // ...
    
      private:
    
        #if defined(unix) || defined(__APPLE__)
        ls::std::network::PosixSocket posixSocket{};
        #endif
    
        // ...
    };
    

    Socket.cpp

    // ...
    
    bool ls::std::network::Socket::connect()
    {
      #if defined(unix) || defined(__APPLE__)
      return ls::std::network::Socket::_connectUnix();
      #endif
    }
    
    // ...
    
    #if defined(unix) || defined(__APPLE__)
    bool ls::std::network::Socket::_connectUnix()
    {
      ls::std::network::ConvertedSocketAddress convertedSocketAddress = ls::std::network::SocketAddressMapper::from(ls::std::network::Socket::_createSocketAddressMapperParameter());
      return this->posixSocket.connect(this->unixDescriptor, reinterpret_cast<const sockaddr *>(&convertedSocketAddress.socketAddressUnix), sizeof(convertedSocketAddress.socketAddressUnix)) == 0;
    }
    #endif
    
    // ...
    

    PosixSocket.hpp

    // ...
    
    class PosixSocket : public ls::std::core::interface_type::IPosixSocket
    {
      public:
    
        PosixSocket() = default;
        ~PosixSocket() override = default;
    
        int connect(int _socketFileDescriptor, const struct sockaddr *_address, socklen_t _addressLength) override;
    };
    
    // ...
    

    PosixSocket.cpp

    // ...
    
    int ls::std::network::PosixSocket::connect(int _socketFileDescriptor, const struct sockaddr *_address, socklen_t _addressLength)
    {
      return ::connect(_socketFileDescriptor, _address, _addressLength);
    }
    
    // ...
    

    IPosixSocket.hpp

    // ...
    
    class IPosixSocket
    {
      public:
    
        virtual ~IPosixSocket() = default;
    
        virtual int connect(int _socketFileDescriptor, const struct sockaddr* _address, socklen_t _addressLength) = 0;
    // ...
    };
    
    // ...
    

    Eine Mock-Klasse für PosixSocket existiert ebenso,
    MockPosixSocket.hpp

    // ...
    
    class MockPosixSocket : public ls::std::core::interface_type::IPosixSocket
    {
      public:
    
        MockPosixSocket() = default;
        ~MockPosixSocket() override = default;
    
        MOCK_METHOD(int, connect, (int _socketFileDescriptor, const struct sockaddr *_address, socklen_t _addressLength), (override));
    
    // ...
    };
    

    Der Test dazu sieht wie folgt aus:

    TEST_F(SocketTest, connect)
    {
      #if defined(unix) || defined(__APPLE__)
      MockPosixSocket mockPosixSocket{};
      EXPECT_CALL(mockPosixSocket, connect(_, _, _)).Times(1);
      ON_CALL(mockPosixSocket, connect(_, _, _)).WillByDefault(Return(true));
      #endif
    
      Socket socket{generateSocketParameter()};
      ASSERT_TRUE(socket.connect());
    }
    

    Beim Ausführen des Tests wird folgende Meldung ausgegeben:

    Actual function call count doesn't match EXPECT_CALL(mockPosixSocket, connect(_, _, _))...
             Expected: to be called once
               Actual: never called - unsatisfied and active
    

    Beim Debuggen habe ich gemerkt, dass trotz Mocks die connect Methode von PosixSocket aufgerufen wird, und nicht die der MockPosixSocket Klasse. Das soll wohl aber laut Recherche nur passieren, wenn eine zu mockende Methode nicht virtuell ist, was hier aber der Fall ist.

    Hat hier zufällig Jemand Erfahrungen mit Google Mock und eine Idee?

    Ich bin auf einem Linux Mint 20.3 mit GCC 12.2. Der source code ist öffentlich zugänglich:

    Und das ist der branch, auf dem ich arbeite: socket

    Vielen Dank schon mal für die Hilfe!



  • @PadMad Naja, du musst deiner Socket Klasse auch sagen, dass sie auf dem Mock arbeiten soll.

    Üblicherweise definiert man sich dafür ein Interface und gibt dem Konstruktor die Möglichkeit die Abhängigkeit übergeben zu bekommen. Dann kann man da den Mock rein schieben und die Klasse ruft die Methode des Mocks auf.



  • Ja, genau.
    In der Socket-Klasse müsstest du dann

    ls::std::network::IPosixSocket &posixSocket; // oder als Zeiger - aber dann müssen alle Aufrufe verändert werden
    

    verwenden und per "constructor injection" diesen als Parameter von außen setzen (d.h. also den Library-Source verändern).



  • Guten Abend,

    Vielen Dank für die Antworten. So recht will das aber nicht funktionieren. Ich habe das interface nun der Socket Klasse als member bereit gestellt:

    ::std::shared_ptr<ls::std::core::interface_type::IPosixSocket> posixSocket{};
    

    Im Konstruktor dieser Klasse wird nun geprüft, ob die API ein mock sein soll oder nicht und dementsprechend gesetzt:

    ls::std::network::Socket::Socket(const ls::std::network::SocketParameter& _parameter) : ls::std::core::Class("Socket"),
    parameter(_parameter)
    {
      #if defined(unix) || defined(__APPLE__)
      this->unixDescriptor = ls::std::network::Socket::_initUnix(_parameter);
      // ...
      #endif
    }
    
    // ...
    
    bool ls::std::network::Socket::_initUnix(const ls::std::network::SocketParameter &_parameter)
    {
      this->_selectUnixSocketApi(_parameter);
      // ...
    }
    
    void ls::std::network::Socket::_selectUnixSocketApi(const ls::std::network::SocketParameter &_parameter)
    {
      if (_parameter.mockSocketApi)
      {
        this->posixSocket = ::std::make_shared<ls_std_network_test::MockPosixSocket>();
      }
      else
      {
        this->posixSocket = ::std::make_shared<ls::std::network::PosixSocket>();
      }
    }
    

    Der Test scheint nun auch nicht mehr auf die PosixSocket Klasse gehen zu wollen, sondern auf die MockPosixSocket Klasse. Dennoch bekomme ich bei der Ausführung des Testes folgende Ausgaben:

    GMOCK WARNING:
    Uninteresting mock function call - returning default value.
        Function call: create(2, 1, 0)
              Returns: 0
    NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md#knowing-when-to-expect for details.
    
    GMOCK WARNING:
    Uninteresting mock function call - returning default value.
        Function call: connect(1, 0x7ffdae8954b0, 16)
              Returns: 0
    NOTE: You can safely ignore the above warning unless this call should not happen.  Do not suppress it by blindly adding an EXPECT_CALL() if you don't mean to enforce the call.  See https://github.com/google/googletest/blob/master/docs/gmock_cook_book.md#knowing-when-to-expect for details.
    ...
    Actual function call count doesn't match EXPECT_CALL(mockPosixSocket, create(_, _, _))...
             Expected: to be called at least once
               Actual: never called - unsatisfied and active
    ...
    Actual function call count doesn't match EXPECT_CALL(mockPosixSocket, connect(_, _, _))...
             Expected: to be called at least once
               Actual: never called - unsatisfied and active
    

    Und der Test sieht nun wie folgt aus:

      TEST_F(SocketTest, connect)
      {
        #if defined(unix) || defined(__APPLE__)
        MockPosixSocket mockPosixSocket{};
        EXPECT_CALL(mockPosixSocket, create(_, _, _)).Times(AtLeast(1));
        ON_CALL(mockPosixSocket, create(_, _, _)).WillByDefault(Return(0));
        EXPECT_CALL(mockPosixSocket, connect(_, _, _)).Times(AtLeast(1));
        ON_CALL(mockPosixSocket, connect(_, _, _)).WillByDefault(Return(0));
        #endif
    
        SocketParameter parameter = generateSocketParameter();
        parameter.mockSocketApi = true;
        Socket socket{parameter};
    
        ASSERT_TRUE(socket.connect());
      }
    

    Neu sind nun die Warnungen von gmock. Die hatte ich vorher nicht. Und wieder habe ich keine Idee.
    (Der Code befindet sich wieder im repository - die Links sind die gleichen wie im originalen Post.)



  • Welche IDE benutzt du denn? Kannst du das Programm nicht debuggen?
    Zur Not gdb per Terminal bedienen.

    Ich könnte dir jetzt die Lösung direkt verraten, aber ich möchte, daß du es selber herausfindest.

    Tipps:

    • Warum hast du nicht meinen Vorschlag bzgl. "constructor injection" umgesetzt?
    • Wie oft wird bei dir der MockPosixSocket-Konstruktor aufgerufen?


  • Eventuell ist PadMad nach diesem Beispiel gegangen.
    https://google.github.io/googletest/gmock_for_dummies.html
    Nur hat wohl PadMad einen entscheidenden Punkt übersehen wie dort die Mock Klasse genutzt wird.

    @PadMad Th69 hat dir dafür schon alle Hinweise gegeben wie man in deinem Falle die Mock-Klasse für den Test bzw. Dann die eigentliche Implementierung in der Anwendung nutzt.



  • Guten Morgen,

    Es scheint nun zu funktionieren. Da ich vorher eh schon ein Parameter Model an den Konstruktor übergeben hatte, war das für das Design weniger schlimm als gedacht dort auch den Mock zu übergeben - es hat den Code von außen nicht verändert, sondern nur im Testfall ergänzt. Nicht ganz die perfekte injection, aber zufriedenstellend.

    SocketTest.cpp

    // ...
    
    SocketParameter parameter = generateSocketParameter();
    
    shared_ptr<MockPosixSocket> mockSocket = make_shared<MockPosixSocket>();
    parameter.posixSocket = mockSocket; // here
    
    EXPECT_CALL(*mockSocket, create(_, _, _)).Times(AtLeast(1));
    ON_CALL(*mockSocket, create(_, _, _)).WillByDefault(Return(0));
    
    // ...
    
    Socket socket{parameter};
    ASSERT_TRUE(socket.connect());
    

    Beim Testen geht er nun auch auf die Mock Klasse. Vielen Dank auf jeden Fall für die Anregungen!


Anmelden zum Antworten