找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 4290|回复: 0

如何使用UnitTest++ libaries

[复制链接]
发表于 2012-1-1 00:01:09 | 显示全部楼层 |阅读模式
Money, a step by step example for Visual Studio .NET 2005Setting up your project (Visual Studio .NET 2005)Compiling and installing UnitTest++ libaries
  • Download the UnitTest++ source from here:  UnitTest++ on SourceForge.net (I unzipped it on my local drive at d:\Dev\UnitTest++\)
  • In this folder, open UnitTest++.vsnet2005.sln which will open the UnitTest++ project AND the TestUnitTest++ project which tests UnitTest++!  (what a concept).
  • Build it and you will see that it builds both projects AND automatically runs the unit tests.  The output of this build will be UnitTest++.vsnet2005.lib which you will need to reference later in your unit test application.
Getting started

In this section, you’ll be creating a new test application project and a new dll project as you will probably do in your development environment.


Create a new console test application project:




Let's link UnitTest++ library to our project. In the project settings you’ll need to add the UnitTest++ headers to your includes:



Then, you’ll need to add the UnitTest++ library location to your project (I just dropped the library in the test app folder.)  Now add the UnitTest++ library itself:



The last thing we’re going to do is to setup the project to automatically run our unit tests during a build.  In the Post-Build Event property page, add the $(TargetPath) to the Command Line and a Description:



Hello UnitTest++!

Now, go to your MoneyTestApp.cpp main function and add make it look like this:


#include "stdafx.h"


#include "UnitTest++.h"


TEST( HelloUnitTestPP )

{

   CHECK( false );

}


int main( int, char const *[] )

{

   return UnitTest::RunAllTests();

}


Hit the build button and notice that you have post-build-event errors.  Should look like this if you have cool colors like me ;-)  You can double-click on the first error and it will take you to the test which failed.



That was your Hello UnitTest++ example…talk about simplicity itself.


Money example using UnitTest++!

Hello UnitTest++ gets you on the right track.  Let’s go through something that’s more like a real-world example.


Our first tests

To begin, let’s create our new Money project within our current solution.  Choose File->Add->New Project…




Your solution should now look like this:



Add our new Money class to the MoneyDLL project:



Go ahead and permanently delete the Money.cpp implementation file since we’ll be doing everything in this sample within the header file itself.  Make your Money.h file look like this:


#ifndef MONEY_H

#define MONEY_H


#include <string>

#include <stdexcept>


class Money

{

public:

   Money(void){};

};


#endif


Now we’re ready to write our first real test. A test is usually decomposed in three parts:

  • setting up datas used by the test
  • doing some processing based on those datas
  • checking the result of the processing

Test-Driven Development always starts with your test code first!  All of our test code will be written in MoneyTestApp.cpp.


Let’s test the constructor first.  First, add include “Money.h” to the MoneyTestApp.  We’ll also need to add the location of the Money project and it’s .dll location to our MoneyTestApp project.  I created a /bin folder within the Money project folder for the Money project output and used that as my additional library location.



Now, replace the HelloUnitTestPP with this code and your MoneyTestApp should now look like this:


#include "stdafx.h"


#include "UnitTest++.h"

#include "Money.h"


TEST( TestConstructorNumber )

{

   // setup

   const std::string _currencyFF = "FF";

   const float _floatNumber123 = 12345678.90123f;


   // create money object

   Money money( _floatNumber123, _currencyFF );


   // test

   CHECK_CLOSE( _floatNumber123, money.getAmount(), 0.01f );

}


int main( int, char const *[] )

{

   return UnitTest::RunAllTests();

}


Build your solution and you should get an error like this:



Since you haven’t added any implementation for Money yet (and rightly so) it fails.  Make the Money.h file look like the following code.  We’re adding a constructor and the getAmount() code:

Money.h

#ifndef MONEY_H

#define MONEY_H


#include <string>


class Money

{

private:

   float m_amount;


public:

   Money( float amount, std::string currency )

      : m_amount( m_amount ) // this is a bug

   {

   }


   float getAmount() const

   {

      return m_amount;

   }

};


#endif


