0% found this document useful (0 votes)
60 views5 pages

Namespace Public Class Private Float Public Void Float: (Testfixture)

The document describes testing an Account class using NUnit. It starts with the Account class that supports deposit, withdraw, and transfer methods. A test class AccountTest is created with a test method TransferFunds to test the TransferFunds method. The test initially fails because TransferFunds is not implemented. After implementing TransferFunds, the test passes. Additional tests are added to test for exceptions and transactional behavior. Tests are marked to indicate expected exceptions or ignored tests. The tests are run using the NUnit GUI and pass or fail as expected. Refactoring extracts common initialization into a SetUp method.

Uploaded by

Arun Kumar
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
60 views5 pages

Namespace Public Class Private Float Public Void Float: (Testfixture)

The document describes testing an Account class using NUnit. It starts with the Account class that supports deposit, withdraw, and transfer methods. A test class AccountTest is created with a test method TransferFunds to test the TransferFunds method. The test initially fails because TransferFunds is not implemented. After implementing TransferFunds, the test passes. Additional tests are added to test for exceptions and transactional behavior. Tests are marked to indicate expected exceptions or ignored tests. The tests are run using the NUnit GUI and pass or fail as expected. Refactoring extracts common initialization into a SetUp method.

Uploaded by

Arun Kumar
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOC, PDF, TXT or read online on Scribd
You are on page 1/ 5

Let’s start with a simple example.

Suppose we are writing a bank application and we have a basic


domain class – Account. Account supports operations to deposit, withdraw, and transfer funds.
The Account class may look like this:

namespace bank
{
public class Account
{
private float balance;
public void Deposit(float amount)
{
balance+=amount;
}

public void Withdraw(float amount)


{
balance-=amount;
}
public void TransferFunds(Account destination, float amount)
{
}

public float Balance


{
get{ return balance;}
}
}
}

Now let’s write a test for this class – AccountTest. The first method we will test is TransferFunds.

namespace bank
{
using NUnit.Framework;

[TestFixture]
public class AccountTest
{
[Test]
public void TransferFunds()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);

source.TransferFunds(destination, 100.00F);
Assert.AreEqual(250.00F, destination.Balance);
Assert.AreEqual(100.00F, source.Balance);
}
}

The first thing to notice about this class is that it has a [TestFixture] attribute associated with
it – this is the way to indicate that the class contains test code (this attribute can be inherited). The
class has to be public and there are no restrictions on its superclass. The class also has to have a
default constructor.
The only method in the class – TransferFunds, has a [Test] attribute associated with it – this is
an indication that it is a test method. Test methods have to return void and take no parameters. In
our test method we do the usual initialization of the required test objects, execute the tested
business method and check the state of the business objects. The Assert class defines a
collection of methods used to check the post-conditions and in our example we use the
AreEqual method to make sure that after the transfer both accounts have the correct balances
(there are several overloadings of this method, the version that was used in this example has the
following parameters : the first parameter is an expected value and the second parameter is the
actual value).

Compile and run this example. Assume that you have compiled your test code into a bank.dll.
Start the NUnit Gui (the installer will have created a shortcut on your desktop and in the
“Program Files” folder), after the GUI starts, select the File->Open menu item, navigate to the
location of your bank.dll and select it in the “Open” dialog box. When the bank.dll is loaded you
will see a test tree structure in the left panel and a collection of status panels on the right. Click
the Run button, the status bar and the TransferFunds node in the test tree turn red – our test has
failed. The “Errors and Failures” panel displayed the following message – “TransferFunds :
expected <250> but was <150>” and the stack trace panel right below it reported where in the test
code the failure has occurred – "at bank.AccountTest.TransferFunds() in
C:\nunit\BankSampleTests\AccountTest.cs:line 17”

That is expected behavior; the test has failed because we have not implemented the
TransferFunds method yet. Now let’s get it to work. Don’t close the GUI and go back to your
IDE and fix the code, make your TransferFunds method look like this:

public void TransferFunds(Account destination, float amount)


{
destination.Deposit(amount);
Withdraw(amount);
}

Now recompile your code and click the run button in GUI again – the status bar and the test tree
turn green. (Note how the GUI has reloaded the assembly automatically for you; we will keep the
GUI open all the time and continue working with our code in IDE and write more tests).

Let’s add some error checking to our Account code. We are adding the minimum balance
requirement for the account to make sure that banks continue to make their money by charging
your minimal overdraft protection fee. Let’s add the minimum balance property to our Account
class:

