This tutorial will guide you through the process of creating a new project with CxxTest capabilities, adding a new class and test suites for the class to the project, and then how to run the tests and interpret the results.

Preparing the Visual Studio Environment

Before you begin using the CxxTest for Visual Studio features, you should prepare your environment by making sure that the CxxTest tool windows are visible.

Our CxxTest package adds two windows to the Visual Studio environment: the CxxTest Suites window, which displays the test suites and test cases available in the loaded solution, and the CxxTest Results window, which displays the success and failure results of your tests after execution.

If this is the first time you're using Visual Studio since installing the CxxTest package, these windows may not be visible. To open them, select View / Other Windows / CxxTest Suites and View / Other Windows / CxxTest Results from the menu. Once they are visible, you may dock them wherever you find them convenient in the main Visual Studio window.

Creating a New CxxTest Project

To create a CxxTest project in Visual Studio, select File / New / Project... from the main menu, then click on Visual C++ in the "Project types" tree on the left. (If Visual C++ is not visible, then you may need to expand a section called Other Languages.)

If the CxxTest package was properly installed, you should see an item in the "Templates" list called Empty Console Project w/ CxxTest. Select this option and enter the name of the new project in the field labeled "Name:" – for this tutorial, enter BankAccount. Make sure that "Create new Solution" is selected in the "Solution" field, that "Create directory for solution" is checked, and that the "Solution Name" is also set to BankAccount. Finally, click OK.

Creating the Class to Test

The new solution and project should appear inside the Solution Explorer tab of the Visual Studio window. Before we can start writing test cases, clearly we need a class that we want to test. For this tutorial, consider a simple bank account class that holds a name and a dollar amount, and supports the following operations:

The bank account class we will be using is very limited. We store an integer dollar amount instead of dollars and cents or fractions of a dollar, because a floating-point type would introduce advanced issues when checking for equality that we wish to avoid for the purposes of this tutorial. (Your C++ text should have more information on floating-point types and the comparison problems that are associated with them.)

Let's begin by adding the BankAccount class that we wish to use in our tests. With the Solution Explorer active, right click on the Header Files folder in the project and choose Add / New Item... from the menu.

In the dialog box that appears, select Visual C++ / Code from the "Categories" tree, then select Header File (.h) in the "Templates" list. In the "Name" field, enter BankAccount.h and click Add.

This has created a new BankAccount.h file in the main directory of our project, and the empty file was automatically opened in the editor. Add the interface for our bank account class by copying and pasting the code below into the new file.

   1 // BankAccount.h
   2 
   3 #ifndef BANKACCOUNT_H_
   4 #define BANKACCOUNT_H_
   5 
   6 #include <string>
   7 
   8 class BankAccount
   9 {
  10 private:
  11     std::string accountHolder;
  12     int balance;
  13     
  14 public:
  15     // Constructors
  16     BankAccount( const char* holder, int initialBalance = 0 );
  17 
  18     // Accessors
  19     int getBalance() const;
  20     bool isOverdrawn() const;
  21 
  22     // Modifiers
  23     void deposit( int value );
  24     void withdraw( int value );
  25 };
  26 
  27 #endif // BANKACCOUNT_H_

Save this file. Next, we need to add the implementation of these methods. Create a new source file in the project by right-clicking on the Source Files folder of the project and choosing Add / New Item... from the menu. In the dialog box that appears, select Visual C++ / Code from the "Categories" tree, then select C++ File (.cpp) in the "Templates" list. In the "Name" field, enter BankAccount.cpp and click Add.

