Difference between revisions of "UnitTesting"

From CodeBlocks
Jump to: navigation, search
(Our first test project environment)
(Our first test project environment)
Line 360: Line 360:
  
 
Time to move to the next section, a little word about Test Driven Development.
 
Time to move to the next section, a little word about Test Driven Development.
 +
 +
 +
== Test Driven Development ==
 +
This is a practice where new code is only added to make a failing test pass. And one only adds the code needed to make that test pass. Once that is done, a new test is added to will check the next functionality to add.
 +
On this really simple example we will more or less practice some TDD.
 +
 +
So get ready, it's time for LeapYear-Step1.
 +
 +
 +
== LeapYear-Step1 ==
 +
We will turn our main.cpp into the guy in charge to carry out the tests, all we need to do is change the contents of main.cpp into this :
 +
 +
    #include "UnitTest++.h"
 +
   
 +
    int main()
 +
    {
 +
        return UnitTest::RunAllTests();
 +
    }
 +
 +
And add '''../UnitTest++/src''' to the compiler search directories on the project level of our LeapYear project.
 +
And add '''../UnitTest++/Deliv/Release/libUnitTest++.a''' to the link libraries of the project
 +
 +
Why : well we need to link with the framework and we need to include the UnitTest++ header so we can use it's functionalities.
 +
 +
The line '''UnitTest::RunAllTests()''' will make sure all test encountered in our sources will be carried out.

Revision as of 21:49, 8 September 2009

This document is under development by killerbot. Starting from 7 September 2009.

Introduction

This document will describe how unit testing can be combined with the Code::Blocks IDE. It will give a hands-on example on a unit testing framework and how it can be used with Code::Blocks.


What is Unit Testing

Simply put Unit Testing is the discipline and best practice of writing and running little test programs that test little units of code. Such a unit can be :

  • a class
  • a free function
  • an interface
  • ...

There are several unit test frameworks available to help you write, deploy, manage your unit tests. The most famous family is the xUnit framework, and some of it's descendants.


Why Unit Testing

Well we write code that implements some functionality. We need a way to check and prove that the implementation is correct. Therefor we need tests. We need tests on the application level, at the integration level of different components, classes, interfaces. But we also need tests for the smallest building blocks, the units. We also wans these tests to run very quickly. That we we can run the tests often, manually or automated. These tests will also be our safeguards during refactoring of the code, extending the code or whatever maintenance tasks we carry out on the code. These tests will also make us think of how our class, method, ... will be used. Since the test can be seen as a user of our code. Thanks do this, thinking about the tests before implementing the functionality can help us in the design : emergent design. It will help us to avoid creating big classes, complex functions. We will only write the methods that are really needed, and no methods that we think might (n)ever be useful in the future. We are no good future tellers ! Those 'might be needed' methods and their tests are just a waste of time. Extending, refactoring will be done when it is actually needed, and at that time we have our unit tests to ensure we don't break any existing code.

Other times to add new unit tests are typically when a bug is discovered; Write a unit test to reproduces the bug, fix the code, and from now on the new unit test will watch your back so the bug won't reappear.

Unit test are also a good place of documentation. The little tests show how the class or method is used. The code documents itself.

So the benefits are :

  • better design
  • prove of your code
  • safe guard during code maintenance (refactoring, extensions)
  • documentation
  • ...


Unit Test frameworks

When writing unit tests, one will create a lot of similar code. Since unit tests are nothing more then little programs, several tasks will be repeated, setting up the test environment, create your class, call a method, verify it's result, write out an error message when the test fails, preferably specifying what the wrong outcome was, and what the expected result is. All these repetitive chores are taken care of by the frame work so the developer can focus on the real code of the test. This document will use a small but very effective framework for the C++ language. It is called UnitTest++. It's homepage is at : UnitTest++. You can download it directly at this link. At the time of writing the latest version is 1.4.


Setup our environment

Let's create a directory on our PC in which we will cary out our experiments. I will refer to this directory from now on as <TestPit>. For my laptop on which I am writing this article, that will be /home/lieven/Projects/UnitTestArticle. Note that everything we will do, works on linux and on windows! Download the "unittest-cpp-1.4.zip" file from the link specified above. Extract it in the <TestPit> directory. All the files will end up in the <TestPit>/UnitTest++ directory.


Build UnitTest++

