# C++ TDD

Test-Driven Development in C++

There are several levels of testing which provides the layers of safety nets for catching any bugs that might be in the code.

  • The lowest level are unit tests. Unit tests validate individual functions in the production code. Unit tests are generally the most comprehensive tests, which it tests all positive and negative test cases or function.

  • Next comes component level testing, which tests the external interfaces for individual components. Components are essentially a collection of the functions.

  • Then comes system-level testing, which tests the external interfaces at a system level. Systems can be collections of components, or of subsystems.

  • And lastly comes performance testing, which tests systems and subsystems at expected production loads to verify that response times and resource utilization such as memory, CPU and disk usage are acceptable levels. Now let's look at some specifics on unit testing. Unit testing tests individual functions in the code.

Unit testing

Red Phase

Write a failing unit test.

Green Phase

Write just enough production code to make the failing unit test pass.

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Unit testing tests individual functions in the code.

  • Each test case for the function should have a corresponding unit test.

  • Groups of unit tests can be combined into test suites, which can help with organizing the tests.

  • A unit test should execute in your development environment rather than the production environment.

  • This is important to ensure you can run them easily and often.

  • And lastly, a unit test should be implemented in an automated fashion. You should be able to click a button and the unit test will build and execute and show you the results.

# Setting up CLion with google test cloned into the Project Folder

Set up GoogleTest for CLion

First copy google test GitHub to a folder called lib inside your project. Then adjust the Cmake accordingly:

cmake_minimum_required(VERSION 3.16)
project(FinalTest)

include_directories(C:/Users/Thiago Souto/Documents/CLionProjects/FinalTest/lib/pkgconfig)
find_library(FinalTest_lib FinalTest)

#group the libraries
set(frameworks ${FinalTest_lib})

set(CMAKE_CXX_STANDARD 14)
add_subdirectory(lib/googletest)
include_directories(lib/googletest/googletest/include)
include_directories(lib/googletest/googlemock/include)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
set(SOURCE_FILES main.cpp)

add_executable(FinalTest ${SOURCE_FILES})

target_link_libraries(FinalTest gtest gtest_main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


Then include the google test library on the main.cpp



 






#include <iostream>
#include <string>
#include <gtest/gtest.h>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
1
2
3
4
5
6
7
8

Making Google test available with `FetchContent`

cmake_minimum_required(VERSION 3.22)
project(C___helloWorld)

set(CMAKE_CXX_STANDARD 17)

include(FetchContent)

set(TEST_LIBRARY googletest)

FetchContent_Declare(
    ${TEST_LIBRARY}
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG        e2239ee6043f73722e7aa812a459f54a28552929 #release 1.11.0
)

FetchContent_MakeAvailable(${TEST_LIBRARY})

include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})

FetchContent_GetProperties(${TEST_LIBRARY})
if(NOT ${TEST_LIBRARY}_POPULATED)
    FetchContent_Populate(${TEST_LIBRARY})
    add_subdirectory(${${TEST_LIBRARY}_SOURCE_DIR} ${${TEST_LIBRARY}_BINARY_DIR})
endif()

set(SOURCE_FILES main.cpp)

add_executable(${PROJECT_NAME} ${SOURCE_FILES})

target_link_libraries(${PROJECT_NAME} gtest gtest_main)


# enable testing functionality
enable_testing()