Build your solution.  Hummm, we have a failure!


Error  1      error: Failure in TestConstructorNumber: Expected 12345679.000000 +/- 0.010000 but was -107374176.000000    d:\dev\money\moneytestapp\moneytestapp.cpp      19   


Within MoneyTestApp.cpp, the CHECK_CLOSE() macro allows us to check floats within a tolerance and outputs the amount and the expected amount if they are not within the tolerance given.


Double-click on the error, and we jump to the assertion that checks the amount of the constructed money object.  The report indicates that amount is not equal to expected value. There are only two ways for this to happen: the member was badly initialized or we returned the wrong value. After a quick check, we find out it is the former. Let's fix that:

Money.h

Money( float amount, std::string currency )

      : m_amount( amount )

{

}


Compile. Our test finally passes!  Go to the ‘output’ window and take a look:


------ Build started: Project: MoneyTestApp, Configuration: Debug Win32 ------

Compiling...

MoneyTestApp.cpp

Linking...

Embedding manifest...

Performing Unit Tests...

Success: 1 tests passed.

Test time: 0.00 seconds.

Build log was saved at "file://d:\Dev\Money\MoneyTestApp\Debug\BuildLog.htm"

MoneyTestApp - 0 error(s), 0 warning(s)

========== Build: 1 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========


Notice that you see the ‘Performing Unit Tests…’ from the post-build description, the number of tests run and the amount of time all of the tests took to run.  Pretty cool for such a tiny amount of test code!

Adding more testsTesting currency

Let’s now add the currency tests.  First, our new test and compile:


MoneyTestApp.cpp


TEST( TestConstructorCurrency )

{

   // setup

   const std::string _currencyFF = "FF";

   const float _floatNumber123 = 12345678.90123f;


   // create money object

   Money money( _floatNumber123, _currencyFF );


   // test

   CHECK( money.getCurrency() == _currencyFF );

}


We get and error saying there’s no getCurrency() method.  Let’s make one:


Money.h


class Money

{

private:

   float m_amount; std::string m_currency;


public:

   Money( float amount, std::string currency )

      : m_amount( amount ), m_currency( currency )

   }


   float getAmount() const

   {

      return m_amount;

   }


   std::string getCurrency() const

   {

      return "bogus";

   }

};


Build and you obviously get an error since your getCurrency() method is returning ‘bogus’ instead of m_currency.  This is another part of TDD, make the test fail the first time, then make it pass.  Fix the getCurrency() method to return m_currency and the test should now pass.  You should now be starting to get the idea of TDD and how very simple it is to add new unit tests using UnitTest++.

Refactoring and Test Fixtures

By now you can see some common code in the 2 unit tests.  How can we easily reuse this code?  By using test fixtures.  Listed below are the 2 refactored unit tests:


MoneyTestApp.cpp


TEST( TestConstructorNumber )

{

   // setup

   const std::string _currencyFF = "FF";

   const float _floatNumber123 = 12345678.90123f;


   // create money object

   Money money( _floatNumber123, _currencyFF );


   // test

   CHECK_CLOSE( _floatNumber123,

                money.getAmount(),

                0.01f );

}


TEST( TestConstructorCurrency )

{

   // setup

   const std::string _currencyFF = "FF";

   const float _floatNumber123 = 12345678.90123f;


   // create money object

   Money money( _floatNumber123, _currencyFF );


   // test

   CHECK( money.getCurrency() == _currencyFF );

}

struct ConstructorFixture

{

   ConstructorFixture() : _currencyFF( "FF" ),

      _floatNumber123( 12345678.90123f )

   {

   };


   const std::string _currencyFF;

   const float _floatNumber123;

};


TEST_FIXTURE( ConstructorFixture, TestConstructorNumber )

{

   Money money( _floatNumber123, _currencyFF );


   CHECK_CLOSE( _floatNumber123,

                money.getAmount(),

                0.01f );

}


TEST_FIXTURE( ConstructorFixture, TestConstructorCurrency )

{

   Money money( _floatNumber123, _currencyFF );


   CHECK( money.getCurrency() == _currencyFF );

}