private float minimumBalance = 10.00F;


public float MinimumBalance
{
get{ return minimumBalance;}
}
We will use an exception to indicate an overdraft:

namespace bank
{
using System;
public class InsufficientFundsException : ApplicationException
{
}
}

Add a new test method to our AccountTest class:


[Test]
[ExpectedException(typeof(InsufficientFundsException))]
public void TransferWithInsufficientFunds()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);
source.TransferFunds(destination, 300.00F);
}

This test method in addition to [Test] attribute has an [ExpectedException] attribute


associated with it – this is the way to indicate that the test code is expecting an exception of a
certain type; if such an exception is not thrown during the execution – the test will fail. Compile
your code and go back to the GUI. As you compiled your test code, the GUI has grayed out and
collapsed the test tree as if the tests were not run yet (GUI watches for the changes made to the
test assemblies and updates itself when the structure of the test tree has changed – e.g. new test is
added). Click the “Run” button – we have a red status bar again. We got the following Failure :
“TransferWithInsufficentFunds : InsufficientFundsException was expected”. Let’s fix our
Account code again, modify the TransferFunds method this way:
public void TransferFunds(Account destination, float amount)
{
destination.Deposit(amount);
if(balance-amount<minimumBalance)
throw new InsufficientFundsException();
Withdraw(amount);
}

Compile and run the tests – green bar. Success! But wait, looking at the code we’ve just written
we can see that the bank may be loosing money on every unsuccessful funds Transfer operation.
Let’s write a test to confirm our suspicions. Add this test method:
[Test]
public void TransferWithInsufficientFundsAtomicity()
{
Account source = new Account();
source.Deposit(200.00F);
Account destination = new Account();
destination.Deposit(150.00F);
try
{
source.TransferFunds(destination, 300.00F);
}
catch(InsufficientFundsException expected)
{
}

Assert.AreEqual(200.00F,source.Balance);
Assert.AreEqual(150.00F,destination.Balance);
}

We are testing the transactional property of our business method – all operations are successful or
none. Compile and run – red bar. OK, we’ve made $300.00 out of a thin air (1999.com déjà vu?)
– the source account has the correct balance of 150.00 but the destination account shows :
$450.00. How do we fix this? Can we just move the minimum balance check call in front of the
updates:

public void TransferFunds(Account destination, float amount)


{
if(balance-amount<minimumBalance)
throw new InsufficientFundsException();
destination.Deposit(amount);
Withdraw(amount);
}

What if the Withdraw() method throws another exception? Should we execute a compensating
transaction in the catch block or rely on our transaction manager to restore the state of the
objects? We need to answer those questions at some point, but not now; but what do we do with
the failing test in the meantime – remove it? A better way is to temporarily ignore it, add the
following attribute to your test method
[Test]
[Ignore("Need to decide how to implement transaction management in the
application")]
public void TransferWithInsufficientFundsAtomicity()
{
// code is the same
}

Compile and run – yellow bar. Click on “Tests Not Run” tab and you will see
bank.AccountTest.TransferWithInsufficientFundsAtomicity() in the list along with the Reason
this test is ignored.

Looking at our test code we can see that some refactoring is in order. All test methods share a
common set of test objects. Let’s extract this initialization code into a setup method and reuse it
in all of our tests. The refactored version of our test class looks like this:
namespace bank
{
using System;
using NUnit.Framework;

[TestFixture]
public class AccountTest
{
Account source;
Account destination;

[SetUp]
public void Init()
{
source = new Account();
source.Deposit(200.00F);
destination = new Account();
destination.Deposit(150.00F);
}

[Test]
public void TransferFunds()
{
source.TransferFunds(destination, 100.00f);
Assert.AreEqual(250.00F, destination.Balance);
Assert.AreEqual(100.00F, source.Balance);
}

[Test]
[ExpectedException(typeof(InsufficientFundsException))]
public void TransferWithInsufficientFunds()
{
source.TransferFunds(destination, 300.00F);

[Test, Ignore("Need to decide how to implement transaction


management in the application")]
public void TransferWithInsufficientFundsAtomicity()
{
try
{
source.TransferFunds(destination, 300.00F);
}
catch(InsufficientFundsException expected)
{
}

Assert.AreEqual(200.00F,source.Balance);
Assert.AreEqual(150.00F,destination.Balance);
}
}
}

Note that Init method has the common initialization code, it has void return type, no parameters,
and it is marked with [SetUp] attribute. Compile and run – same yellow bar!

You might also like