Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

BizUnit Context Tutorial

4.47/5 (4 votes)
5 May 2007CPOL13 min read 2  
This article explains how to use the Context object, a key part of the BizUnit framework.

Introduction

BizUnit is a very extensible testing framework. One of the core elements of BizUnit is the Context object. Unfortunately, the documentation does not explain how users can take advantage of it, and it is left to developers to try and understand it from some of the code snippets provided in the NDoc documentation.

This tutorial starts with a brief note on BizUnit and then goes on to introduce the Context object and explains how it can be used in BizUnit based testing. It uses examples from the official documentation wherever applicable, and supplements it where the information in the documentation is not very clear. It will not replicate the list of methods and properties etc., so for an exhaustive reference, please refer back to the documentation. The three main context loader steps are explained here, and future articles will explore custom steps as well as advanced uses of the Context in unit and system testing.

Please note that some of the file paths refer to the structure of the BizUnit source code and the functional tests (for instance, the TestData folder is a specific folder in the set of unit tests that accompany the source), so I am assuming you have the code and can follow along.

Author's note: 23-11-2006: Based on feedback received, I have updated the article to include a section on the basics of BizUnit.

Update on 24-March-2007: For those who haven't come across the project yet, I have started the BizUnitExtensions Open Source project, and you can download bug fixes to the BizUnit core as well as a load of additional steps.

Update on 05-May-2007: Kevin has moved BizUnit to CodePlex, and has put some new releases with bug fixes and enhancements. The Context has now got a large number of enhancements which will be documented shortly. The Extensions project will no longer be releasing the Core as part of its regular releases, to avoid confusion in the community as to which source code should be used.

BizUnit - The Basics

BizUnit is a unit testing tool intended for BizTalk. It is also an extensible framework, and allows developers to write more tests to enrich the functionality that is out of the box. It makes use of NUnit.

For an introduction to BizUnit, you can read this article, written by Kevin Smith, the guy who wrote the tool. You can also take a look at Naveen's article here on CodeProject.

Basically, BizUnit works in a black-box mode. Let's assume we have an orchestration that takes data from a file location and then submits it to a Queue or a Web Service and then to a database. Now, if we wanted to manually test it, we would have to create a sample file, place it in the folder corresponding to the orchestration's 'receive location', and then check that it arrived on the queue and finally at the database (and if the Web Service itself logged the SOAP request, we can check for the presence of an entry in the appropriate log file).

With BizUnit, we can automate the steps that we have done manually. We can create a folder with all our sample files (which is a pre-requisite manual step) and write BizUnit Steps to copy the files to the receive locations and to check the queues, log files, and databases.

It turns out that since most of the steps involve copying files, posting to SOAP endpoints, and checking queues, databases, and event logs, this tool can be used to test non-BizTalk projects as well. We can simply ignore the steps that pertain to BizTalk such as BAMQueryStep.

(Note: There isn't actually a LogFileReader step because we all have different log file formats, but it is possible to write one, or if we have a fixed format, we can use the XmlContextLoader or RegExLoader to pull up the content from the file and check it.)

As with all automated testing, when you have to do this step once or twice, it is easy enough to do manually, but when data conditions start to grow and you need an automated test as part of your automated build and release process, then this becomes invaluable. Further, when you do integration testing and need to verify the flow of messages between various services and components, manual testing is simply not sustainable.

The issue with it is that for large test scripts, the BizUnit scripts can soon become unwieldy. For example, if you had a test case where data was picked up from a specified source file, loaded into a target, then validated and perhaps moved somewhere else (maybe to an MSMQ queue or database), you may find that the filenames and folder names are duplicated across the test steps. Further, if you already have this data (folder names, filenames, connection strings etc.) in a configuration file which you are using, say, for some NUnit tests that exercise non-BizTalk artifacts), then it is quite unnecessary to hardcode all of them in the test XML file and as the test library grows, it becomes very hard to maintain.

This is where the 'Context' object proves to be a great help.

The Context Object

The Context object represents a state object that is passed between BizUnit test steps. Inside the BizUnit system, the Context object is passed to each individual test step. It cannot be initialised and used in the TestFixture class but is available for use in custom steps. In the XML file corresponding to the test case, the Context can be read and manipulated using the specified steps (prewritten or custom).

Inside the BizUnit system, state may be written to or read from the Context. The Context also provides helper methods for test steps to read their configuration, and to log information, warnings, errors, and data in a consistent manner. When a test is executed, the detailed information available in the output window is produced by using the Context class. So if you are writing a custom step, you would be expected to log exceptions, errors, and warnings etc., using the methods provided in the Context class.