Copy the following code into the new file.

   1 // BankAccount.cpp
   2 
   3 #include "BankAccount.h"
   4 
   5 #include <string>
   6 
   7 using namespace std;
   8 
   9 // Initializes a new account with the specified account holder name
  10 // and initial balance.  If the initial balance is omitted, it defaults
  11 // to zero.
  12 BankAccount::BankAccount( const char* holder, int initialBalance )
  13 {
  14     accountHolder = holder;
  15     balance = initialBalance;
  16 }
  17 
  18 // Returns the current balance in the account.
  19 int BankAccount::getBalance() const
  20 {
  21     return balance;
  22 }
  23 
  24 // Returns true if the account is overdrawn (has a negative balance);
  25 // otherwise, it returns false.
  26 bool BankAccount::isOverdrawn() const
  27 {
  28     return (balance < 0);
  29 }
  30 
  31 // Adds the specified amount to the balance in the account.
  32 void BankAccount::deposit( int value )
  33 {
  34     balance += value;
  35 }
  36 
  37 // Subtracts the specified amount from the balance in the account.
  38 void BankAccount::withdraw( int value )
  39 {
  40     balance -= value;
  41 }

Save this file. We now have a completed BankAccount class that we can test.

Creating the Test Cases

To create our test cases, we need to add a new .h header file to our project that will contain a special class that derives from one in the CxxTest library. A special wizard is provided by the CxxTest package to create test suites; to access it, right-click on the header file that contains the class that you wish to test – in our case, BankAccount.h – and choose Generate Test Suite... from the menu.

The wizard that appears will have acceptable defaults already entered in the fields, based on the file that was selected. Click Finish to dismiss the wizard and create the new test suite. (If you click Next instead, you will be presented with a tree view of all the classes and methods that are declared in the header file that you chose to test. This allows you to automatically generate placeholders for all of the functions that you wish to write test cases for. For the purposes of this tutorial, we will be writing test cases manually.)

The newly generated test suite file should look like the following snippet:

   1 #ifndef BANKACCOUNTTESTS_H_
   2 #define BANKACCOUNTTESTS_H_
   3                 
   4 #include <cxxtest/TestSuite.h>
   5 
   6 #include "BankAccount.h"
   7 
   8 class BankAccountTests : public CxxTest::TestSuite
   9 {
  10 public:
  11 };
  12 
  13 #endif /* BANKACCOUNTTESTS_H_ */

There are several points of interest here. We include the <cxxtest/TestSuite.h> header to make available the definition of the CxxTest::TestSuite class, from which all test suite classes must inherit. Additionally, we must obviously include the header for the BankAccount class that we are testing.

Before we begin to add test cases to this class, there are a couple coding conventions that you must follow when writing test cases. A method that is intended to be executed as a test case must have a name that begins with test, a return type of void, and takes no argumens. If you do not follow these rules, the function will not be recognized as a test case by CxxTest and it will not be executed.

Now we will add a series of methods to the class that perform various operations and tests. First we will create a simple test that verifies that the BankAccount constructor works as expected. Add the following method under the public modifier:

   1         void testInitialBalances()
   2         {
   3             // An account initialized with a name only should start
   4             // empty.
   5             BankAccount emptyAccount( "Empty Account" );
   6             TS_ASSERT_EQUALS( emptyAccount.getBalance(), 0 );
   7     
   8             // An account initialized with a balance should obviously
   9             // have that balance.
  10             BankAccount nonemptyAccount( "Nonempty Account", 12345 );
  11             TS_ASSERT_EQUALS( nonemptyAccount.getBalance(), 12345 );
  12         }

This method creates a new BankAccount object, first with the balance omitted, so it should default to zero. We use the special TS_ASSERT_EQUALS directive from CxxTest to verify this. TS_ASSERT_EQUALS takes two parameters and verifies that both are equal.

The second half of the function creates an account with an initial balance and verifies that the balance was set properly.

Notice that we defined this method inside the header file, instead of creating a separate source file to contain the method bodies. This is an example of inlining a function, and we do it here for simplicity – typically when writing test cases, there is no need to introduce extra complexity into the project by separating them into .h and .cpp files.

Save this file and activate the CxxTest Suites tool window.

Click the "Refresh Tests from Solution" button in its toolbar and it should display a tree containing the test suite class BankAccountTests and its single test case, testInitialBalances. Ensure that each is checked, then click the "Run Selected Tests" button in the tool window's toolbar. This will cause the solution to be built and the tests executed. If everything performed properly, the CxxTest Results tool window will appear as shown to the below.