# define tests
add_test(
        NAME ${PROJECT_NAME}
        COMMAND $<TARGET_FILE:${PROJECT_NAME}>
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# Setting up CLion with Google Test compiled into the system

So, first I'll create a new project and call it googletest_test. Then I'll compile and execute it. Now that I have the project set up, I'm going to modify the code for a simple Google Test assert unit test that should always pass. Then I'll try to compile and execute the code. This doesn't compile right now because the project doesn't know about the path to the Google Test headers.

#include <iostream>
#include <gtest/gtest.h>

TEST(GTTest, SimpleAssert){
    ASSERT_TRUE(true);
}
1
2
3
4
5
6

CLion uses CMake for its project files. I'll fix my compilers by adding the Google Test include files and library files test and gtest main to the project's Cmake list .txt file. Google Test in Linux also requires the p thread library.

So, first I'll add the path to the Google Test headers using the Include Directories command.

Include_directories(/home/rich/googletest/googletest/include/)

Then I'll add the path to the directory that has the Google Test library files using the Link Directories command.

link_directories(/home/rich/googletest/googletest/build/googlemock/gtest/)

Then I'll add the command to link the Google Test libraries and the p thread library.

target_link_libraries(googletest_test gtest gtest_main pthread)

And lastly, I'll try to compile everything. That compiles and executes.

Here is the Cmake file:

cmake_minimum_required(VERSION 3.16)
project(googletest_test)

Include_directories(/home/rich/googletest/googletest/include/)
set(CMAKE_CXX_STANDARD 14)

link_directories(/home/rich/googletest/googletest/build/googlemock/gtest/)
set(SOURCE_FILES main.cpp)
add_executable(googletest_test ${SOURCE_FILES})
target_link_libraries(googletest_test gtest gtest_main pthread)
1
2
3
4
5
6
7
8
9
10

But it's a simple text output. I want it to have the green bar like I had with the fizzbuzz example. CLion comes with a Google Test runner included. So, all I have to do is create a new run configuration that uses that test runner.

So, first I'll bring up the Configuration screen by clicking on Run and Edit Configurations. Then I'll add a new Google Test configuration and call it unit tests. Then I'll run that new configuration and see the green bar. And we're all set. I've got a C++ project in CLion using Google Test and a test runner showing me the results of my test.

# First test

#include <string>
#include <gtest/gtest.h>

//Production Code
int string_length(std::string theStr) {
    return theStr.length();
}

// A Unit test
TEST(StringLengthTests, SimpleTest) {
    std::string testStr = "1";
    int result = string_length("1");
    GTEST_ASSERT_EQ(1, result);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from StringLengthTests
[ RUN      ] StringLengthTests.SimpleTest
[       OK ] StringLengthTests.SimpleTest (0 ms)
[----------] 1 test from StringLengthTests (2 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (5 ms total)
[  PASSED  ] 1 test.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13

A unit test performs three steps. A setup step, where it creates the test string, an action step where it calls the production code to perform the action that is under test, an assertion step where the test validates the results of the action. This is a common structure that all of your unit tests should follow.



 
 
 


// A Unit test
TEST(StringLengthTests, SimpleTest) {
    std::string testStr = "1";        //Setup
    int result = string_length("1");  // Action
    GTEST_ASSERT_EQ(1, result);       //Assert
}
1
2
3
4
5
6

# Test Runner tab

To configure the test runner tab we have to add a template for the google test at the Edit configurations on the runner.





Summary of Unit testing

  • Unit tests are our first safety net for catching bugs in the production code.
  • Unit tests validate test cases for individual functions.
  • They should be built and run in the development environment.
  • And lastly, unit test should run fast.

We ideally want a developer rerunning the unit test every three to five minutes and this can be difficult with a slow build process, or if any of the test runs slow, i.e. for more than a few seconds.

# What is test-driven development?

  • A process for writing code that helps you take personal responsibility for the quality of your code.

  • The process drives this by having you write the unit tests before the production code. This can seem pretty strange at first. But after you've used the process for a while, it becomes the norm, and you'll find it hard to write code any other way.

  • Even though the tests are written before the production code, that doesn't mean that all the tests are written first. You write one unit test for one test case, and then you write the production code to make it pass.

  • The tests and the production code are written together, with small tests being written and then small bits of production code that make those tests pass.

This short cycle of writing a unit test and then writing the production code to make it pass provides immediate feedback on the code. This feedback is one of the essential features of TDD.

# Some Benefits of TDD

  • TDD gives you confidence to make changes in your code because you have a test before you begin that verifies the code is working and will tell if any of your changes have broken anything.

  • This confidence comes from the immediate feedback the test provides for each small change in the production code.

  • The tests document what the production code is supposed to do. A new developer looking at the code can use the unit test as a source for documentation for understanding what the production code is doing.

  • Writing the unit test first helps drive good object-oriented design, as making individual classes and functions testable in isolation drives the developer to break dependencies and add interfaces rather than making concrete implementations together directly.

# TDD Workflow: Red, Green, Refactor

  1. The first phase is the red phase. In the red phase, you write a failing unit test for the next bit of functionality you want to implement in the production code.

  2. Next comes the green phase, where you write just enough production code to make the failing unit test pass. Last is the refactor phase, where you clean up the unit test and the production.

  3. Last is the refactor phase, where you clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

  4. Then you repeat the process for all the functionality you need to implement and all the positive and negative test cases.

# The three laws of TDD

The Three Laws of TDD


  1. You are not allowed to write any production code unless it is to make a failing unit test pass.

  2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.

  3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.


  • Every behavior should be paired with a well-designed unit test

  1. You are not allowed to write any production code unless it is to make a failing unit test pass

which means you must first write a unit test and that unit test must fail before you can write any production code now right away this just sounds dumb I mean what are you supposed to test there so cold there how can you write a test for something if something doesn't exist so that's the first arbitrary thing in test-driven development and it gets gets many developers put off and again make any sense out of that but the worst is yet to come because the second law makes things much worse it says


  1. You are not allowed to write more of a unit test than is sufficient to make that is sufficient to fail

as soon as the unit test fails you have to stop writing it you can't write the whole test you have to stop writing it as soon as it fails and oh by the way failure to compile is failing so the instance the unit test doesn't compile properly you have to stop writing it and start writing production code now since there is no production code to begin with the unit test is probably not going to compile very quickly you're going to try two or three two or three words you may not even finish an entire line and you'll see a compiler error and you have to stop and start writing production code so this is much worse than just the first log and if you're a developer you think well how am I going to get anything done if I'm going to write a couple of characters or a test I'm gonna have to switch and write a couple of characters of production code but then the third block is send the worst of them all and it says


  1. You are not allowed to write any more production code then its sufficient to pass the currently failing test

now this is going to lock you into a cycle that is five seconds long you will have to write a unit test first because the first law says you have to but you're not going to be able to write much of it because it won't compile so then you'll have to write some production code but you can't read much of that because that'll make the unit

# Example: TDD session, the FizzBuzz kata

For the example, we'll be using the fizzBuzz code kata. The name kata comes from martial arts and means training exercises. So code kata are training exercises for programmers. The fizzBuzz Kata is pretty simple. We'll be implementing a function that is passed in an integer value as an input and the function will return the string fizz if the passed-in number is a multiple of three, Buzz if the passed-in number is a multiple of five, and fizzBuzz if the passed-in number is a multiple of three and five. If the value is not a multiple of three or five, then the value itself is returned as a string.


First Let's write a test and get the system ready.

I've created a Folder on the FinalTest project called FizzBuzz with a fizz.cpp file and a fizz.h for the header (the header fole was create by Clion, but I will try to work with It).

Then I wrote the following test:

#include "fizz.h"
#include <gtest/gtest.h>

TEST(FizzBuzzTest, doesPass){
    ASSERT_TRUE(true);
}
1
2
3
4
5
6

And I changed the Cmake to include the FizzBuzz directory include_directories(FizzBuzz) and changed the source code been evaluated set(SOURCE_FILES FizzBuzz/fizz.cpp).

This is the cmake file:

cmake_minimum_required(VERSION 3.16)
project(FinalTest)

include_directories(C:/Users/Thiago Souto/Documents/CLionProjects/FinalTest/lib/pkgconfig)
find_library(FinalTest_lib FinalTest)

#group the libraries
set(frameworks ${FinalTest_lib})

set(CMAKE_CXX_STANDARD 20)

# Directories
add_subdirectory(lib/googletest)
include_directories(FizzBuzz)
include_directories(lib/googletest/googletest/include)
include_directories(lib/googletest/googlemock/include)

# Source code in use
set(SOURCE_FILES FizzBuzz/fizz.cpp)

add_executable(FinalTest ${SOURCE_FILES})

target_link_libraries(FinalTest gtest gtest_main) 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

And this is the result of the test:

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FizzBuzzTest
[ RUN      ] FizzBuzzTest.doesPass
[       OK ] FizzBuzzTest.doesPass (0 ms)
[----------] 1 test from FizzBuzzTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (4 ms total)
[  PASSED  ] 1 test.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13

We are going to implement the following tests. These should be in order of increasing complexity from the simplest test case to the most complex.


  1. Can I call the fizzBuzz function?
  2. Get "1" when I pass in 1.
  3. Get "2" when I pass in 2.
  4. Get "Fizz" when I pass in 3.
  5. Get "Buzz" when I pass in 5.
  6. Get "Fizz" when I pass in 6 (a multiple of 3).
  7. Get "Buzz" when I pass in 10 (a multiple of 5).
  8. Get "FizzBuzz" when I pass in 15 (a multiple of 3 and 5).

Red Phase

Write a failing unit test.

Now Let's implement our first test case. Can I call fizzBuzz? To start, we enter the red phase, and in the red phase, I need to implement a failing unit test. So I'm gonna repurpose this assert true test case to validate that I can call the fizzBuzz function. Okay. Can call fizz buzz. I remove the assert true, and in standard string result equals fizzBuzz.




 
 
 

#include "fizz.h"
#include <gtest/gtest.h>

TEST(FizzBuzzTest, canCallFizzBuzz){
    std::string result = fizzBuzz(1);
}
1
2
3
4
5
6

And the test fails error: use of undeclared identifier 'fizzBuzz', because we have not implemented the function just yet.











 
 











-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/Thiago Souto/Documents/CLionProjects/FinalTest/cmake-build-debug
[ 16%] Building CXX object lib/googletest/googletest/CMakeFiles/gtest.dir/src/gtest-all.cc.obj
[ 33%] Linking CXX static library ..\..\gtestd.lib
[ 33%] Built target gtest
[ 50%] Building CXX object lib/googletest/googletest/CMakeFiles/gtest_main.dir/src/gtest_main.cc.obj
[ 66%] Linking CXX static library ..\..\gtest_maind.lib
[ 66%] Built target gtest_main
[ 83%] Building CXX object CMakeFiles/FinalTest.dir/FizzBuzz/fizz.cpp.obj
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(9,26): error: use of undeclared identifier 'fizzBuzz'
    std::string result = fizzBuzz(1);
                         ^
1 error generated.
NMAKE : fatal error U1077: 'C:\PROGRA~2\MICROS~4\2019\COMMUN~1\VC\Tools\Llvm\bin\clang-cl.exe' : return code '0x1'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\HostX86\x86\nmake.exe"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\HostX86\x86\nmake.exe"' : return code '0x2'
Stop.
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\HostX86\x86\nmake.exe"' : return code '0x2'
Stop.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Now I have a compile error because the fizzBuzz function does not exist and per our three laws of TDD, not compiling is equivalent to failing. So we've successfully implemented a failing unit test which means we've completed the red phase.


Green Phase

Write just enough production code to make the failing unit test pass.

So now we move on to the green phase. We need to make this test pass. We can do that pretty simply by implementing the fizzBuzz function. This shouldn't be the final implementation of the function, this should just be enough to make this test pass.




 
 
 






#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return "";
};


TEST(FizzBuzzTest, canCallFizzBuzz){
    std::string result = fizzBuzz(1);
}
1
2
3
4
5
6
7
8
9
10
11
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FizzBuzzTest
[ RUN      ] FizzBuzzTest.canCallFizzBuzz
[       OK ] FizzBuzzTest.canCallFizzBuzz (0 ms)
[----------] 1 test from FizzBuzzTest (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (4 ms total)
[  PASSED  ] 1 test.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13

And that passes. Excellent, we've made our unit test pass with the simplest change we could make to the production code. We're done with the green phase and it's time to go on to the refactor phase.

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Is there anything to refactor here? I don't think so, there's nothing here yet. So let's mark this as complete in our to do list, and then go ahead and move right back to the red phase and restart our TDD cycle with a next test case.


  1. Can I call the fizzBuzz function?

  2. Get "1" when I pass in 1.

Red Phase

Write a failing unit test.

First Lets do a failing test:













 
 
 
 

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return "";
};


TEST(FizzBuzzTest, canCallFizzBuzz){
    std::string result = fizzBuzz(1);
}

TEST(FizzBuzzTest, returns1When1Passed){
    std::string result = fizzBuzz(1);
    ASSERT_STREQ("1", result.c_str());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FizzBuzzTest
[ RUN      ] FizzBuzzTest.canCallFizzBuzz
[       OK ] FizzBuzzTest.canCallFizzBuzz (0 ms)
[ RUN      ] FizzBuzzTest.returns1When1Passed
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(19): error: Expected equality of these values:

  "1"
  result.c_str()
    Which is: ""
[  FAILED  ] FizzBuzzTest.returns1When1Passed (1 ms)
[----------] 2 tests from FizzBuzzTest (4 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (7 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTest.returns1When1Passed

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Okay. Standard string result equals fizzBuzz(1). Assert string equals one result dot C string. Okay. And that fails. We see it's for our new test case, that it's failed. Good, okay, so now that we've got our failing unit test, we can go to the green phase and make this test pass.

Green Phase

Write just enough production code to make the failing unit test pass.

To make this test pass, we'll need to make the simplest change we can to the production code that will make it pass without breaking any of the other tests.

That should be pretty easy in this case. We'll just modify the code to always return a string of one.





 














#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return "1";
};


TEST(FizzBuzzTest, canCallFizzBuzz){
    std::string result = fizzBuzz(1);
}

TEST(FizzBuzzTest, returns1When1Passed){
    std::string result = fizzBuzz(1);
    ASSERT_STREQ("1", result.c_str());
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

And the test pass

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FizzBuzzTest
[ RUN      ] FizzBuzzTest.canCallFizzBuzz
[       OK ] FizzBuzzTest.canCallFizzBuzz (0 ms)
[ RUN      ] FizzBuzzTest.returns1When1Passed
[       OK ] FizzBuzzTest.returns1When1Passed (0 ms)
[----------] 2 tests from FizzBuzzTest (3 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (6 ms total)
[  PASSED  ] 2 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

There we go, all of our tests pass now, so on to the refactoring phase. Is there anything here to refactor? Is there any duplication? There is.

This call to fizzBuzz is duplicated in both of my tests. That first test case is no longer really needed as the second test case validates that fizzBuzz can be called. I'm gonna go ahead and remove that first test case.

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return "1";
};

TEST(FizzBuzzTest, returns1When1Passed){
    std::string result = fizzBuzz(1);
    ASSERT_STREQ("1", result.c_str());
}
1
2
3
4
5
6
7
8
9
10
11
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from FizzBuzzTest
[ RUN      ] FizzBuzzTest.returns1When1Passed
[       OK ] FizzBuzzTest.returns1When1Passed (0 ms)
[----------] 1 test from FizzBuzzTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (3 ms total)
[  PASSED  ] 1 test.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13

Okay, I don't think there's any more refactoring to be done. Let's mark this test case complete, and then back to the red phase in the next test case.

  1. Can I call the fizzBuzz function?
  2. Get "1" when I pass in 1.
  3. Get "2" when I pass in 2.

Red Phase

Write a failing unit test.

Okay, third test case. In this test case, I'm going to need to return a string of two when a value of two is passed in. I'm in the red phase, so the first thing I'm going to do is implement a failing unit test.

Fizz buzz test. That's actually tests. Returns two with two passed in. (whistling) Okay. Result equals fizzBuzz(2). Assert string equals two result dot C string.

Google Test does its string comparison on C style strings, so we cast the standard string to a C string here.










 




 


#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return "1";
};

TEST(FizzBuzzTests, returns1When1Passed){
    std::string result = fizzBuzz(1);
    ASSERT_STREQ("1", result.c_str());
}

TEST(FizzBuzzTests, returns2When2Passed){
    std::string result = fizzBuzz(2);
    ASSERT_STREQ("2", result.c_str());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1Passed
[       OK ] FizzBuzzTests.returns1When1Passed (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2Passed
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(19): error: Expected equality of these values:

  "2"
  result.c_str()
    Which is: "1"
[  FAILED  ] FizzBuzzTests.returns2When2Passed (1 ms)
[----------] 2 tests from FizzBuzzTests (4 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (6 ms total)
[  PASSED  ] 1 test.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returns2When2Passed

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Okay, so we've got our new unit test written, and it fails.

Green Phase

Write just enough production code to make the failing unit test pass.

Okay, now that we've got the failing unit test, I can go to the green phase and update the production code to make the test pass. I'll make this test pass by generalizing production code. I'll make it more generalized by converting the passed in value to a string, and this is a common practice with TDD. You'll implement some functionality in a very specific way in one test case just to make that test case pass, and then you'll generalize that production code in other test cases to make all the test cases pass.


So instead of returning this very specific one, I will convert this to a string.





 












#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return std::to_string(value);
};

TEST(FizzBuzzTests, returns1When1Passed){
    std::string result = fizzBuzz(1);
    ASSERT_STREQ("1", result.c_str());
}

TEST(FizzBuzzTests, returns2When2Passed){
    std::string result = fizzBuzz(2);
    ASSERT_STREQ("2", result.c_str());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

And now all my tests pass.

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1Passed
[       OK ] FizzBuzzTests.returns1When1Passed (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2Passed
[       OK ] FizzBuzzTests.returns2When2Passed (0 ms)
[----------] 2 tests from FizzBuzzTests (2 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (6 ms total)
[  PASSED  ] 2 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

And now that the test is passing, we can go on to the refactoring phase. Now in the refactoring phase, we will look for any duplication or other cleanup in the code. Is there any duplication here?

The structure between our two tests is duplicated. Let's refactor that into a simple utility function that can be used by our test.


Okay. So, I'm going to create a void check fizz buzz function, which is passed in the integer value that we want to test with, and the expected result string and in this function, we will follow the same pattern. (whistling) Expected result. C string result dot C string. Which means both of my test cases here to use that new checkFizzBuzz() utility function.








 
 
 
 


 



 


#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return std::to_string(value);
};

void checkFizzBuzz( int value, std::string expectedResult ){
    std::string result = fizzBuzz(value);
    ASSERT_STREQ(expectedResult.c_str(), result.c_str());
}

TEST(FizzBuzzTests, returns1When1Passed){
    checkFizzBuzz(1, "1");
}

TEST(FizzBuzzTests, returns2When2Passed){
    checkFizzBuzz(2, "2");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

, and after all these changes, I rerun my tests to see that they still pass. Okay, great. Let's mark off this test case and we'll

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1Passed
[       OK ] FizzBuzzTests.returns1When1Passed (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2Passed
[       OK ] FizzBuzzTests.returns2When2Passed (0 ms)
[----------] 2 tests from FizzBuzzTests (3 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (6 ms total)
[  PASSED  ] 2 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  1. Can I call the fizzBuzz function?
  2. Get "1" when I pass in 1.
  3. Get "2" when I pass in 2.
  4. Get "Fizz" when I pass in 3.

Red Phase

Write a failing unit test.

In this test case, things are gonna start to get a little interesting. We're going to make the code return Fizz when a value of three is passed in.

Again, we're in the red phase, so our first step is to implement a failing unit test. We'll use our utility function to implement it.





















 
 
 

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    return std::to_string(value);
};

void checkFizzBuzz( int value, std::string expectedResult ){
    std::string result = fizzBuzz(value);
    ASSERT_STREQ(expectedResult.c_str(), result.c_str());
}

TEST(FizzBuzzTests, returns1When1PassedIn){
    checkFizzBuzz(1, "1");
}

TEST(FizzBuzzTests, returns2When2PassedIn){
    checkFizzBuzz(2, "2");
}

TEST(FizzBuzzTests, returnsFizzWhen3PassedIn){
    checkFizzBuzz(3, "Fizz");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(14): error: Expected equality of these values:

  expectedResult.c_str()
    Which is: "Fizz"
  result.c_str()
    Which is: "3"
[  FAILED  ] FizzBuzzTests.returnsFizzWhen3PassedIn (2 ms)
[----------] 3 tests from FizzBuzzTests (8 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (11 ms total)
[  PASSED  ] 2 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returnsFizzWhen3PassedIn

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Green Phase

Write just enough production code to make the failing unit test pass.

Okay, now that we have a failing unit test, we'll go ahead and move on to the green phase and make this test pass. We want to make the smallest and simplest change we can to the production code to make this test pass. That can be very specific for this test case, and then we'll generalize it as we need to in the future for future test cases. So for this test, I'm gonna update the production code to check if three is passed in, and if so, return Fizz. If value three equals value, return fizz.

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    if (value == 3)
        return "Fizz";
    return std::to_string(value);
};

void checkFizzBuzz( int value, std::string expectedResult ){
    std::string result = fizzBuzz(value);
    ASSERT_STREQ(expectedResult.c_str(), result.c_str());
}

TEST(FizzBuzzTests, returns1When1PassedIn){
    checkFizzBuzz(1, "1");
}

TEST(FizzBuzzTests, returns2When2PassedIn){
    checkFizzBuzz(2, "2");
}

TEST(FizzBuzzTests, returnsFizzWhen3PassedIn){
    checkFizzBuzz(3, "Fizz");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Great. Okay, our test is passing.

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[----------] 3 tests from FizzBuzzTests (21 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (34 ms total)
[  PASSED  ] 3 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Let's move on to the refactoring phase. Is there anything to refactor? I don't think so. Let's mark this test case complete, and then back to the red phase to enter another iteration of the TDD cycle.

  1. Can I call the fizzBuzz function?
  2. Get "1" when I pass in 1.
  3. Get "2" when I pass in 2.
  4. Get "Fizz" when I pass in 3.
  5. Get "Buzz" when I pass in 5.

Red Phase

Write a failing unit test.

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    if (value == 3)
        return "Fizz";
    return std::to_string(value);
};

void checkFizzBuzz( int value, std::string expectedResult ){
    std::string result = fizzBuzz(value);
    ASSERT_STREQ(expectedResult.c_str(), result.c_str());
}

TEST(FizzBuzzTests, returns1When1PassedIn){
    checkFizzBuzz(1, "1");
}

TEST(FizzBuzzTests, returns2When2PassedIn){
    checkFizzBuzz(2, "2");
}

TEST(FizzBuzzTests, returnsFizzWhen3PassedIn){
    checkFizzBuzz(3, "Fizz");
}

TEST(FizzBuzzTests, returnsBuzzWhen5PassedIn){
    checkFizzBuzz(5, "Buzz");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(16): error: Expected equality of these values:

  expectedResult.c_str()
    Which is: "Buzz"
  result.c_str()
    Which is: "5"
[  FAILED  ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[----------] 4 tests from FizzBuzzTests (9 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (14 ms total)
[  PASSED  ] 3 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returnsBuzzWhen5PassedIn

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

Green Phase

Write just enough production code to make the failing unit test pass.

Okay, now we're in the green phase, and we'll make this test pass by adding another if condition to the production code.

#include "fizz.h"
#include <gtest/gtest.h>

std::string fizzBuzz(int value) {
    if (value == 3)
        return "Fizz";
    if (value == 5)
        return "Buzz";
    return std::to_string(value);
};
1
2
3
4
5
6
7
8
9
10
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[----------] 4 tests from FizzBuzzTests (7 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (11 ms total)
[  PASSED  ] 4 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Okay, so now we go to the refactoring phase. Is there anything to refactor? It doesn't really look like it at this point. Let's move on with the next test case.

  1. Can I call the fizzBuzz function?
  2. Get "1" when I pass in 1.
  3. Get "2" when I pass in 2.
  4. Get "Fizz" when I pass in 3.
  5. Get "Buzz" when I pass in 5.
  6. Get "Fizz" when I pass in 6 (a multiple of 3).

Red Phase

Write a failing unit test.

So we'll mark this off. Okay, now in this test case, we want to generalize the production code to not just return Fizz for the value of three, but for three or any multiple of three. We're in the red phase, so let's write our failing unit test.

TEST(FizzBuzzTests, returnsFizzWhen6PassedIn){
    checkFizzBuzz(6, "Fizz");
}
1
2
3
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(18): error: Expected equality of these values:

  expectedResult.c_str()
    Which is: "Fizz"
  result.c_str()
    Which is: "6"
[  FAILED  ] FizzBuzzTests.returnsFizzWhen6PassedIn (1 ms)
[----------] 5 tests from FizzBuzzTests (8 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran. (12 ms total)
[  PASSED  ] 4 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returnsFizzWhen6PassedIn

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Green Phase

Write just enough production code to make the failing unit test pass.

std::string fizzBuzz(int value) {
    if (value % 3 == 0)
        return "Fizz";
    if (value == 5)
        return "Buzz";
    return std::to_string(value);
};
1
2
3
4
5
6
7

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Great. So let's refactor. I think this fizzBuzz function could use a little cleanup to make those if statements a little bit cleaner. I also don't like the multiple return statements. I think I'm gonna leave those for now, so let's do the next test case. Mark that off.

  1. Get "Fizz" when I pass in 6 (a multiple of 3).
  2. Get "Buzz" when I pass in 10 (a multiple of 5).

Red Phase

Write a failing unit test.

TEST(FizzBuzzTests, returnsBuzzWhen10PassedIn) {
    checkFizzBuzz(10, "Buzz");
}
1
2
3
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 6 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 6 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen6PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen10PassedIn
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(18): error: Expected equality of these values:

  expectedResult.c_str()
    Which is: "Buzz"
  result.c_str()
    Which is: "10"
[  FAILED  ] FizzBuzzTests.returnsBuzzWhen10PassedIn (1 ms)
[----------] 6 tests from FizzBuzzTests (51 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test suite ran. (57 ms total)
[  PASSED  ] 5 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returnsBuzzWhen10PassedIn

 1 FAILED TEST

Process finished with exit code 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Green Phase

Write just enough production code to make the failing unit test pass.

std::string fizzBuzz(int value) {
    if ((value % 3) == 0)
        return "Fizz";
    if ((value % 5) == 0)
        return "Buzz";
    return std::to_string(value);
};
1
2
3
4
5
6
7
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 6 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 6 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen6PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen10PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen10PassedIn (0 ms)
[----------] 6 tests from FizzBuzzTests (23 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test suite ran. (31 ms total)
[  PASSED  ] 6 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Okay, this is getting to be routine. All right, let's refactor. Is there anything to refactor here, any duplication? The expression in the two if statements in fizzBuzz are pretty much identical. I'm going to go ahead and extract those into a isMultiple() utility method, try and make the code a little bit cleaner. So I'll do that now. Bool isMultiple. Int value, int base. Return 0 equals value mod base. And then I'll modify the if statements to use the new utility function, hopefully make the code a little more readable, and validate that my unit tests still pass.

std::string fizzBuzz(int value) {
    if (isMultiple(value, 3))
        return "Fizz";
    if (isMultiple(value, 5))
        return "Buzz";
    return std::to_string(value);
};
1
2
3
4
5
6
7
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 6 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 6 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen6PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen10PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen10PassedIn (0 ms)
[----------] 6 tests from FizzBuzzTests (27 ms total)

[----------] Global test environment tear-down
[==========] 6 tests from 1 test suite ran. (32 ms total)
[  PASSED  ] 6 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Great. Okay, on to the last test case. In this final test case, we're gonna return FizzBuzz if the passed in value is a multiple of three and five.

  1. Get "Fizz" when I pass in 6 (a multiple of 3).
  2. Get "Buzz" when I pass in 10 (a multiple of 5).
  3. Get "FizzBuzz" when I pass in 15 (a multiple of 3 and 5).

Red Phase

Write a failing unit test.

TEST(FizzBuzzTests, returnsFizzBuzzWhen15PassedIn) {
    checkFizzBuzz(15, "FizzBuzz");
}
1
2
3
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 7 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 7 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (1 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen6PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen10PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen10PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzBuzzWhen15PassedIn
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\FizzBuzz\fizz.cpp(22): error: Expected equality of these values:

  expectedResult.c_str()
    Which is: "FizzBuzz"
  result.c_str()
    Which is: "Fizz"
[  FAILED  ] FizzBuzzTests.returnsFizzBuzzWhen15PassedIn (2 ms)
[----------] 7 tests from FizzBuzzTests (33 ms total)

[----------] Global test environment tear-down
[==========] 7 tests from 1 test suite ran. (39 ms total)
[  PASSED  ] 6 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] FizzBuzzTests.returnsFizzBuzzWhen15PassedIn

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

Green Phase

Write just enough production code to make the failing unit test pass.

Okay. All right, we have our failing unit test. Now let's enter the green phase and make it pass. You're gonna make this pass pretty simply by adding another if statement to the fizzBuzz function, checking if the value is a multiple of three and five.

//
// Created by Thiago Souto on 17/04/2020.
//

#include "fizz.h"
#include <gtest/gtest.h>

bool isMultiple(int value, int base) {
    return((value % base) == 0);
}

std::string fizzBuzz(int value) {
    if (isMultiple(value, 3) && isMultiple(value, 5))
        return "FizzBuzz";
    if (isMultiple(value, 3))
        return "Fizz";
    if (isMultiple(value, 5))
        return "Buzz";
    return std::to_string(value);
};

void checkFizzBuzz(int value, std::string expectedResult) {
    std::string result = fizzBuzz(value);
    ASSERT_STREQ(expectedResult.c_str(), result.c_str());
}

TEST(FizzBuzzTests, returns1When1PassedIn) {
    checkFizzBuzz(1, "1");
}

TEST(FizzBuzzTests, returns2When2PassedIn) {
    checkFizzBuzz(2, "2");
}

TEST(FizzBuzzTests, returnsFizzWhen3PassedIn) {
    checkFizzBuzz(3, "Fizz");
}

TEST(FizzBuzzTests, returnsBuzzWhen5PassedIn) {
    checkFizzBuzz(5, "Buzz");
}

TEST(FizzBuzzTests, returnsFizzWhen6PassedIn) {
    checkFizzBuzz(6, "Fizz");
}

TEST(FizzBuzzTests, returnsBuzzWhen10PassedIn) {
    checkFizzBuzz(10, "Buzz");
}

TEST(FizzBuzzTests, returnsFizzBuzzWhen15PassedIn) {
    checkFizzBuzz(15, "FizzBuzz");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 7 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 7 tests from FizzBuzzTests
[ RUN      ] FizzBuzzTests.returns1When1PassedIn
[       OK ] FizzBuzzTests.returns1When1PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returns2When2PassedIn
[       OK ] FizzBuzzTests.returns2When2PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen3PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen3PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen5PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen5PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzWhen6PassedIn
[       OK ] FizzBuzzTests.returnsFizzWhen6PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsBuzzWhen10PassedIn
[       OK ] FizzBuzzTests.returnsBuzzWhen10PassedIn (0 ms)
[ RUN      ] FizzBuzzTests.returnsFizzBuzzWhen15PassedIn
[       OK ] FizzBuzzTests.returnsFizzBuzzWhen15PassedIn (0 ms)
[----------] 7 tests from FizzBuzzTests (35 ms total)

[----------] Global test environment tear-down
[==========] 7 tests from 1 test suite ran. (39 ms total)
[  PASSED  ] 7 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Excellent. Our code is now passing all the test cases, so on to the refactoring phase. Anything to refactor here? I think this looks pretty good at this point and no more refactoring is really necessary for this simple implementation, so that completes the fizzBuzz code kata.

# Google Test

# The TEST Macro

  • The TEST macro defines an individual test for a particular test case.
  • Tests from the same test cases will be grouped together in the execution output.
  • Test case and test names should be valid C++ identifiers and should not use "_".
TEST(TestCaseName, TestName)
{
    EXPECT_EQ(1, 1);
}
1
2
3
4

# Test Fixtures

Google Test provides a framework for what are known as test fixtures or test suites.

  • Test fixtures allow for common setup and tear down between tests.
  • Test fixtures are classes which are derived from the Google Test test class. And a new instance of this class will be created for each test.
  • Each test fixture can implement virtual SetUp and TearDown functions which will be called between each test.
  • And when creating tests using a test fixture, the TEST_F macro is used rather than the TEST macro. And the text fixture name and the test name are passed in.

# Text Fixtures - Constructor/Destructor vs SetUp/TearDown

Since Google Test creates new instances of the text fixture class at the start of each test and then destroys that class after the test is run, then the initialization and cleanup of the test fixture can be done in the class' constructor and destructor.

This is the preferable method and should be more commonly used as it allows for specifying member variables as const and will automatically call base class constructors.

SetUp and TearDown functions may still be necessary if there's a chance the code will generate an exception in the cleanup as that will lead to undefined behavior if it occurred in the destructor.


Here I've got a very simple example of a test fixture with a test that uses it. The text fixture has a pointer to a test object, which it creates a new instance of in a constructor and cleans up in the destructor. The test then makes a method call on the test object and asserts the results.

class TestFixturesExample : public ::testing::Test{
    public:
        TestFixtureExample::TestFixtureExample{
            testObj = new TestObj();
        }
        virtual void TearDown(){
            delete(testObj);
        }
        testObj *testObj;
    }
    TEST_F(TestFixtureExample, TestIt){
        ASSERT_TRUE( testObj->run() );
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# Google Test Asserts

Google Test provides assert macros. And these macros perform the checks that determine if a test passes or fails. There are two types of failure assert macros.

  • The ASSERT macros will completely abort the current tests when they fail. Meaning that none of the other asserts in the test are called.

  • The EXPECT macros will indicate the failure, but will not abort the current test. Any additional macros will still be called, potentially identifying additional failures in the test.

The EXPECT macros should be used if possible when the test has multiple asserts to try to identify all the failures in the test.

# Comparison Type Asserts

There are four comparison type asserts that Google Test provides.

  • A basic comparison, which simply verifies that the passed-in condition evaluates to true or false.
  • A binary comparison, which will verify that the two values are either equal, not equal, less than, less than or equal, greater than, or greater than or equal to each other.
  • A string comparison, which will compare two C style strings to each other. For standard string comparisons, you can also use the binary comparison asserts.
  • Google Test also provides asserts for comparing floats and doubles. Floats and doubles are unique, in that they can never be compared exactly due to how they are stored in memory. The asserts that Google Test provides will verify that two floating point values are either very close to each other with several digits of precision, or allow you to specify how much air is allowed between the two compared numbers.

# Basic Assert Statements

TEST( Examples, BasicAssertExamples ){
    ASERT_TRUE(1 == 1);     //Passes!
    ASERT_FALSE(1 == 2);    //Passes!
    ASERT_TRUE(1 == 2);     //Fails!
    ASERT_FALSE(1 == 1);    //Fails!
}
1
2
3
4
5
6

# Bynary Comparison Example

Here I'm showing you an example of each of the binary comparison asserts. Each of these asserts pass as the passed-in values match the corresponding comparison operation of the assert. The equals, not equals, less than, less than or equal, greater than, or greater than or equal.

TEST( Examples, BinaryAssertExamples ){
    ASSERT_EQ(1, 1);
    ASSERT_NE(1, 2);
    ASSERT_LT(1, 2);
    ASSERT_LE(1, 1);
    ASSERT_GT(2, 1);
    ASSERT_GE(2, 2);
}
1
2
3
4
5
6
7
8

# String Comparison Example

Here I'm showing the different asserts for string comparison. The top two asserts show how the binary comparison asserts can be used with the C++ standard string. The remaining asserts show how C style strings can be compared for equals or not equals, and optionally, ignoring case.

TEST( Examples, StringAssertExamples ){
    ASSERT_EQ(std::string("1"), std::string("1"));
    ASSERT_NE(std::string("a"), std::string("b"));
    ASSERT_STREQ("a", "a");
    ASSERT_STRNE("a", "b");
    ASSERT_STRCASEEQ("A", "a");
    ASSERT_STRCASENEQ("A", "b");
}
1
2
3
4
5
6
7
8

# Float/Double Comparison Example

Here I'm showing the assert comparisons for floating point values. The first two asserts use Google Test's built-in logic for determining a floating point values are very close to each other in value. The second two asserts show how you can specify your own absolute error for the difference between the two passed-in floating point values.

TEST( Examples, FloatDoubleAssertExamples ){
    ASSERT_FLOAT_EQ(1.0001f, 1.0001f);
    ASSERT_DOUBLE_EQ(1.0001, 1.0001);
    ASSERT_NEAR(1.0001, 1.0001, .0001);     // Passes
    ASSERT_NEAR(1.0001, 1.0003, .0001);     // Fails
}
1
2
3
4
5
6

# Asserts on Exceptions

Google Test also supports asserts on exceptions. Asserts can fail a test when an exception is expected to be thrown from a function call. That assert can be on a specific exception or on any exception. An assert can also fail a test when an exception is thrown that was not expected.

TEST( Examples, FloatDoubleAssertExamples ){
    ASSERT_THROW( callIt(), ReallyBadException);
    ASSERT_ANY_THROW(callIt());
    ASSERT_NO_THROW(callIt());      //Passes
}
1
2
3
4
5

# Command Line Arguments

Google Test also adds several command line options to your unit test executable. In this slide, I'm showing a few of the most useful.

  • The gtest_filter command line argument allows you to specify which tests are executed. This parameter is broken up into two regular expressions separated by a colon. The first regular expression defines the names of the test cases that should be executed. And the second regular expression defines the names of the tests that should be executed. Both regular expressions must be matched by a test for it to be executed.

  • The next option is gtest_repeat. This allows you to tell Google Test to run the suite of tests n number of times. This can be very useful for identifying flaky tests that fail intermittently.

  • The last option is gtest_shuffle. This runs the test in a randomized order. This can also be very helpful in verifying that there are no dependencies between the different tests in the test suite.

# The Supermarket Checkout Kata

The checkout class has the following test cases that I'll go through as I'm implementing the class with TDD.

  1. Can create an instance of the checkout class,
  2. Can add an item price,
  3. Can add an individual item to the list of checkout items,
  4. Can calculate the current total,
  5. Can add multiple items and getting the correct total,
  6. Can add discount rules, and
  7. Can apply the discount rules when calculating the total.

# Setup the project

First create a new folder and a new cpp called Supermarket. Then adjust the cmake file:

cmake_minimum_required(VERSION 3.16)
project(FinalTest)

include_directories(C:/Users/Thiago Souto/Documents/CLionProjects/FinalTest/lib/pkgconfig)
find_library(FinalTest_lib FinalTest)

#group the libraries
set(frameworks ${FinalTest_lib})

set(CMAKE_CXX_STANDARD 20)

# Directories
add_subdirectory(lib/googletest)
# include_directories(FizzBuzz)
include_directories(Supermarket)
include_directories(lib/googletest/googletest/include)
include_directories(lib/googletest/googlemock/include)


# Source code in use
set(SOURCE_FILES Supermarket/Supermarket.cpp)

add_executable(FinalTest ${SOURCE_FILES})

target_link_libraries(FinalTest gtest gtest_main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Then, write a simple code to execute an ASSERT_TRUE(true); test.

#include <iostream>
#include <string>
#include <gtest/gtest.h>

TEST(Supermarket, SimpleAssert){
    ASSERT_TRUE(true);
}
1
2
3
4
5
6
7

Test result:

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from Supermarket
[ RUN      ] Supermarket.SimpleAssert
[       OK ] Supermarket.SimpleAssert (0 ms)
[----------] 1 test from Supermarket (2 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (4 ms total)
[  PASSED  ] 1 test.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13

So now that I have Google test set up and my green bar, I'm ready to enter my TDD loop of red green refactor. I'm going to go ahead and enter the red phase and start implementing the first test case.

  1. Can create an instance of the checkout class,

Red Phase

Write a failing unit test.

TEST(SupermarketTests, CanInstantiateCheckout){
    Checkout co;
}
1
2
3

Green Phase

Write just enough production code to make the failing unit test pass.

We create a Checkout class in the project and #include "Checkout.h". And I also created a src folder and put the cpp files inside.

#include <iostream>
#include <string>
#include <gtest/gtest.h>
#include "Checkout.h"

TEST(SupermarketTests, CanInstantiateCheckout){
    Checkout co;
}
1
2
3
4
5
6
7
8


remember to include the Checkout.cpp file in the cmake

cmake_minimum_required(VERSION 3.16)
 project(FinalTest)
 
 include_directories(C:/Users/Thiago Souto/Documents/CLionProjects/FinalTest/lib/pkgconfig)
 find_library(FinalTest_lib FinalTest)
 
 #group the libraries
 set(frameworks ${FinalTest_lib})
 
 set(CMAKE_CXX_STANDARD 17)
 
 # Directories
 add_subdirectory(lib/googletest)
 # include_directories(FizzBuzz)
 include_directories(Supermarket)
 include_directories(lib/googletest/googletest/include)
 include_directories(lib/googletest/googlemock/include)
 
 
 # Source code in use
 set(SOURCE_FILES Supermarket/src/CheckoutTest.cpp Supermarket/src/Checkout.cpp)
 
 
 add_executable(FinalTest ${SOURCE_FILES})
 
 target_link_libraries(FinalTest gtest gtest_main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

And I can execute it, excellent. I now have my first passing unit test and I'm ready to refactor it. Is there anything to refactor here? I don't think so. I'm ready to go back to the red phase.

  1. Can create an instance of the checkout class,
  2. Can add an item price,

Now we will be implementing the add item prices and items, and calculate total test cases. I'm gonna begin my marking off the can instantiate checkout test case, let's do that now. Okay, then I need to create the test for the can add item price test case which will simply instantiate the checkout class and try to call in add item price method that will pass in the item string and the unit price as an integer.

Red Phase

Write a failing unit test.

TEST(SupermarketTests, CanAddItemPrice){
    Checkout co;
    co.addItemPrice("a", 1);
}
1
2
3
4
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\Supermarket\src\CheckoutTest.cpp(16,8): error: no member named 'addItemPrice' in 'Checkout'
    co.addItemPrice("a", 1);
    ~~ ^
1 error generated.
1
2
3
4

Green Phase

Write just enough production code to make the failing unit test pass.

Okay so now that I've got a failing unit test, I need to enter the green phase and make the test pass. I need to make it pass the simplest way possible. In this case I just need to implement the add item price method to checkout.

// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <stdio.h>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

TEST(SupermarketTests, CanInstantiateCheckout){
    Checkout co;
}

TEST(CheckoutTests, CanAddItemPrice) {
    Checkout co;
    co.addItemPrice("a", 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Checkout.cpp

#include "Checkout.h"

void Checkout::addItemPrice(std::string item, int price) {
    ;
}
1
2
3
4
5
6
7
// Checkout.h

#ifndef FINALTEST_CHECKOUT_H
#define FINALTEST_CHECKOUT_H

#include <string>

class Checkout {
public:
//    Checkout();
//    virtual ~Checkout();

    void addItemPrice(std::string item, int price);

};

#endif //FINALTEST_CHECKOUT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

So now that my unit test is passing I can go ahead and enter the refactoring phase. Is there anything to refactor here? At checkout instantiation it's duplicated it for both of my tests. I don't think the first test is necessary anymore so I'm validating that I can instantiate an instance of checkout in the can add item price test. So I'm gonna go ahead and remove that first test. And validate my remaining test still passes, great.

// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <stdio.h>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

TEST(CheckoutTests, CanAddItemPrice) {
    Checkout co;
    co.addItemPrice("item", 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Okay, and then it's back to the red phase and the next test case. So in this test case I need to update the checkout class to have a public interface for adding checkout items.

  1. Can create an instance of the checkout class,
  2. Can add an item price,
  3. Can add an individual item to the list of checkout items,

Red Phase

Write a failing unit test.

So I'm in the red phase, so the first step is to add a failing unit test. Test checkout test can add item.

TEST(CheckoutTests, CanAddItem) {
    Checkout co;
    co.addItem("a");
}
1
2
3
4
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\Supermarket\src\CheckoutTest.cpp(20,8): error: no member named 'addItem' in 'Checkout'
    co.addItem("a");
    ~~ ^
1 error generated.
1
2
3
4

Green Phase

Write just enough production code to make the failing unit test pass.

So again I want this to be the simplest implementation possible to make the currently failing unit test pass. So for right now an empty implementation will do that. And we should be able to verify that it makes it pass, and it does, easy okay.

// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

TEST(CheckoutTests, CanAddItemPrice) {
    Checkout co;
    co.addItemPrice("a", 1);
}

TEST(CheckoutTests, CanAddItem) {
    Checkout co;
    co.addItem("a");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Checkout.cpp

#include "Checkout.h"


void Checkout::addItemPrice(std::string item, int price) {
    ;
}

void Checkout::addItem(std::string item) {
    ;
}
1
2
3
4
5
6
7
8
9
10
11
12
// Checkout.h

#ifndef FINALTEST_CHECKOUT_H
#define FINALTEST_CHECKOUT_H

#include <string>


class Checkout {
public:
//    Checkout();
//    virtual ~Checkout();

    void addItemPrice(std::string item, int price);

    void addItem(std::string item);
};


#endif //FINALTEST_CHECKOUT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (0 ms)
[----------] 2 tests from CheckoutTests (3 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (9 ms total)
[  PASSED  ] 2 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

So now to the refactoring phase. Is there anything to refactor? The checkout instantiation is duplicated again. This is gonna happen in all of our tests as they're all gonna need a checkout instance.


This is a good time to go ahead and add a test fixture where I can instantiate checkout as a member variable, and then it can be reused by each of the tests.


So I'll go ahead and do that now. Class checkout tests, which is derived from testing test. And in its constructor, first I'm going to put in a protected member variable for the checkout class. Use a better name than co, and that's really all I need to do. Now I'll change these to test fixture macros TEST_F, and remove the checkout instantiation from each and instead use the new member variable in my test fixture class.


And my test should still pass. Helps if I put in the public inheritance, and now they pass, okay great.

// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

class CheckoutTests : public ::testing::Test {
public:

protected:
    Checkout checkOut;
};

TEST_F(CheckoutTests, CanAddItemPrice) {
    checkOut.addItemPrice("a", 1);
}

TEST_F(CheckoutTests, CanAddItem) {
    checkOut.addItem("a");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (0 ms)
[----------] 2 tests from CheckoutTests (4 ms total)

[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (8 ms total)
[  PASSED  ] 2 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Anything else to refactor here? I don't think so, this looks pretty simple. Okay back to the red phase and the next test case.

  1. Can create an instance of the checkout class,
  2. Can add an item price,
  3. Can add an individual item to the list of checkout items,
  4. Can calculate the current total,

Red Phase

Write a failing unit test.

With this test case I need to update the checkout class to have a public method for calculating the current total and verify that total is correct for one item. I'm in the red phase so my first step is to create the failing unit test.


In this test I'll add one price for item A and then add one item A to the list of checkout items. Then I'll call the calculate total method and verify it returns one, so let's do that now.

TEST_F(CheckoutTests, CanCalculateTotal) {
    checkOut.addItemPrice("a", 1);
    checkOut.addItem("a");
    int total = checkOut.calculateTotal();
    ASSERT_EQ(1, total);
}
1
2
3
4
5
6
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\Supermarket\src\CheckoutTest.cpp(32,26): error: no member named 'calculateTotal' in 'Checkout'
    int total = checkOut.calculateTotal();
                ~~~~~~~~ ^
1 error generated.
1
2
3
4

So I can see here that I have a compile error which means I have a failing unit test.

Green Phase

Write just enough production code to make the failing unit test pass.

So now that I've got my failing test I can go ahead and enter the green phase and make it pass by adding the calculate total method to the checkout class. I'll want the simplest implementation of that method to start, so I'll just have it return one.


So let's add that method to the class, integer calculate total no parameters.

// Checkout.h

#ifndef FINALTEST_CHECKOUT_H
#define FINALTEST_CHECKOUT_H

#include <string>


class Checkout {
public:
//    Checkout();
//    virtual ~Checkout();

    void addItemPrice(std::string item, int price);

    void addItem(std::string item);

    int calculateTotal();
};


#endif //FINALTEST_CHECKOUT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Add the implementation integer checkout calculate total no parameters, and for right now for the simplest implementation necessary to make the currently failing test pass I will just have it return one.

// Checkout.cpp

#include "Checkout.h"


void Checkout::addItemPrice(std::string item, int price) {
    ;
}

void Checkout::addItem(std::string item) {
    ;
}

int Checkout::calculateTotal() {
    return 1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (0 ms)
[ RUN      ] CheckoutTests.CanCalculateTotal
[       OK ] CheckoutTests.CanCalculateTotal (0 ms)
[----------] 3 tests from CheckoutTests (4 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (10 ms total)
[  PASSED  ] 3 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Great, now that my unit test is passing I can enter the refactoring phase. Is there any duplication? The add item price and add item method calls are duplicated in this new test. I don't think the can add item price or can add item tests are needed now as they're both being done in the can calculate total test. I'm gonna go ahead and remove those test cases. Remove both of those, simplify my tests and verify my remaining test still runs. And it does, excellent. So now I can mark off that test case.


















 
 
 
 
 
 
 








// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

class CheckoutTests : public ::testing::Test {
public:

protected:
    Checkout checkOut;
};

TEST_F(CheckoutTests, CanAddItemPrice) {
    checkOut.addItemPrice("a", 1);
}

TEST_F(CheckoutTests, CanAddItem) {
    checkOut.addItem("a");
}

TEST_F(CheckoutTests, CanCalculateTotal) {
    checkOut.addItemPrice("a", 1);
    checkOut.addItem("a");
    int total = checkOut.calculateTotal();
    ASSERT_EQ(1, total);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

class CheckoutTests : public ::testing::Test {
public:

protected:
    Checkout checkOut;
};

TEST_F(CheckoutTests, CanCalculateTotal) {
    checkOut.addItemPrice("a", 1);
    checkOut.addItem("a");
    int total = checkOut.calculateTotal();
    ASSERT_EQ(1, total);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1. Can create an instance of the checkout class,
  2. Can add an item price,
  3. Can add an individual item to the list of checkout items,
  4. Can calculate the current total,
  5. Can add multiple items and getting the correct total,
  6. Can add discount rules, and
  7. Can apply the discount rules when calculating the total.

In this test case (5), I'm going to add prices for multiple items, add multiple items to the checkout, and then verify the correct total is calculated.

Red Phase

Write a failing unit test.

I'm in the red phase, so first I'm going to add the failing test case. In this test case, I'll add the price for two items and actually add two items and verify that the correct total is calculated. Let me do that now. Test fixture, CheckoutTests,

TEST_F(CheckoutTests, CanGetTotalForMultipleItems) {
  checkOut.addItemPrice("a", 1);
  checkOut.addItemPrice("b", 2);
  checkOut.addItem("a");
  checkOut.addItem("b");
  int total = checkOut.calculateTotal();
  ASSERT_EQ(3, total);
}  
1
2
3
4
5
6
7
8
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (0 ms)
[ RUN      ] CheckoutTests.CanCalculateTotal
[       OK ] CheckoutTests.CanCalculateTotal (0 ms)
[ RUN      ] CheckoutTests.CanAddMultipleAndCalculateTotal
C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\Supermarket\src\CheckoutTest.cpp(42): error: Expected equality o
f these values:
  2
  total
    Which is: 1
[  FAILED  ] CheckoutTests.CanAddMultipleAndCalculateTotal (1 ms)
[----------] 4 tests from CheckoutTests (10 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (14 ms total)
[  PASSED  ] 3 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] CheckoutTests.CanAddMultipleAndCalculateTotal

 1 FAILED TEST

Process finished with exit code 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

Green Phase

Write just enough production code to make the failing unit test pass.

Great. So I have a failing unit test, so onto the green phase and let's make it pass. Things start to get a little bit more complicated with this test. What I wanna try and do the simplest thing I can to get the test to pass.


To do that I'll go ahead and add a map, Ada structure, to the checkout class, to keep track of the prices per item. Then I'll update checkout to have a total member variable and increment that total whenever a new item is added.


Let me do that now. First I will add a header file, standard map, Ada structure. Then I'll add a couple of protected member variables. One for the map prices, and then member variable for my running total.




















 
 
 




// Checkout.h

#ifndef FINALTEST_CHECKOUT_H
#define FINALTEST_CHECKOUT_H

#include <string>
#include <map>

class Checkout {
public:
//    Checkout();
//    virtual ~Checkout();

    void addItemPrice(std::string item, int price);

    void addItem(std::string item);

    int calculateTotal();

protected:
    std::map<std::string, int> prices;
    int total;
};

#endif //FINALTEST_CHECKOUT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Make it initialize the total to zero and the class's initialization list.

Checkout::Checkout():total(0){

}
1
2
3

Then I'm going to update the item price method to save this price in my new map of prices. Prices[Item] = price. Then the addItem method, I'm going to increment the total member variable with the price for this specified item. Total += prices[Item]. Lastly, I'm going to change the calculate total to return my running total.

WARNING

to avoid the error: definition of implicitly declared default constructor declare the constructors and destructors on the .h file and on the cpp as well, as shown below:

// Checkout.h

#ifndef FINALTEST_CHECKOUT_H
#define FINALTEST_CHECKOUT_H

#include <string>
#include <map>

class Checkout {
public:
    Checkout(); //Constructor stub
    virtual ~Checkout(); //Constructor stub

    void addItemPrice(std::string item, int price);

    void addItem(std::string item);

    int calculateTotal();

protected:
    std::map<std::string, int> prices;
    int total;
};

#endif //FINALTEST_CHECKOUT_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Checkout.cpp

#include "Checkout.h"

Checkout::Checkout():total(0){
    //Constructor stub
}

Checkout::~Checkout(){
    //Destructor stub
}

void Checkout::addItemPrice(std::string item, int price) {
    prices[item] = price;
}

void Checkout::addItem(std::string item) {
    total += prices[item];
}

int Checkout::calculateTotal() {
    return total;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// CheckoutTest.cpp

#include <iostream>
#include <string>
#include <cstdio>
#include <gtest/gtest.h>
#include "Checkout.h"

using namespace std;

class CheckoutTests : public ::testing::Test {
public:

protected:
    Checkout checkOut;
};

TEST_F(CheckoutTests, CanAddItemPrice) {
    checkOut.addItemPrice("a", 1);
}

TEST_F(CheckoutTests, CanAddItem) {
    checkOut.addItem("a");
}

TEST_F(CheckoutTests, CanCalculateTotal) {
    checkOut.addItemPrice("a", 1);
    checkOut.addItem("a");
    int total = checkOut.calculateTotal();
    ASSERT_EQ(1, total);
}

TEST_F(CheckoutTests, CanGetTotalForMultipleItems) {
    checkOut.addItemPrice("a", 1);
    checkOut.addItemPrice("b", 2);
    checkOut.addItem("a");
    checkOut.addItem("b");
    int total = checkOut.calculateTotal();
    ASSERT_EQ(3, total);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Now our tests run smooth

Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (1 ms)
[ RUN      ] CheckoutTests.CanCalculateTotal
[       OK ] CheckoutTests.CanCalculateTotal (0 ms)
[ RUN      ] CheckoutTests.CanGetTotalForMultipleItems
[       OK ] CheckoutTests.CanGetTotalForMultipleItems (0 ms)
[----------] 4 tests from CheckoutTests (26 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (30 ms total)
[  PASSED  ] 4 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Now my test passes. Now that my test is passing, I can enter the refactor.

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Is there anything to refactor? Anything of concern? I don't think so. So let's mark off this test case, and then we'll move on to the next one.

  1. Can create an instance of the checkout class,
  2. Can add an individual item to the list of checkout items,
  3. Can calculate the current total,
  4. Can add multiple items and getting the correct total,
  5. Can add discount rules, and
  6. Can apply the discount rules when calculating the total.

Red Phase

Write a failing unit test.

So for this test case, I need to add a method to the checkout to allow me to submit a discount rule. A discount has three parameters: the item type, the number of items required for the discount, and the discounted price. So I'm going to add a method called AddDiscount, which takes those three parameters. I'm in the red phase now, so I'll start by implementing a failing unit test that tries to call that new method.

TEST_F(CheckoutTests, CanAddDiscount) {
    checkOut.addDiscount("a", 3, 2);
}
1
2
3

Okay, so I added a test which calls AddDiscount passing in the A type, with three for the number of A's, and two for the discounted price. The test is failing to compile because the AddDiscount method doesn't exist in the checkout class yet. I'm in the green phase now, so I'm gonna add that method to the checkout class to make the test pass.

Green Phase

Write just enough production code to make the failing unit test pass.

As always, this should be the simplest implementation possible to make that test pass. So I'm just going to implement an empty function for now.

void Checkout::addDiscount(std::string item, int nbrOfItems, int discountPrice) {
    ;
}
1
2
3
Running main() from C:\Users\Thiago Souto\Documents\CLionProjects\FinalTest\lib\googletest\googletest\src\gtest_main.cc
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from CheckoutTests
[ RUN      ] CheckoutTests.CanAddItemPrice
[       OK ] CheckoutTests.CanAddItemPrice (0 ms)
[ RUN      ] CheckoutTests.CanAddItem
[       OK ] CheckoutTests.CanAddItem (0 ms)
[ RUN      ] CheckoutTests.CanCalculateTotal
[       OK ] CheckoutTests.CanCalculateTotal (0 ms)
[ RUN      ] CheckoutTests.CanGetTotalForMultipleItems
[       OK ] CheckoutTests.CanGetTotalForMultipleItems (0 ms)
[ RUN      ] CheckoutTests.CanAddDiscount
[       OK ] CheckoutTests.CanAddDiscount (0 ms)
[----------] 5 tests from CheckoutTests (19 ms total)

[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran. (25 ms total)
[  PASSED  ] 5 tests.

Process finished with exit code 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

So now that all my tests are passing, I can go back to the refactoring phase.

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.

Anything to refactor? I don't think so.

  1. Can create an instance of the checkout class,
  2. Can add an individual item to the list of checkout items,
  3. Can calculate the current total,
  4. Can add multiple items and getting the correct total,
  5. Can add discount rules, and
  6. Can apply the discount rules when calculating the total.

So lets go back to the red phase and the next test case. Okay. In this next test case, I'm going to add a discount rule, and then add enough items to the checkout so that the rule should be applied when calculating the total.

Red Phase

Write a failing unit test.

I'm going to implement the simplest test case, which is a rule for three of the same item, which has a lower price than the three individual items. I'm in the red phase, so I'll implement my failing unit test first.

Green Phase

Write just enough production code to make the failing unit test pass.

Refactor Phase

Clean up the unit test and the production code to remove any duplication and make sure the code follows your team's coding standards and best practices.