The struct ConstructorFixture is a common class which can be reused by TEST_FIXTUREs by passing the class name as the first parameter to the TEST_FIXTURE.  The TEXT_FIXTUREs can now reuse the member variables declared within the ConstructorFixture...brilliant!  Hilighted is the common code which will be refactored into a struct for use in our TEST_FIXTUREs.  Replace the 2 test cases on the left with the code on the right in your test application and build.

Testing for equality                 

Now we want to check if 2 Money objects are equal.  Let's start by adding a new test then add our methods.  To do this, I’m going to create a new fixture class and a new TEST_FIXTURE:


MoneyTestApp.cpp


struct OperatorFixture

{

   OperatorFixture() : _currencyFF( "FF" ),

                       _currencyUSD( "USD" ),

                       _floatNumber12( 12.0f ),

                       _floatNumber123( 123.0f )

   {};


   const std::string _currencyFF;

   const std::string _currencyUSD;

   const float _floatNumber12;

   const float _floatNumber123;

};


TEST_FIXTURE( OperatorFixture, TestEqual )

{

   Money money12FF( _floatNumber12, _currencyFF );

   Money money123USD( _floatNumber123, _currencyUSD );


   CHECK( money12FF == money12FF );

   CHECK( money123USD == money123USD );

}


Build your solution and it will tell you there’s no == operator defined in Money.  Let’s do that now.  Add the following code to your Money.h file:


Money.h


…  

bool operator ==( const Money &other ) const

   {

      return false;  // this will fail

   }


Remember, you want the test to fail the first time; that’s the only way you know that the test written will return a failure.  Build the project and note the error.  Now you can go back and change the guts of the operator function to this and rebuild:


Money.h


return m_amount == other.m_amount  &&

   m_currency == other.m_currency;


We’re trying to follow the TDD (red, green & refactor) policy explicitly to ensure accurate passes and failures of our unit tests.  Let’s add another unit test for inequality:


MoneyTestApp.cpp


TEST_FIXTURE( OperatorFixture, TestNotEqual )

{

   Money money12FF( _floatNumber12, _currencyFF );

   Money money123FF( _floatNumber123, _currencyFF );

   Money money12USD( _floatNumber12, _currencyUSD );

   Money money123USD( _floatNumber123, _currencyUSD );


   CHECK( money12FF != money123FF );

   CHECK( money12FF != money12USD );

   CHECK( money12USD != money123USD );

}


Build and note the failure.  Now add the implementation:


Money.h


bool operator !=( const Money &other ) const

{

   return (*this == other); // is going to fail

}


We should receive 3 new errors for the 3 test failures, excellent!  Time to fix and rebuild:


Money.h


bool operator !=( const Money &other ) const

{

   return !(*this == other);

}

Adding Money

Let's add some adding tests to MoneyTestApp. You know the routine...


MoneyTestApp.cpp


TEST( TestAdd )

{

   // setup

   Money money12FF( 12, "FF" );

   Money expectedMoney( 135, "FF" );


   // process

   Money money( 123, "FF" );

   money += money12FF;


   // check

   CHECK_EQUAL( expectedMoney.getAmount(), money.getAmount() );

   CHECK( money.getAmount() == expectedMoney.getAmount() );  // less information

}


Build and note the failures.  While writing that test case, you may ask yourself, “what is the result of adding money objects of different currencies?”  Obviously this is an error and it should be reported by throwing an exception, say IncompatibleMoneyError, when the currencies are not the same.  We will write another test case for this later.  For now let’s get our testAdd case working:


Money.h


Money &operator +=( const Money &other )

{

   m_amount = 9876.54321f;  // this will fail

   return *this;

}


Compile and build.  Notice that in the TestAdd test, there’s 2 checks which look the same, but use different functions to test.  This is shows you the difference between the CHECK and the CHECK_EQUAL functions.  The CHECK function gives you a simple failure, whereas the CHECK_EQUAL will give you actual results andexpected results in the failure as highlighted below:


Performing Unit Tests...

d:\dev\money\moneytestapp\moneytestapp.cpp(84): error: Failure in TestAdd: Expected 9876.542969 but was 135.000000

