There are several options available beyond the basic [Test] attribute. This section will provide an overview of these options.
This allows you to attach a description to a test, which will appear in the Text and Xml output.
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// A Test with a Description
[Test(Description="This was written by Homer to test the Iliad.")]
public void TestWithDescription()
{
Console.WriteLine("Test with description");
}
}
}
It's often handy to categorize a Test. This can be done using the Category feature. Within the Zanebug GUI, this allows Tests to be filtered, sorted, etc. by category.
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// A Test with a Category
[Test(Category="Some Category")]
public void TestWithCategory()
{
Console.WriteLine("Test with category");
}
}
}
MultiThreading
You can run tests in their own threads by adding the IsMultiThreaded property to the TestFixture attribute. This will cause each test in the TestFixture to be loaded and executed in its own thread. The tests will execute asynchronously (they won't run in sequence). Below is an example:
using System;
using System.Threading;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture(IsMultiThreaded=true)]
public class MultiThreadedTest
{
int i = 0;
[Test]
public void Thread1()
{
i = 0;
int a = 0;
while(a <= 10)
{
i += 4;
Console.WriteLine("Thread1: " + i.ToString());
Thread.Sleep(5);
a++;
}
Console.WriteLine("Thread1: " + i.ToString());
Assert.IsFalse(i==44, "i equals 44, which means it was not interrupted by Thread2.");
}
[Test]
public void Thread2()
{
i = 0;
int a = 0;
while(a <= 10)
{
i += 10;
Console.WriteLine("Thread2: " + i.ToString());
Thread.Sleep(5);
a++;
}
Console.WriteLine("Thread2: " + i.ToString());
Assert.IsFalse(i==110, "i equals 110, which means it was not interrupted by Thread1.");
}
}
}
Here is the output:
Thread1: 4 Thread2: 10 Thread1: 14 Thread2: 24 Thread1: 28 Thread2: 38 Thread1: 42 Thread2: 52 Thread1: 56 Thread2: 66 Thread1: 70 Thread2: 80 Thread1: 84 Thread2: 94 Thread1: 98 Thread2: 108 Thread1: 112 Thread2: 122 Thread1: 126 Thread2: 136 Thread1: 140 Thread2: 150 Thread1: 150 Thread2: 150
As you can see, both threads executed simultaneously.
Repeating MultiThreaded Tests
In some cases, you'll want to run the same test on multiple threads to see if you run into resource contention issues, deadlocks, etc. To do this, simply
Mark the TestFixture with the IsMultiThreaded property and
Add the Repeat attribute to your Test
Below is an example:
using System;
using System.Threading;
using Adapdev.UnitTest;
namespace Examples.Zanebug;
{
[TestFixture(IsMultiThreaded=true)]
public class MultiThreadedRepeatTest
{
int i = 0;
[Test, Repeat(5)]
public void ThreadedRepeat()
{
i = 0;
int a = 0;
while(a <= 10)
{
i += 1;
Thread.Sleep(5);
a++;
Console.WriteLine("Thread {0}: {1}", AppDomain.GetCurrentThreadId(), i);
}
Assert.IsFalse(i==11, "i equals 10, which means it was not interrupted by other Threads.");
}
}
}
Here is the output:
Thread 5880: 1 Thread 5884: 2 Thread 5888: 1 Thread 5880: 1 Thread 5884: 3 Thread 5876: 4 Thread 5888: 1 Thread 2720: 2 Thread 5880: 3 Thread 5884: 4 Thread 5876: 5 Thread 5880: 6 Thread 5888: 6 Thread 5884: 8 Thread 2720: 9 Thread 5876: 10 Thread 5888: 11 Thread 2720: 12 Thread 5880: 12 Thread 5884: 14 Thread 5876: 15 Thread 5880: 16 Thread 5888: 16 Thread 5884: 17 Thread 5876: 19 Thread 5884: 20 Thread 5880: 21 Thread 5876: 22 Thread 2720: 23 Thread 5888: 24 Thread 2720: 25 Thread 5884: 26 Thread 5888: 26 Thread 5880: 28 Thread 5876: 29 Thread 5884: 30 Thread 5880: 31 Thread 5876: 32 Thread 2720: 33 Thread 5888: 34 Thread 5884: 35 Thread 5880: 36 Thread 2720: 36 Thread 5876: 38 Thread 5888: 39 Thread 5884: 40 Thread 2720: 40 Thread 5888: 41 Thread 5880: 42 Thread 5876: 42 Thread 2720: 43 Thread 5888: 44 Thread 5876: 44 Thread 2720: 44
As you can see, the test executed on 5 simultaneous threads.
Both TestFixtures and Tests can have custom code run at the beginning and the end of their iterations. This is quite useful in instances where you need to setup database information, prepare a resource, etc. and then restore it to its previous state when the testing is done. A typical example would be the following scenario:
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// Runs once at the beginning of the TestFixture
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
// insert test records into database
}
// A Test
[Test]
public void SimpleTest()
{
// test code
}
// Runs once at the end of the TestFixture
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
// remove test records from database
}
}
}
In the above example, setup and teardown operations are done each time the TestFixture runs. The same can be done at the Test level. It's even possible to run setup and teardown operations only for specific Tests.
List below are the various setup and teardown attributes:
Setup
TestFixtureSetUp
TestSetUp
SetUp (same as TestSetUp - provided for NUnit compatability)
TearDown
TestFixtureTearDown
TestTearDown
TearDown (same as TestTearDown - provided for NUnit compatability)
See the API for full documentation
Test Specific SetUp and Teardown
When a SetUp or TearDown operation should only be performed for a specific Test, then the Test property should be used:
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// Runs once at the beginning of SimpleTest only
[TestSetUp("SimpleTest")]
public void SpecificTestSetUp()
{
// setup code
}
// A Test
[Test]
public void SimpleTest()
{
// test code
}
// Runs once at the end of SimpleTest only
[TestTearDown("SimpleTest")]
public void SpecificTestTearDown()
{
// teardown code
}
}
}
Often times tests are written that expect an error to occur. For example, if you have a method called Divide(int a, int b), and you pass in a 0, you would expected a DivideByZeroException. Proper testing would validate that this error is thrown. In order to do this, the ExpectedException attribute should be used:
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
[Test]
[ExpectedException(typeof(DivideByZeroException))]
public void ExpectedDivideByZeroException()
{
MyMath.Divide(12,0); // throws DivideByZeroException
}
}
}
In the above example, the Test expects a DivideByZeroException to be thrown. If the exception is thrown, the test passes. If it isn't thrown, the test fails.
The test will pass if the expected exception is thrown, or an exception that inherits from it (i.e. a child class). For instance, if the above example expected an exception type of Exception, then the Test would still pass since DivideByZeroException is a subclass of Exception.
It is possible to ignore tests at run-time. For example, you may have a lengthy, resource intesive test that you only need to run occassionaly. You can mark the Test with Ignore, and it won't be run. If you want to run it at a later period, you simply remove the Ignore attribute. This allows you to maintain the Test code without having to always run it. You can optionally provide a reason why the test is being ignored, which will be displayed at run-time.
Within the Zanebug GUI, you can override the Ignore attribute by checking a Test. When the TestSuite first loads, all Tests marked with Ignore will not be checked. They can be checked at any time if you decide to run them. Likewise, you can impose the Ignore attribute on Tests at run-time by unchecking them in the GUI.
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
[Test]
[Ignore]
public void SimpleTest1()
{
//test code
}
[Test]
[Ignore("Some reason")]
public void SimpleTest2()
{
//test code
}
}
}
When it's required that an operation only consume a certain amount of memory, then the MaxKMemory attribute should be used. This specifies how much memory should be used, and anything above that will fail.
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
[Test]
[MaxKMemory(200)]
public void SimpleTest()
{
//test code
//if this test consumes more than 200kb of memory
//then it will fail
}
}
}
This is a beta feature at this point, and is not guaranteed to perform accurately (discrepancies have been noticed with the memory measurement feature).
Performance-based testing often measures the speed at which an operation occurs. When it's necessary to ensure that a certain level of performance can be attained, the MinOperationsPerSecond attribute should be used.
The Zanebug engine measures the execution time for a Test, and then determines how many times that Test can be run within a second.
The test engine introduces a small amount of overhead via Reflection, which should be taken into account
Due to initialization overhead, the Test will always perform slower the first time. Therefore, it is recommended that a Test be performed more than once after being loaded in order to get a better idea of its performance
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// A Test that will fail if it can't be repeated
// the min number of times in a second
[Test]
[MinOperationsPerSecond(1000)]
public void MinOperationsPerSecond()
{
// if this test can not be repeated 1000 times within a second
// it fails
}
}
}Tests must often be repeated in succession to measure things such as average performance, concurrency issues, etc. This can be accomplished with the Repeat attribute
using System;
using Adapdev.UnitTest;
namespace Examples.Zanebug
{
[TestFixture]
public class Attributes
{
// A Test that is repeated a set number of times
[Test]
[Repeat(5)]
public void Repeat()
{
// test code
}
// A Test that is repeated a set number of times
// with a 3 second delay in between
[Test]
[Repeat(5,3000)]
public void RepeatWithDelay()
{
// test code
}
}
}Often times you'll need to execute a transactional test. The most common example is a test where you want to insert data into a database, but then roll the database back to its original state.
Zanebug provides the ability to do distributed transactional tests, using COM+ Transactions underneath the covers. They are distributed because they can process a transaction against multiple platforms, such as a transaction that spans MSMQ, Oracle and Sql Server.
Transaction
To do a transactional test, use the [Transaction] attribute. If the test passes, the transaction will commit. If the test fails, the transaction will abort and rollback.
using System;
using System.EnterpriseServices;
using Adapdev.UnitTest;
namespace Zanebug.Examples
{
[TestFixture]
public class TransactionTest
{
[Test]
[Transaction]
public void Transaction()
{
Assert.IsTrue(ContextUtil.IsInTransaction, "Should be in a transaction.");
Console.WriteLine("TransactionId: " + ContextUtil.TransactionId);
}
[Test]
public void NoTransaction()
{
Assert.IsFalse(ContextUtil.IsInTransaction);
}
}
}In the example above, System.EnterpriseServices is referenced. This is only because ContextUtil is being referenced. If you don't need ContextUtil, you don't need to import the System.EnterpriseServices.dll. Zanebug transactions will work fine without it.
RollbackTransaction
In contrast to the Transaction attribute, the RollbackTransaction will automatically abort the transaction at the end of the test and roll things back. This is great for testing database CRUD operations.
using System;
using System.EnterpriseServices;
namespace Examples.Adapdev
{
[TestFixture]
public class RollbackTransactionTest
{
[Test]
[RollbackTransaction]
public void Transaction()
{
Assert.IsTrue(ContextUtil.IsInTransaction, "Should be in a transaction.");
Console.WriteLine("TransactionId: " + ContextUtil.TransactionId);
}
}In the example above, System.EnterpriseServices is referenced. This is only because ContextUtil is being referenced. If you don't need ContextUtil, you don't need to import the System.EnterpriseServices.dll. Zanebug transactions will work fine without it.