After the extracted the zip archive we have a directory structure like this : <TestPit>/UnitTest++ | +docs | +src

The files in the UnitTest++ directory are of little use for us, since they bring project files for some bizarre Microsoft IDE ;-) . We will create our own Code::Blocks project to build the UnitTest++ framework.

Why do we need to build something ? Well as said, the framework will do a lot of chores for us, those chores are implemented by code, that code is framework. We will build to frame work into a static library, and for every unit test (or set of unit tests) we create a little (console/shell) program with the test code we wrote, which will link with the static library to obtain a unit test executable, driven by the framework.

We will put our Code::Blocks project file in a nice structured directory tree, therefor let's create a 'Project' subdirectory in the <TestPit>/UniTest++ directory.

In there create a new file with the name "UnitTest++.cbp". Give that file the following content :

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <CodeBlocks_project_file> <FileVersion major="1" minor="6" /> <Project> <Option title="UnitTest++" /> <Option pch_mode="2" /> <Option compiler="gcc" /> <Build> <Target title="Debug"> <Option platforms="Windows;" /> <Option output="../Deliv/Debug/libUnitTest++.a" prefix_auto="0" extension_auto="0" /> <Option working_dir="" /> <Option object_output="../Deliv/Debug/" /> <Option type="2" /> <Option compiler="gcc" /> <Option createDefFile="1" /> <Compiler> <Add option="-Winit-self" /> <Add option="-Wredundant-decls" /> <Add option="-Wcast-align" /> <Add option="-Wundef" /> <Add option="-Wfloat-equal" /> <Add option="-Wmissing-declarations" /> <Add option="-Wmissing-include-dirs" /> <Add option="-Wswitch-enum" /> <Add option="-Wswitch-default" /> <Add option="-Wmain" /> <Add option="-pedantic" /> <Add option="-std=c++98" /> <Add option="-Wextra" /> <Add option="-Wall" /> <Add option="-ansi" /> <Add option="-g" /> </Compiler> </Target> <Target title="Release"> <Option platforms="Windows;" /> <Option output="../Deliv/Release/libUnitTest++.a" prefix_auto="0" extension_auto="0" /> <Option working_dir="" /> <Option object_output="../Deliv/Release/" /> <Option type="2" /> <Option compiler="gcc" /> <Option createDefFile="1" /> <Compiler> <Add option="-O3" /> <Add option="-Winit-self" /> <Add option="-Wredundant-decls" /> <Add option="-Wcast-align" /> <Add option="-Wundef" /> <Add option="-Wfloat-equal" /> <Add option="-Wmissing-declarations" /> <Add option="-Wmissing-include-dirs" /> <Add option="-Wswitch-enum" /> <Add option="-Wswitch-default" /> <Add option="-Wmain" /> <Add option="-pedantic" /> <Add option="-std=c++98" /> <Add option="-Wextra" /> <Add option="-Wall" /> <Add option="-ansi" /> </Compiler> <Linker> <Add option="-s" /> </Linker> </Target> <Target title="Debug(linux)"> <Option platforms="Unix;Mac;" /> <Option output="../Deliv/Debug/libUnitTest++.a" prefix_auto="0" extension_auto="0" /> <Option working_dir="" /> <Option object_output="../Deliv/Debug/" /> <Option type="2" /> <Option compiler="gcc" /> <Option createDefFile="1" /> <Compiler> <Add option="-Winit-self" /> <Add option="-Wredundant-decls" /> <Add option="-Wcast-align" /> <Add option="-Wundef" /> <Add option="-Wfloat-equal" /> <Add option="-Wmissing-declarations" /> <Add option="-Wmissing-include-dirs" /> <Add option="-Wswitch-enum" /> <Add option="-Wswitch-default" /> <Add option="-Wmain" /> <Add option="-pedantic" /> <Add option="-std=c++98" /> <Add option="-Wextra" /> <Add option="-Wall" /> <Add option="-ansi" /> <Add option="-g" /> </Compiler> </Target> <Target title="Release(linux)"> <Option platforms="Unix;Mac;" /> <Option output="../Deliv/Release/libUnitTest++.a" prefix_auto="0" extension_auto="0" /> <Option working_dir="" /> <Option object_output="../Deliv/Release/" /> <Option type="2" /> <Option compiler="gcc" /> <Option createDefFile="1" /> <Compiler> <Add option="-O3" /> <Add option="-Winit-self" /> <Add option="-Wredundant-decls" /> <Add option="-Wcast-align" /> <Add option="-Wundef" /> <Add option="-Wfloat-equal" /> <Add option="-Wmissing-declarations" /> <Add option="-Wmissing-include-dirs" /> <Add option="-Wswitch-enum" /> <Add option="-Wswitch-default" /> <Add option="-Wmain" /> <Add option="-pedantic" /> <Add option="-std=c++98" /> <Add option="-Wextra" /> <Add option="-Wall" /> <Add option="-ansi" /> </Compiler> <Linker> <Add option="-s" /> </Linker> </Target> </Build> <VirtualTargets> <Add alias="All" targets="Debug;Release;Debug(linux);Release(linux);" /> </VirtualTargets> <Unit filename="../src/AssertException.cpp" /> <Unit filename="../src/AssertException.h" /> <Unit filename="../src/CheckMacros.h" /> <Unit filename="../src/Checks.cpp" /> <Unit filename="../src/Checks.h" /> <Unit filename="../src/Config.h" /> <Unit filename="../src/CurrentTest.cpp" /> <Unit filename="../src/CurrentTest.h" /> <Unit filename="../src/DeferredTestReporter.cpp" /> <Unit filename="../src/DeferredTestReporter.h" /> <Unit filename="../src/DeferredTestResult.cpp" /> <Unit filename="../src/DeferredTestResult.h" /> <Unit filename="../src/ExecuteTest.h" /> <Unit filename="../src/MemoryOutStream.cpp" /> <Unit filename="../src/MemoryOutStream.h" /> <Unit filename="../src/Posix/SignalTranslator.cpp"> <Option target="Debug(linux)" /> <Option target="Release(linux)" /> </Unit> <Unit filename="../src/Posix/SignalTranslator.h"> <Option target="Debug(linux)" /> <Option target="Release(linux)" /> </Unit> <Unit filename="../src/Posix/TimeHelpers.cpp"> <Option target="Debug(linux)" /> <Option target="Release(linux)" /> </Unit> <Unit filename="../src/Posix/TimeHelpers.h"> <Option target="Debug(linux)" /> <Option target="Release(linux)" /> </Unit> <Unit filename="../src/ReportAssert.cpp" /> <Unit filename="../src/ReportAssert.h" /> <Unit filename="../src/Test.cpp" /> <Unit filename="../src/Test.h" /> <Unit filename="../src/TestDetails.cpp" /> <Unit filename="../src/TestDetails.h" /> <Unit filename="../src/TestList.cpp" /> <Unit filename="../src/TestList.h" /> <Unit filename="../src/TestMacros.h" /> <Unit filename="../src/TestReporter.cpp" /> <Unit filename="../src/TestReporter.h" /> <Unit filename="../src/TestReporterStdout.cpp" /> <Unit filename="../src/TestReporterStdout.h" /> <Unit filename="../src/TestResults.cpp" /> <Unit filename="../src/TestResults.h" /> <Unit filename="../src/TestRunner.cpp" /> <Unit filename="../src/TestRunner.h" /> <Unit filename="../src/TestSuite.h" /> <Unit filename="../src/TimeConstraint.cpp" /> <Unit filename="../src/TimeConstraint.h" /> <Unit filename="../src/TimeHelpers.h" /> <Unit filename="../src/UnitTest++.h" /> <Unit filename="../src/Win32/TimeHelpers.cpp"> <Option target="Debug" /> <Option target="Release" /> </Unit> <Unit filename="../src/Win32/TimeHelpers.h"> <Option target="Debug" /> <Option target="Release" /> </Unit> <Unit filename="../src/XmlTestReporter.cpp" /> <Unit filename="../src/XmlTestReporter.h" /> <Extensions> <envvars /> <code_completion /> <debugger /> </Extensions> </Project> </CodeBlocks_project_file>


