Md. Adil Akhter's Weblogs

Me, My Thoughts and My Journey ....

May 2008 - Posts

More on Unit Testing : TestContext

In Test Driven Development  as a developer we spend significant amount of time in writing unit tests. As improving coding coverage, testing all the boundary condition and ensuring the of quality code is significantly important, writing unit test effectively become one of the priority that we has to consider in software development.

In our today's discussion, we will see what is TestContext of VSTS unit testing framework and how we can use TestContext  to leverage writing effective unit tests for our application.

Prior to move forward with our today's Discussion on TestContext, I would really suggest you the following postings-

So, let's begin with a brief description on what is TestContext. Then we will go through different scenarios where we can use TestContext. :)

What is TestContext?

TestContext is a abstract class of Microsoft.VisualStudio.TestTools.UnitTesting namespace that provides various information about the current test run. Purposes that has been served by TestContext Class -

1.  To store information and provide information to the unit tests during Unit Test Run.

2.  Provide a mechanism to measure performance of your code being tested by the Unit Test.

3.  In Testing the web service it stores the additional information, like server's URL.

4.  In Asp.Net unit tests, it get holds of the Page object.

5.  For Data Driven Unit Tests , it provides access to the data rows.

The class definition of the TestContext Class -

   1: public abstract class TestContext
   2:    {
   3:        public const string AspNetDevelopmentServerPrefix = "AspNetDevelopmentServer.";
   4:  
   5:        protected TestContext();
   6:  
   7:        public virtual UnitTestOutcome CurrentTestOutcome { get; }
   8:        public abstract DbConnection DataConnection { get; }
   9:        public abstract DataRow DataRow { get; }
  10:        public abstract IDictionary Properties { get; }
  11:        public virtual System.Web.UI.Page RequestedPage { get; }
  12:        public virtual string TestDeploymentDir { get; }
  13:        public virtual string TestDir { get; }
  14:        public virtual string TestLogsDir { get; }
  15:        public virtual string TestName { get; }
  16:  
  17:        public abstract void AddResultFile(string fileName);
  18:        public abstract void BeginTimer(string timerName);
  19:        public abstract void EndTimer(string timerName);
  20:        public abstract void WriteLine(string format, params object[] args);
  21:    }

To start with the TestContext , we need to add a property in our Unit Test class as -

 

ClassDiagram1  
Figure1: TestContext Property in Unit Test class

Now if we just add a simple test and debug it - we will find that the VSTS Unit Testing Framework actually using an instance of  UnitTestAdapterContext which is derived from TestContext. This property TestContext is automatically initialized/assigned by the test run engine of VSTS when the Unit Test class is initialized after [ClassInitalize] method and before [TestInitalize] method.

image  Figure2 : UnitTestAdapterContext and its' properties in Debug Mode

Want to know more about TestContext?

It would be really hard to find the assembly  that contains UnitTestAdapterContext Class unless we search the private assemblies installed in <vs installation directory>\common7\IDE\PrivateAssemblies ; in my case which is - C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies.The assembly that contains UnitTestAppterContextClass -Microsoft.VisualStudio.QualityTools.Tips.UnitTest.Adapter.dll.

UnitTestAdapterContext class looks like following in reflector -

image
Figure3 : UnitTestAdapterContext in Reflector

How can we use TestContext ?

Like I mentioned in the beginning , we can use TestContext in different cases writing unit tests. Now , we are going to look at some features of TestContext class that we can use very easily in our unit tests.

 

 

Scenario 1 : Getting path of the folder that current test run created for storing input and outputs

 

It's a really handy feature of TestContext. It provides you the path of the unique folder for current test run that VSTS unit testing framework creates in every test run. In VSTS unit testing framework, every test run create a unique folder in  machine where it's running the test and generate a xml based test report ( *.trx) -

 

image

Figure 4 : Folders/Directories that are created by Test Run

Additional input or output of unit test can be stored in this folder and also can be cleaned up before exiting the unit test(if required). Furthermore, it's the safest place to put the additional stuff related to unit tests because our application have full access to this folder ( just consider the situation where unit tests are running in a Build Machine or in a remote server where application might not have access to "C:\Temp\" folder :) ).

 

So, how can we get the path of the folder of current test run? Simple -

string testDirectory = TestContext.TestDir;

We can get access to the deployment folder  and the folder that contain logs  as well -

string deploymentDirectory = TestContext.TestDeploymentDir;
string logDirectory = TestContext.TestLogsDir;

 

Scenario 2 : Generating report how much time was elapsed to complete the Unit Tests

 

TestContext Class has 2 public member BeginTimer and EndTimer which takes a string parameter as argument ( Name of the timer). Using this feature of TestContext class, how much time elapsed to run particular unit test can be measured very easily. This feature of TestContext might be useful in tracking how much time the code to be tested is consuming and whether or not it's going to meet speed criteria of nonfunctional requirements.

 

   1:  TestContext.BeginTimer("longRunningProcess");
   2:   
   3:  // Call the long running method here to test whether it meet the performance requirement
   4:  //....
   5:  //
   6:   
   7:  TestContext.EndTimer("longRunningProcess");
   8:            

Result of the timer is shown as -

image
Figure 5: Output from the Timer

Scenario 3 : Adding additional information in Test Result

 

There is another cool property in the TestContext class which gives you flexibility to provide additional information in Test Result which will be stored in Test Result file( .trx extension stored as Xml). This could be really useful to provide additional information about the Test such as Test Environment, parameter and other details that might be important to store with test result. Lets consider a particular unit test -

 

   1: [TestMethod]
   2: public void ShouldCreateUser()
   3: {
   4:     // ... Unit Test Codes....
   5: }
   6:  
   7: [TestCleanup]
   8: public void TestCleanup()
   9: {
  10:     TestContext.WriteLine("{0} : {1}", TestContext.TestName ,TestContext.CurrentTestOutcome);
  11: }

 