You can also use it to store information such as name-value pairs which help in the logic of the custom step. (Of course, this is not mandatory. Since the custom step is merely your own class, you can define and use your own private member variables as you wish.)

As I mentioned in the "Basics" section above, large scripts can soon become unmaintainable, and so another important use for the Context is to store configuration data that is used across test steps. For very simple test cases where we utilise only a couple of steps, this may not seem very useful, but as the test cases get bigger and when data gets duplicated, the storing of parameters in the Context becomes crucial.

Using the Context: Basics

A simple way to think about Context is that it can be used like NAnt properties. In NAnt, we would declare a property variable with a specific name and store a value in it. Then in all the tasks, you simply refer to the property using ${propertyname} and the system picks up the appropriate value.

In BizUnit, out of the box, we would first create a FileValidate step to point to our configuration data file. This step loads the file into memory (to avoid losing the file, set <DeleteFile> to false). After this, we can use XmlContextLoader, TextContextLoader, or RegExContextLoader to extract data from the source file.

The choice of the context loader depends on the format of the data. XmlContextLoader expects you to supply XPath expressions that allow it to navigate the source data and extract the values. RegExContextLoader expects to be supplied a Regular Expression to be applied to the source data (and strictly speaking, as long as the Regular Expression is correct, it does not matter if the file is XML or text). The TextContextLoader is slightly different. It works like the Substring() method and the old VB6 Mid$() functions, and looks for a specified pattern, index, and string length to retrieve data from the file. (See below for a detailed example.)

Which steps support context configuration data?

Most of the steps (one notable exception being the DotNetObjectInvoker) support configuration data. This is not very clear in the documentation (although there are some hints here and there). In the XML node (corresponding to any element in the step), we need to use the attribute "takeFromCtx = <contextkeyname>", and the system will look up the context when it executes the step.

For example, consider the following FileCreateStep. Here, the context has already been loaded using the appropriate context loader.

<TestStep assemblyPath="" 
  typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.FileCreateStep">
<SourcePath takeFromCtx="sourcefilename"></SourcePath>
<CreationPath takeFromCtx="targetfilename"></CreationPath>
</TestStep>

In the SourcePath and CreationPath elements, we use the takeFromCtx attribute and give it the context key name that the system should look up. The system then substitutes this at runtime.

Where it differs from NAnt

A very important limitation of the context variables is that there is no run time 'macro expansion' behaviour beyond a simple context lookup.

For instance, in a NAnt task, we can concatenate various properties together (along with system properties), so if we wanted a full file name, we could use something like:

${source.path}\${file.name}.${file.extension}

The system would then substitute the property names with the appropriate values, and use the supplied symbols such as \ and the period (before the file extension) to arrive at the full file name. At this point in time, this cannot be done in BizUnit, although it could be done by overloading the methods in the Context class to apply a macro expansion when the appropriate symbols such as $ and {} are detected.

XmlContextLoader

Overview

The XmlContextLoader evaluates an XPath expression to the source data and adds the value into the context.

Example

The following shows an example of the XML representation of this test step. In this example, the existence of the file has been validated using the file validate step (not shown), and this file contains all the context variables.

The actual file used in this example is included along with the source code, in the TestData folder. The file name is XmlContextData.xml. The contents are shown below:

XML
<Variables> 
  <sourcefilename>c:\temp\test.xml</sourcefilename> 
  <targetfilename>c:\temp\testtarget.xml</targetfilename> 
  <connectionstring>Persist Security Info=False;Integrated Security=SSPI;
     database=Northwind;server=(local);Connect Timeout=30</connectionstring> 
  <sourcefolder>c:\temp\</sourcefolder> 
  <targetfolder>c:\temp\</targetfolder> 
</Variables>

Now when we use the ContextLoader, we can load all the contents of the file into the context by using the correct type of context loader such as an XmlContextLoader or a RegExContextLoader.

Here, we are using the XmlContextLoader, and the XPath expressions represent the navigation path to the data in the source file (i.e., 'Variables' is the top level element and 'sourcefilename' and 'targetfilename' are the elements which contain the values we want to load into the context). Each of these XPath context key elements will result in the equivalent of Context.Add(key,object) being executed.

XML
<ContextLoaderStep assemblyPath="" 
  typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.XmlContextLoader"> 