Some things to note about the content of the project file, the power of Code::Blocks.

  • there are 4 targets : Debug/Release for Windows and Debug/Release for linux [yes : 1 project file that allows building on linux and on windows]
  • some files are only part of the linux targets, some only for the windows targets

But before we build, let's modify some source files (not really needed, but I prefer to have warning free compilations). These are the changes I carried out :

  • ReportAssert.cpp : #include "ReportAssert.h"
  • win32/TimeHelpers.cpp : first the m_threadHandle and then the m_startTime in the initializerlist

Next open up this project file in Code::Blocks and build the debug and release target. From now one we will only use the Release target, we go for speed ;-)

The result of the build process will show up in subdirectories of the new 'Deliv' subdirectory of <testPit>/UnitTest++ : <testPit>/UnitTest++/Deliv/Release/libUnitTest++.a

Alright, we have build the unit test framework in a platform independent way, no silly makefiles, no obscure M$ IDE, good old super Code::Blocks. It is time to write some code so we have something to unit test.

In the first example we will create a simple free function that checks if a a give year is a leap your or not.

The business logic is as follows : a year is a leap year when it can be divided by 4, unless itcan be divided by 100. But in case it can be divided by 400, then again it is a leap year.

In the next section we will first setup a Code::Blocks project containing both the 'functionality files, and the unit test files. Typically one will package several functionality files into a static library and create a unit test project that tests all the class/functionalities in that library package, also called a component. That is we have a component (whose deliverable is the static library, and we have a unit test project that tests the entire component).


