Introduction to Google Mock – C++ Testing and Mocking Framework – Why Mock?



Introduction to Google Mock – C++ Testing and Mocking Framework – Why Mock?

0 1


gmock-presentation

Presentation on Google Mock

On Github DonaldWhyte / gmock-presentation

Introduction to Google Mock

C++ Testing and Mocking Framework

Created by Donald Whyte / @donald_whyte Credit to Denis Cheklov for his contributions

Outline

  • Google Test
  • Why Mock?
  • Google Mock
  • Real Life Example
  • When to Mock

Google Test

Google Test

Cross-platform C++ testing framework by Google

Commonly used in conjunction with Google Mock

Features

  • Rich set of assertions
  • Value-parametrised tests
  • Type-parametrised tests
  • Death tests
  • Test discovery
  • XML test report generation
  • Multi-threaded tests

Assertion Macros

ASSERT_EQ(5, 5);
ASSERT_GE(5, 0);
ASSERT_FLOAT_EQ(4.4, 4.4444444287381217);
ASSERT_TRUE(somePtr);
ASSERT_EQ(c1.size(), c2.size())
    << "Vectors c1 and c2 are of unequal length";

ASSERT_THROW(c1.at(47374), std::out_of_range);

ASSERT_DEATH(c1[47374], "Assertion failed:*");

EXPECT_* and ASSERT_*

// assertions abort test if they fail
ASSERT_TRUE(somePtr);
// even if expectations fail, the test continues
// allows multiple failures to be recorded in one test pass
EXPECT_EQ("donald", somePtr->name());
EXPECT_EQ(105, somePtr->age());

Test

TEST(CaseName, TestName) {

}
TEST(StockRepository, ShouldSuccessfullyAddNewStock) {
    StockRepository repo;

    // Ensure stock can't be retrieved initially
    Stock* nonExistentStock = repo.stock("IBM");
    EXPECT_FALSE(nonExistentStock);
    // Add stock
    const int rc = repo.addStock("IBM", 4.74);
    EXPECT_EQ(0, rc);
    // Ensure it can be retrieved
    Stock* addedStock = repo.stock("IBM");
    ASSERT_TRUE(addedStock);
    EXPECT_EQ("IBM", addedStock->name());
    EXPECT_DOUBLE_EQ(4.74, addedStock->price());
}

Test Fixture

class StockRepositoryTests : public ::testing::Test {
  protected:
    StockRepository d_repo;

  public:
    StockRepositoryTests() { } // #1

    ~StockRepositoryTests() { } // #4

    void SetUp() { // #2
        // initialise test data, mocks and objects under test
        d_repo.addStock("AAPL", 50.5);
    }

    void TearDown() { // #3
        // cleanup used resources
    }
};

Test Fixture

TEST_F(StockRepositoryTests, TotalStockCountReflectsNumStocksInRepo) {
    EXPECT_EQ(1u, d_repo.numStocks());
}
TEST_F(StockRepositoryTests, ShouldFailToAddStockWithDuplicateName) {
    // Try to add stock that already exists
    const int rc = d_repo.addStock("AAPL", 242.53);
    EXPECT_NE(0, rc);
    // Ensure price was not overwritten
    Stock* unchangedStock = repo.stock("AAPL");
    ASSERT_TRUE(unchangedStock);
    EXPECT_EQ("AAPL", unchangedStock->name());
    EXPECT_DOUBLE_EQ(50.5, unchangedStock->price());
    // Ensure stock count was not changed
    EXPECT_EQ(1u, d_repo.numStocks());
}

Test Discovery

.
..
src/
tests/
    finprogtests.m.cpp
    Makefile
    finprog_stock.cpp
    finprog_stockrepository.cpp
    ...
// finprogtests.m.cpp
#include <gtest/gtest.h>

int main(int argc, char* argv[]) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Running Tests

./finprogtests
[==========] Running 4 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 3 tests from StockRepositoryTests
[ RUN      ] StockRepositoryTests.TotalStockCountReflectsNumStocksInRepo
[       OK ] StockRepositoryTests.TotalStockCountReflectsNumStocksInRepo (0 ms)
[ RUN      ] StockRepositoryTests.ShouldFailToAddStockWithDuplicateName
[       OK ] StockRepositoryTests.ShouldFailToAddStockWithDuplicateName (1 ms)
[----------] 2 tests from StockRepositoryTests (1 ms total)