Using TestContext.WriteLine , we can provide additional information [ For Example - just to show, in the above example at line 10 , we are writing the test name and test result] and that is visible to the test result viewer as -

 

testoutput

Figure 6: Output from TestContext.WriteLine


Scenario 4 : TestContext in Asp.Net Unit Testing

 

TestContext provides the access to the Asp.net Page object representing the current web request while unit testing Asp.net Web Application. How? TestContext has a property - RequestPage which is actually an instance of System.Web.UI.Page object and defined as-

"...references the Page object created by the URL used to invoke the test"

TestContextInAspnet

Figure 7: TestContext.RequestPage

This property is only valid for Asp.Net Unit Testing. After getting hold of Page Object of current web request, any control of the page can be accessed using FindControl method of the Page Object. Using PrivateObject class, we can invoke the private/protected methods of the current .Aspx page represented by Page Object.


Example of an Asp.net Unit Test :

   1: [TestMethod]
   2: [HostType("Asp.Net")]
   3: [UrlToTest("http://localhost/ourwebapplication")]
   4: public void TestMethod()
   5: {
   6:     Page page = TestContext.RequestedPage;
   7:  
   8:     PrivateObject privateObject = new PrivateObject(page);
   9:     
  10:     Button button = (Button)page.FindControl("Button1");
  11:     privateObject.Invoke("Button1_Click", button, EventArgs.Empty);
  12:  
  13:     Label label = (Label)page.FindControl("Label1");
  14:  
  15:     Assert.AreEqual<string>("Hello World !!!!", label.Text);
  16: }


We have a label and button in our an .aspx web page. When a button click event occurs, the label text get changed to "Hello world !!!". And another important things to note that we are using IIS to host our web application(line 3).Now, we are going to test this feature using Asp.Net Unit Test in the sample code above.

 

So, first we get hold of our Page object(Line 6) representing our current web request(UrlToTest in Line 3) through the TestContext.RequestPage which eventually enable us to access any control of the page object(e.g. Line 10 & 13). Using the PrivateObject( line 8), we could conveniently invoke any member of the page class as well (line 11).

Hence, using TestContext, we can effective leverage our codes inside Aspx page into Test5 Coverage.


Scenario 5:
TestContext in Data Driven Unit Tests

In Data Driven Unit Test, TestContext provides access to the current DataRow for which test is running and by that way, it provides access to every DataRow of the DataSource one by one.

In the following example, we will see how the TestContext enable us to getting access to every DataRow in the DataSource -

First, lets define our Table that contains few UserContacts with valid information. So, our unit test should create every one as a UserContact of our Application. But that's not our example's goal here. In this example , We will see how TestContext enables us to access every UserContact stored in the UserContact Table so easily.

TableDefinition

Figure 7: UserContact Table Definition

Let's put some Valid User Contact Information in our Table - UserContact -

TableData

Figure 7: Data in UserContact Table

After that, let's write our unit test -

   1: [DataSource("System.Data.SqlClient", "[path to MDF]\";
   2: Integrated Security=True;User Instance=True", "UserContact", 
   3: DataAccessMethod.Sequential), TestMethod()]
   4: public void ShouldCreateUsers()
   5: { 
   6:     string userLoginName = (TestContext.DataRow["LoginName"].ToString());
   7:     string userFirstName = (TestContext.DataRow["FirstName"].ToString());
   8:     string userLastName = (TestContext.DataRow["LastName"].ToString());
   9:     string userEmailAddress = (TestContext.DataRow["EmailAddress"].ToString());
  10:     string userDepartment = (TestContext.DataRow["Department"].ToString());
  11:  
  12:     TestContext.WriteLine("Creating user - Login Name :{0} Name : {1} {2} 
  13:                           EmailAddress : {3} ", userLoginName, userFirstName, 
  14:                           userLastName, userEmailAddress);
  15:  
  16:     bool successful = UserManger.CreateUser(userLoginName, 
  17:                                           userFirstName, userLastName, 
  18:                                           userEmailAddress, userDepartment);
  19:  
  20:     Assert.IsTrue(successful);
  21: }
From the above example, we can see that (line 5  - 10 ) , TestContext is providing us access to the current DataRow for which the unit test is getting executed. We can see the output of TestContext.Writeline(line 12) and Result of the unit test for each DataRow below -

 

 

Scenario 6: TestContext.AddResultFile

 

Last but not the least, TestContext has another useful member called AddResultFile which is defined as -

void AddResultFile(string filename)

Through TestContext.AddResulFile, additional files can be added with the test result. The additional file will be stored in the unique folder created for current test run.This might be useful in some cases, for example, if we are validating our objects with some external resource, like schema - we can store the schema with our test result so that we can validate our test result later on after test run.

Conclusion

Hence, we can utilize the TestContext class in several cases to solve our problem in writing unit tests. But saying all this, I would like to emphasize on designing test cases and map it to a unit test carefully because "Harnessing the power that is provided by the tool and utilizing it effectively and every possible way" could only bring desirable result .

In our today's discussion, we went through several scenarios where we can leverage TestContext to write a better and precisely more effective unit test using the Unit Testing Framework provided with Visual Studio. Thanks for being with me so far and I would really appreciate comments or any suggestion.

I would really appreciate any comments or suggestion on this topic.
 

kick it on DotNetKicks.com
More Posts