Our first test project environment

Let's create a new directory in our <TestPit> : <TestPit>/LeapYear. For the simplicity we will put the project file and the source file all together in this directory. Normally we would work in a more structured way, but in a demo article we have some artistic freedom ;-) Next you can create with the Code::Block's wizard a new console project in the LeapYear directory. Or you can create the following 4 files and give the the contents as specified below. Note that this demo works on linux and on windows, everything is still completely platform independent. Long Live Code::Blocks.

The project file : LeapYear.cbp (the one below already contains the 3 source files, in case you use the wizard you have to add the LeapYear.h/cpp files to the project)

   <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
   <CodeBlocks_project_file>
       <FileVersion major="1" minor="6" />
   	<Project>

<Option title="LeapYear" /> <Option pch_mode="2" /> <Option compiler="gcc" /> <Build> <Target title="Debug"> <Option output="Deliv/Debug/LeapYear" prefix_auto="1" extension_auto="1" /> <Option object_output="Deliv/Debug/" /> <Option type="1" /> <Option compiler="gcc" /> <Compiler> <Add option="-g" /> </Compiler> </Target> <Target title="Release"> <Option output="Deliv/Release/LeapYear" prefix_auto="1" extension_auto="1" /> <Option object_output="Deliv/Release/" /> <Option type="1" /> <Option compiler="gcc" /> <Compiler> <Add option="-O2" /> </Compiler> <Linker> <Add option="-s" /> </Linker> </Target> </Build> <Compiler> <Add option="-Wall" /> <Add option="-fexceptions" /> </Compiler> <Unit filename="LeapYear.cpp" /> <Unit filename="LeapYear.h" /> <Unit filename="main.cpp" /> <Extensions> <envvars /> <code_completion /> <lib_finder disable_auto="1" /> <debugger /> </Extensions> </Project>

   </CodeBlocks_project_file>


The main source file : main.cpp

   int main()
   {
       return 0;
   }

The header exporting our powerful function : LeapYear.h

   #ifndef LEAPYEAR_H_INCLUDED 
   #define LEAPYEAR_H_INCLUDED
   
   #endif // LEAPYEAR_H_INCLUDED

And the implementation of the leap year logic : LeapYear.cpp

   #include "LeapYear.h"


We can already build the project, we have a nice little program that does .... nothing.

Time to move to the next section, a little word about Test Driven Development.


Test Driven Development

This is a practice where new code is only added to make a failing test pass. And one only adds the code needed to make that test pass. Once that is done, a new test is added to will check the next functionality to add. On this really simple example we will more or less practice some TDD.

So get ready, it's time for LeapYear-Step1.


LeapYear-Step1

We will turn our main.cpp into the guy in charge to carry out the tests, all we need to do is change the contents of main.cpp into this :

   #include "UnitTest++.h"
   
   int main()
   {
       return UnitTest::RunAllTests();
   }

And add ../UnitTest++/src to the compiler search directories on the project level of our LeapYear project. And add ../UnitTest++/Deliv/Release/libUnitTest++.a to the link libraries of the project

Why : well we need to link with the framework and we need to include the UnitTest++ header so we can use it's functionalities.

The line UnitTest::RunAllTests() will make sure all test encountered in our sources will be carried out.