The green progress bar indicates that all tests executed successfully. The BankAccountTests test suite is collapsed because only tests with errors are expanded by default.

We can now add a few more test cases to the file. Try adding the following methods:

   1         void testDeposits()
   2         {
   3             // Start Joe out poor, with $0.00.
   4             BankAccount account( "Joe Hokie" );
   5     
   6             // Deposit $5.00 in his account.
   7             account.deposit( 5 );
   8     
   9             // Hopefully the bank is keeping track of his money properly.
  10             TS_ASSERT_EQUALS( account.getBalance(), 5 );
  11     
  12             // Have Joe deposit a little more, and verify.
  13             account.deposit( 100 );
  14             TS_ASSERT_EQUALS( account.getBalance(), 105 );
  15         }
  16 
  17         void testWithdrawals()
  18         {
  19             // Start Francois out moderately wealthy.
  20             BankAccount account( "Francois Hokie", 5000 );
  21     
  22             // Francois is a big spender, who makes several withdrawals.
  23             account.withdraw( 100 );
  24             account.withdraw( 200 );
  25             account.withdraw( 300 );
  26             account.withdraw( 400 );
  27             account.withdraw( 500 );
  28 
  29             // He should have $3500 left now.
  30             TS_ASSERT_EQUALS(account.getBalance(), 3500);
  31         }
  32 
  33         void testVariousActivity()
  34         {
  35             // Start Sabrina out with $75, and then monitor her
  36             // account over some deposits and withdrawals.
  37             BankAccount account( "Sabrina Hokie", 75 );
  38     
  39             account.deposit( 50 );
  40             TS_ASSERT_EQUALS( account.getBalance(), 125 );
  41 
  42             account.withdraw( 10 );
  43             TS_ASSERT_EQUALS( account.getBalance(), 115 );
  44     
  45             account.withdraw( 42 );
  46             TS_ASSERT_EQUALS( account.getBalance(), 73 );
  47     
  48             account.deposit( 534 );
  49             TS_ASSERT_EQUALS( account.getBalance(), 607 );
  50         }

So far we have only used the TS_ASSERT_EQUALS directive in our tests. CxxTest supports many others, which you can find in its user guide. The ones that will be most relevant to you in this course are:

Let's create a test case that uses some of these.

   1         void testOverdrawn()
   2         {
   3             // Hiroyuki isn't very responsible with his money, so
   4             // he is going to withdraw more than he has available.
   5             BankAccount account( "Hiroyuki Hokie", 115 );
   6             account.withdraw( 200 );
   7     
   8             TS_ASSERT_LESS_THAN( account.getBalance(), 0 );
   9             TS_ASSERT_EQUALS( account.getBalance(), -85 );
  10             TS_ASSERT( account.isOverdrawn() );
  11     
  12             // Later, a Wall Street financial expert takes Hiroyuki
  13             // under his wing and teaches him how to properly manage
  14             // his funds.  He makes a large deposit to get his life
  15             // back on track.
  16             account.deposit( 1000 );
  17     
  18             TS_ASSERT_LESS_THAN( 0, account.getBalance() );
  19             TS_ASSERT_EQUALS( account.getBalance(), 915 );
  20             TS_ASSERT( !account.isOverdrawn() );
  21         }

Save the file and run the tests again. If you have copied all of these properly, Visual Studio should report that five tests ran successfully. But what if a test case failed? Let's create one that is designed to fail in order to see the results. The following test case contains a mathematical error in its assertion:

   1         void testBadCase()
   2         {
   3             BankAccount account( "Bad Test Case", 5 );
   4             account.deposit( 7 );
   5 
   6             // To my knowledge, 5 + 7 != 13.
   7             TS_ASSERT_EQUALS( account.getBalance(), 13 );
   8         }

When this test is executed, the CxxTest Results tool window will display a test failure.

You can also double-click on the item in "Details" labeled "Failed assertion" and Visual Studio will open the source file and highlight the line containing the particular assertion that caused the test case to fail.