<XPath contextKey="sourcefilename">
  /*[local-name()='Variables']/*[local-name()='sourcefilename']
</XPath> 
<XPath contextKey="targetfilename">
  /*[local-name()='Variables']/*[local-name()='targetfilename']
</XPath> 
</ContextLoaderStep>

RegExContextLoader

Overview

The RegExContextLoader applies a Regular Expression to the source data and adds the value into the context.

Example

The following shows an example of the XML representation of this test step. In this example, the existence a file has been validated using the file validate step, and this file contains all the context variables.

The actual file used in this example is included along with the source code, in the TestData folder. The file name is RegExTestData.txt. The content of the file is shown below:

The BizTalk web site is here: http://www.microsoft.com/biztalk, 
you can find out more about the product there.

Now when we use the ContextLoader, we can load all the contents of the file into the context by using the correct type of context loader such as an XmlContextLoader or a RegExContextLoader.

Here we are using a RegExContextLoader and adding in various items into the context. Each of these RegEx context key elements will result in the equivalent Context.Add(key,object) being executed. (For example, the context key named HTTP_Url will now contain http://www.microsoft.com/biztalk.)

<ContextLoaderStep assemblyPath="" 
   typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.RegExContextLoader"> 
<RegEx contextKey="HTTP_Url">/def:html/def:body/def:p[2]/def:form</RegEx> 
<RegEx contextKey="ActionID">/def:html/def:body/def:p[2]/def:form/def:input[3]</RegEx> 
<RegEx contextKey="ActionType">/def:html/def:body/def:p[2]/def:form/def:input[4]</RegEx> 
<RegEx contextKey="HoldEvent">/def:html/def:body/def:p[2]/def:form/def:input[2]</RegEx>
</ContextLoaderStep>

TextContextLoader

Overview

The TextContextLoader works like the Substring and the old VB6 Mid$ functions. It searches through the source data for the specified strings and then returns the substrings from a specified position and for a specified length.

Example

The following shows an example of the XML representation of this test step. In this example, the existence of a file has been validated using the file validate step (not shown), and this file contains all the context variables. The actual file used in this example is included along with the source code, in the TestData folder. The file name is TextContextLoaderDemo.txt. The file contains the following two lines:

source=c:\temp\test.xml"
target=c:\temp\testtargetnew.xml"

In the following snippet, we are using a TextContextLoader and adding in various items into the context.

<ContextLoaderStep assemblyPath="" 
   typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.TextContextLoader"> 
<Item contextKey="sourcefilename" searchString="source" skipNumber="1" stringLength="16" /> 
<Item contextKey="targetfilename" searchString="target" skipNumber="1" stringLength="25" /> 
</ContextLoaderStep>

Now what happens is that when setting up the context key named "sourcefilename", the system looks for the pattern/search string which is "source" and finds its IndexOf value. It then adds that to the skipNumber which is 1, thus making it ignore the "=" symbol in the line of data. It then looks for the next 16 characters (which happens to be the full path name), and returns that. The same processing occurs for the next context key "targetfilename", and in this case, it takes 25 characters.

This concept could be applied to a big string of parameters in the source file (if you don't want to bother with creating an XML file and working out the necessary XPath expressions).

Limitation

This step does have one limitation in that the string lengths for the returned value need to be known in advance, thus limiting the ability of the developer to easily change the test data. A future version of this step (or another custom step) can be created which allows flexible loading of an array of context values.

ContextManipulatorStep

Overview

The ContextManipulator is used to manipulate BizUnit Context fields. According to the documentation, it maybe used to create a new field from one or more existing fields.

Example

In the following example, a new context key named newsource is created and the value is taken from the existing context key.

<TestStep assemblyPath="" 
   typeName="Microsoft.Services.BizTalkApplicationFramework.BizUnit.ContextManipulatorStep"> 
<ContextItem contextKey="newsource"> 
<ItemTest takeFromCtx="sourcefilename"></ItemTest> 
</ContextItem> 
</TestStep>

Issue

Out of the box, this step does not appear to work no matter what attempt is made. It seems that the code is not written correctly for the step. (We would be better off writing our own step for this.) I will be writing a custom step for this shortly, and will post it at this site.

Summary

This article provided just a brief overview of how the Context works and I hope this has given you the understanding you need to make use of it. In my current integration project, we have written a few custom steps and made extensive use of the Context to read our app.config and web.config files, and it has proved invaluable in eliminating redundant declarations and hard-coding. Please email me if you have any questions, and I will try to answer them as soon as possible. Good luck (and if you write any custom steps or work out new techniques to use the Context, do let the rest of the community know).

There are a couple of bugs in the provided code for this step:

Fix 1: Remove the code that creates the stream reader and reads to the end of data stream, as data will then not load into XmlDoc (because the pointer is set to the end), and the StreamReader is never used anywhere.

Fix 2: Change the SelectSingleNode code to the following:

C#
contextValue = doc.SelectSingleNode( xpathExp ).InnerText;

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)