d:\dev\money\moneytestapp\moneytestapp.cpp(85): error: Failure in TestAdd: money.getAmount() == expectedMoney.getAmount()

FAILURE: 2 out of 5 tests failed.

Test time: 0.00 seconds.


Time to fix the += operator:


Money.h


Money &operator +=( const Money &other )

{

   m_amount += other.m_amount;

   return *this;

}


Let's implement the incompatible money test exception case before we forget about it...  That test case should expect an IncompatibleMoneyError exception to be thrown.  UnitTest++ can test that for us as well, but you need to specify that the test case should expect an exception to be thrown.  To do this, use the CHECK_THROW function which takes an expression (which should throw an exception) and the exception class name that should be thrown:


MoneyTestApp.cpp


TEST( TestIncompatibleMoneyError )

{

   // just test throwing an exception

   CHECK_THROW( throw IncompatibleMoneyError(), IncompatibleMoneyError );

}


Compile to see that the IncompatibleMoneyError exception is not yet implemented and add it to the Money class:


Money.h


class IncompatibleMoneyError : public std::runtime_error

{

public:

   IncompatibleMoneyError() : runtime_error( "Incompatible moneys" )

   {

   }

};


class Money

{

private:


Build your project successfully.  Let’s add another exception test that really tests our exception functionality:


MoneyTestApp.cpp


TEST_FIXTURE( OperatorFixture, TestAddThrow )

{

   Money money123FF( _floatNumber123, _currencyFF );

   Money money123USD( _floatNumber123, _currencyUSD );


   CHECK_THROW( money123FF += money123USD, IncompatibleMoneyError )

}


Compile:


Performing Unit Tests...

d:\dev\money\moneytestapp\moneytestapp.cpp(99): error: Failure in TestAddThrow: Expected exception: "IncompatibleMoneyError" not thrown

FAILURE: 1 out of 7 tests failed.

Test time: 0.00 seconds.


Very nice indeed.  We have not yet added the exception throwing logic to our += operator in our Money class, so no exception was thrown which is an error.  Let’s implement our exception handling in the += operator:


Money.h


   Money &operator +=( const Money &other )

   {

      if ( m_currency != other.m_currency )

      {

         throw IncompatibleMoneyError();

      }


      m_amount += other.m_amount;

      return *this;

   }


Build the project and it should now succeed.  For the sake of learning one more test function, we’re going to do a timed, performance test.  This one’s a little trickier; add the following code:


MoneyTestApp.cpp


#include "TimeConstraint.h"

#include "TestResults.h"

TEST( TimeConstraintAdd )

{

   UnitTest::TestResults result;

   {

      UnitTest::TimeConstraint t( 10, result, "", 0, "" );


      Money money12FF( 12, "FF" );

      Money expectedMoney( 135, "FF" );


      Money money( 123, "FF" );

      money += money12FF;


      UnitTest::TimeHelpers::SleepMs(20);  // artificially force failed test

   }


   CHECK_EQUAL( 0, result.GetFailureCount() );

}


The UnitTest::TimeHelpers::SleepMs() function allows you to artificially slow down the test so that it fails.  The first parameter in the UnitTest::TimeConstraint constructor is the milliseconds in which the processing should complete, so I’m adding a 20ms delay so that it fails.  Build and note the following failure:


Performing Unit Tests...

d:\dev\money\moneytestapp\moneytestapp.cpp(118): error: Failure in TimeConstraintAdd: Expected 0 but was 1

FAILURE: 1 out of 8 tests failed.

Test time: 0.02 seconds.

Project : error PRJ0019: A tool returned an error code from "Performing Unit Tests..."


Now you can comment this delay and the test should pass.


Files

Visual Studio project files.

Other Links

Listed below are some other helpful links for UnitTest++:

A little history on UnitTest++ from Noel Llopis

UnitTest++ Homepage

Credits

This article was written by Matthew Granic of Medical Simulation Corporation®.  Inspired from many others, especially Noel Llopis and all the TDD newbies around the net (like me) who are looking for 'Hello World' and ‘Money’ examples of UnitTest++ J

您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-28 14:42 , Processed in 0.020375 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表