[----------] 2 tests from StockTests
[ RUN      ] StockTests.InitialisedCorrectly
[       OK ] StockTests.InitialisedCorrectly (1 ms)
[ RUN      ] StockTests.PrintSuccessful
[       OK ] StockTests.PrintSuccessful (1 ms)
[----------] 2 tests from StockTests (2 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 2 test cases ran. (3 ms total)
[  PASSED  ] 4 tests.

Command Line Parameters

Repeat test suite multiple times
./finprogtests --gtest_repeat=100
Run specific tests (regex match)
./finprogtests --gtest_filter=StockRepositoryTests.*

Why Mock?

Here's a component hierarchy.
Suppose we want a test for the red component at the top there. The component has three dependencies, or collaborators, which we build and pass into component at construction. These collaborators might be rely on external systems or require a large amount of setup. This makes testing the component difficult, because we either have to ensure these external systems are available and in the right state, or write lots more test code to setup the collaborators. Since we aim to write tests for most of our components (or should), this extra effort builds up and results in huge amounts of development time taken up by tests. ...so then we end up just not writing tests. :P
To avoid this, we replace the implementations of these collaborators which much simpler, fake implementations.
No more environment dependencies, no more massive setup. It becomes much quicker and easier to write the tests.

What to Eliminate

Anything non-deterministic that can't be reliably controlled within the unit test

  • External data sources (e.g. files, databases)
  • Network connections (e.g. services)
  • External code dependencies (libraries)
  • Internal code dependencies
    • simpler test code
    • makes individual tests less brittle
    • some downsides to this

Solution: Use Test Double

  • A test double is an object or function substituted for a "real" (production ready) object during testing.
  • Should appear exactly the same as a "real" production instance to its clients (collaborators).
  • Term originates from a notion of a "stunt double" in films

Types

  • Stubs return hard-coded values
  • Spies record the code's interaction with collaborators
    • times method called and passed arguments
  • Mocks return hard-coded values and verify interaction
    • cross between a stub and a spy
Mocks are the focus of this talk WHY? * most flexible * but also because this talk is about Google Mocks, which is a testing framework whose core focus is mocking (so why would be talking about something else :P)

Google Mock

Coin Flipper

  • A simple game to flip a coin
  • CoinFlipper class implements the game
  • It interacts with a random number generator
  • We can change a number generator at runtime
  • Goal is to test CoinFlipper

Interfaces

class Rng {
  public:
    virtual ~Rng();
    virtual double generate(double min, double max) = 0;
};
class CoinFlipper {
  private:
    Rng* d_rng; // held, not owned

  public:
    enum Result {
        HEADS = 0,
        TAILS = 1
    };

    explicit CoinFlipper(Rng* rng);
    Result flipCoin() const;
};

Implementation

CoinFlipper::CoinFlipper(Rng* rng) : d_rng(rng)
{
    BSLS_ASSERT(d_rng);
}

CoinFlipper::Result CoinFlipper::flipCoin() const
{
    const double val = d_rng->generate(0.0, 1.0);
    BSLS_ASSERT(0.0 <= val && val <= 1.0);

    return (val < 0.5) ? HEADS : TAILS;
}

Playing the Game

... generator; // Construct a particular generator

// Create a game
CoinFlipper game(&generator);

// Start playing
CoinFlipper::Result flip = game.flip();
flip is either HEADS or TAILS
  • One collaborator -- Rng
  • Real RNG is unpredictable
  • We want to test CoinFlipper produces both results
    • we also want these tests to be repeatable
    • without relying on an external environment
  • Have to mock Rng

Google Mock to the Rescue!

  • create mock classes using simple macros
  • rich set of matchers and actions
  • easily extensible by users to provide even richer features

Defining Mock Collaborators

MOCK_METHOD[n](methodName, returnType(arg1Type, ..., argNType));
MOCK_CONST_METHOD[n](methodName, returnType(arg1Type, ..., argNType));
class MockRng : public Rng {
  public:
    MOCK_METHOD2(generate, double(double, double));
};

Defining Mock Collaborators

  • ~Rng() must be virtual
  • If generate() is defined in a superclass, then it must be declared virtual in that superclass
  • In other words, you can't mock non-virtual methods

Setting Expectations

// test fails in `generate()` is not called exactly once with
// `min = 0.0` and `max = 1.0`
MockRng rng;
EXPECT_CALL(rng, generate(DoubleEq(0.0), DoubleEq(1.0))
    .Times(Exactly(1))
    .WillOnce(Return(0.25));
EXPECT_CALL(mockObject, method(arg1Matcher, ..., argNMatcher))
    .Times(cardinality)          // 0 or 1
    .InSequence(sequences)       // 0+
    .WillOnce(action)            // 0+
    .WillRepeatedly(action)      // 0 or 1

Full Expectation Format

EXPECT_CALL(mockObject, method(arg1Matcher, ..., argNMatcher))
    .With(multiArgumentMatcher)  // 0 or 1
    .Times(cardinality)          // 0 or 1
    .InSequence(sequences)       // 0+
    .After(expectations)         // 0+
    .WillOnce(action)            // 0+
    .WillRepeatedly(action)      // 0 or 1
    .RetiresOnSaturation();      // 0 or 1
                        

Default Actions

Specify default actions a mock should take, without setting strict expectations on whether they should be called

// Test passes regardless of whether `rng.generate()` is called or not.
// here. This just sets the default action (return `min` argument back
// to caller)
MockRng rng;
ON_CALL(rng, generate(_, _))
    .WillByDefault(ReturnArg<0>());
ON_CALL(mockObject, method(matchers))
    .With(multiArgumentMatcher)  // 0 or 1
    .WillByDefault(action);

ON_CALL or EXPECT_CALL?

  • ON_CALL defines stubs
  • EXPECT_CALL defines mocks
  • Tests using loads of mocks become tightly coupled with the component's implementation

Rule of Thumb

Use ON_CALL unless you actually want to test a method is called a specific number of times

Structuring Mock Tests

  • CoinFlipper generates a value in the range [0,1]
  • <0.5 means HEADS, >=0.5 means TAILS
  • Let's write a test to ensure HEADS is returned when the generated value is <0.5

Test Flow

TEST(TestFixture, TestName) {
    // 1) Create mock objects (collaborators)

    // 2) Specify your expectations of them

    // 3) Construct object(s) under test, passing mocks

    // 4) Run code under test

    // 5) Check output (using Google Test or some other framework)

    // 6) Let gmock automatically check mock expectations were met at
    //    end of test
}

Test Flow

TEST(CoinFlipper, ShouldReturnHeadsIfRandValueIsLessThanProbability) {
    // 1) Create mock objects (collaborators)
    MockRng rng;

    // 2) Specify your expectations of them
    EXPECT_CALL(rng, generate(DoubleEq(0.0), DoubleEq(1.0)))
        .Times(Exactly(1))
        .WillOnce(Return(0.25));

    // 3) Construct object(s) under test, passing mocks
    CoinFlipper coinFlipper(&rng);

    // 4) Run code under test
    CoinFlipper::Result result = coinFlipper.flipCoin();

    // 5) Check output (using Google Test or some other framework)
    EXPECT_EQ(CoinFlipper::HEADS, result);

    // 6) Let gmock automatically check mock expectations were met at end of test
}

Parametrised Tests

TEST_P(CoinFlipper, CoinFlip) {
    const double randomVal = GetParam().first;
    const CoinFlipper::Result expectedResult = GetParam().second;

    MockRng rng;
    EXPECT_CALL(rng, generate(DoubleEq(0.0), DoubleEq(1.0)))
        .Times(Exactly(1))
        .WillOnce(Return(randomVal));

    CoinFlipper coinFlipper(&rng);
    CoinFlipper::Result result = coinFlipper.flipCoin();

    EXPECT_EQ(expectedResult, result);
}

INSTANTIATE_TEST_CASE_P(ValidRandomNumberGenerated, CoinFlipper,
                        Values(bsl::make_pair(0.0, CoinFlipper::HEADS),
                               bsl::make_pair(0.25, CoinFlipper::HEADS),
                               bsl::make_pair(0.49999, CoinFlipper::HEADS),
                               bsl::make_pair(0.5, CoinFlipper::TAILS),
                               bsl::make_pair(0.75, CoinFlipper::TAILS),
                               bsl::make_pair(1.0, CoinFlipper::TAILS)));

Matchers

Matchers are functions used to match mock inputs to their expected values

// Matchers are used to set expectations on the values of mocked
// function arguments
EXPECT_CALL(printer, printVec(UnorderedElementsAre("foo", "bar")))
    .Times(Exactly(1));

// They can also be outside of call expectations, to match any kind
// of value
EXPECT_THAT(someVector, UnorderedElementsAre("foo", "bar"));
ASSERT_THAT("my name is Donald", HasSubstr("Donald"));

Common Matchers

Google Mock provides lots of matchers out-of-the-box

Matcher Matches _ matches anything Eq(value) values using operator==() DoubleEq(value) values using fuzzy 64-bit float equality IsNull() null raw/smart pointers StrCaseEq(string) string (case-insensitive) HasSubstr(string) strings with given substring

Common Matchers

Google Mock provides lots of matchers out-of-the-box

Matcher Matches Contains(elem) containers that contain elem at least once UnorderedElementsAre(e0, e1, ...) containers that contain specified elements, ignoring order Field(&class::field, matcher) objects with value for specified member variable (e.g. obj.d_age) Property(&class::property, matcher) objects with value for specified member function (e.g. obj.age())

Nesting Matchers

// writeVec() should be called once, with a 2-element a vector where:
//   * one element ~= 0.53
//   * one element that's not 1.0
EXPECT_CALL(writer, writeVec(UnorderedElementsAre(DoubleEq(0.53),
                                                  Not(DoubleEq(1.0)))))
    .Times(Exactly(1)).WillOnce(Return(0));

// writePerson() should be called once, with a person named Donald
// (case-insensitive) who is 102 years old
EXPECT_CALL(writer, savePersonToFile(
                       AllOf(
                             Property(&Person::name, StrCaseEq("donald")),
                             Property(&Person::age, 102)
                       ));
    .Times(Exactly(1)).WillOnce(Return(0));

Defining Matchers

MATCHER(IsEven, "") { return (arg % 2) == 0; }

MATCHER_P(IsDivisibleBy, n, "") {
    return (arg % n) == 0;
}
// all numbers in vector being written should be even
EXPECT_CALL(writer, writeVec(Each(IsEven))
    .Times(Exactly(1)).WillOnce(Return(0));

// all numbers in vector being written should be divisible by 3
EXPECT_CALL(writer, writeVec(Each(IsDivisibleBy(3))))
    .Times(Exactly(1)).WillOnce(Return(0));

Actions

Actions specify what a mock method does when called

EXPECT_CALL(writer, writeVec(_))
    .Times(Exactly(1)).WillOnce(
        Return(1) // action
    );

Common Actions

Action Return(value) Return specified value SetArgPointee<N>() Set value of Nth argument passed to mocked method (useful for out parameters) Throw(exception) Throw specified exception Invoke(f) Invoke function f with arguments passed to mocked method DoAll(a1, a2, ..., aN) Perform multiple actions in sequence

Defining Actions

// return statement in actions causes mock method to return this value
ACTION(Sum) {
    return arg0 + arg1;
}

// parametrised action
ACTION_P(AppendName, name) {
    arg0.push_back(name);
}
EXPECT_CALL(calculator, add(_, _))
    .Times(Exactly(1)).WillOnce(Sum());

EXPECT_CALL(customerDatabase, retrieveCustomerNames(_))
    .Times(Exactly(1)).WillOnce(DoAll(
        AppendName("Bob"),
        AppendName("Susie"),
        Return(0)             // retrieval was a success
    ));

Summary

Test Doubles

  • Replace collaborators with test doubles, which:
    • removes dependencies on non-deterministic behaviour
    • reduces setup code in tests
  • Makes tests easier to write and maintain
  • Use stubs if it's awkward to use the real thing
  • Use mocks when you want to test the communication between components

Google Test and Mock

  • Google Test is a C++ testing framework
  • Google Mock is a C++ framework for defining stubs/mocks
    • large set of matchers/actions
    • extensible
    • often used in conjunction with Google Test

When to Use Google Mock

  • Google Mock is not a panacea
  • Shouldn't be used to replace all collaborators
  • Awkward to use generated mocks in certain situations:
    • complex data structures
    • commonly used interfaces
  • Using hand-crafted test implementations is a better choice in some circumstances
  • Google Mock suitable for most cases

Useful Resources

Introduction to Google Mock C++ Testing and Mocking Framework Created by Donald Whyte / @donald_whyte Credit to Denis Cheklov for his contributions