Trust, but verify

1. Prelude

Today, I want show how one of the best query tools in the world, can be used to easily and quickly create integration tests for workflow services. These integration tests will seamlessly automate the testing and verification of workflows that are created by web services.

This article is borne out of practical experience in the context of the Dynamics NAV (Navision) business software environment. This is an implementation detail, however; the method doesn’t depend on that.

Note: this article assumes the reader has some familiarity with LINQ. Should you not be familiar with LINQ, I strongly urge you to become so. LINQ is based on monads which come from the esoteric branch of mathematics known as Category Theory. This branch unifies vast areas of mathematics and provides extreme generality and expressiveness. It is however completely unnecessary to know about these deep things to be able to use and understand LINQ, which is in fact very easy to use and intuitive, even more so than SQL; it does not, however, replace SQL or T-SQL.

2. Setup

The basic setup is this:

  1. a script creates an order which it then posts to a web service
  2. the web service creates a workflow
  3. the workflow is executed
  4. the web service responds back (okay, error)
  5. the script queries the database to see if the changes made (or not made) matches expectations; these queries form our test-cases

we can visualize the method like this

3. Implementation

The little notepad symbol in the above diagram is the logo for the LINQPad tool. This tool is the software equivalent of a Swiss knife.

LINQPad makes it easy to connect to a database and execute queries against an object-relational mapping, meaning you can query C# objects that nicely represent the data domain.

Scripts can be made as C#/VB expressions, C#/VB statements or C#/VB programs. In our case we’ll need the full power of C# programs so we select that.

To send orders, we’ll make a trivial HTTP POST method:

string HttpPost(string service, string order)
     // trivial implementation left as an exercise for the reader ...

Next we want to build an actual order, we can do that using LINQ to XML. And we can in fact use LINQ to SQL queries to build up these orders from existing data in the system if needed.

LINQPad exposes the tables of the connected database as C# types. Let’s have a look at an actual data source:

The types are shown in the upper-left corner. I should note here that I did not connect to a SQL database because I didn’t have one ready at home but instead used a free web data source (it follows the OData protocol).

There is one important thing to keep in mind, however. When doing tests where an external system modifies the database, it doesn’t make sense to cache data. The reason is that we cannot see changes made to the database in the same session if we have caching enabled because the LINQ to SQL query provider is unaware of these changes. We cannot have valid tests without up-to-date data. The solution is simple; just put this statement into the main method:

void Main()
    ObjectTrackingEnabled = false;
    // ...

In the context of Navision there is one thing which will annoy any user of LINQPad. That is the fact that Navision has one table for each company, meaning if there are three companies in the database, then there will be three customer tables. So if you wish to query all three companies, you’ll have to do some search-replacing – but luckily that’s trivial, if a slight nuisance.

One could imagine building “Navision awareness” into a LINQPad, since it is in fact possible to extend LINQPad, but I’ve not had the time to do this. This might make it possible to just have a variable decide what table version is currently being used. That’s a side-effecting rabbit hole of its own, though.

Now onto the actual tests. These can be quite trivial, so let me make up a toy example:

// ...

var customersMissingTopRef =
        from c in CompanyCustomers
        where c.ParentCustomerNo != "" && c.TopCustomerNo == ""
        select c;
// ...

Test(customersMissingTopRef.Any(), "customers with missing top reference");
// ...

The Test method could print out the name of the test case and whether it succeeded or failed.

The type of the customersMissingTopRef variable will be: IQueryable<CompanyCustomer>. The IQueryable supports the standard query operators such as Select, SelectMany, Where, GroupBy, OrderBy, Take, etc.

4. Conclusion

This approach is a light-weight one. A more solid approach would be to build a full object-relational mapping from the database and code up test cases in stand-alone files that will be run as part of a test framework.

Nonetheless, the method shown here is straight-forward, easy to implement, and can save hours of tedious manual testing and retesting and can be considered first step with a low barrier-to-entry. I’ve already saved what would amount to several days worth of manual testing due to this method and have found several software defects and regressions.


About xosfaere

Software Developer
This entry was posted in Computer Science, Datamodel, Declarative, Imperative, Paradigm, Program, Software, Technical and tagged , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s