Created by Donald Whyte
                                / @donald_whyte
                            
                            
                            
                                Credit to Denis Cheklov for
                                his contributions
                            
                        
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:*");
                        
// 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(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());
}
                        
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_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());
}
                        
.
..
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();
}
                        
./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.
                        
./finprogtests --gtest_repeat=100
                        
./finprogtests --gtest_filter=StockRepositoryTests.*
                         
                        
                     
                        
                     
                        
                     
                        
                    
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;
};
                        
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;
}
                        
... generator; // Construct a particular generator
// Create a game
CoinFlipper game(&generator);
// Start playing
CoinFlipper::Result flip = game.flip();
                        flip is either
                        HEADS or TAILS
                     
                        RngCoinFlipper
                                produces both results
                                Rng
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));
};
                        
// 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
                        
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
                        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);
                        
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(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
}
                        
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 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"));
                        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 | 
Google Mock provides lots of matchers out-of-the-box
| Matcher | Matches | 
|---|---|
| Contains(elem) | containers that contain elemat 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()) | 
// 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));
                        
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 specify what a mock method does when called
EXPECT_CALL(writer, writeVec(_))
    .Times(Exactly(1)).WillOnce(
        Return(1) // action
    );
                        | 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 fwith
                                    arguments passed to mocked method | 
| DoAll(a1, a2, ..., aN) | Perform multiple actions in sequence | 
// 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
    ));