SysTest quick start

Overview

I am a big fan of TestDrivenDevelopment and I am trying to follow this approach for all my development. Unfortunately DynamicsAX didn't have any UnitTest framework and thus it was difficult to use TestDrivenDevelopment. It was clear that we needed something in DynamicsAX and that's why we now have SysTest.

SysTest

SysTest is a UnitTest framework. If you are familiar with any other UnitTest framework then you will feel comfortable with SysTest.

Main features/goals:

  • it is very fast
  • it offers rich set of "assert" methods
  • it supports tests throwing exceptions (expected exceptions)
  • it supports transactions (tests can be placed inside a database transaction that is aborted at the end)
  • it supports company accounts (tests can be placed inside a separate company account that is deleted at the end of the test)
  • it supports fixture setup and tear down that are called automatically by the framework
  • it supports suites of tests
  • it supports setup and tear down method for suite that is run before the first method and after the last test method
  • it supports suites of suites
  • it supports code coverage
  • it has a wide variety of listeners for DynamicsAX environment
  • toolbar for quick test suite execution

Examples

Let's demonstrate the framework on examples. Let's create a unit test to test int2str conversion method. First we have to create our test class:

 public class MySimpleTest extends SysTestCase
{
}

Now if you use toolbar (Tools > Development Tools > Unit Test > Show toolbar), then enter name of your test MySimpleTest and click Run. You immediately see "0 run, 0 failed". It's that simple. Ok, it maybe simple because it doesn't do much. Let's add a test validating that the function can successfully convert positive and negative numbers. To do so just add new method named with test prefix that is public, returns void and doesn't take any parameter. In the method check all your asserts.

 public class MySimpleTest extends SysTestCase
{
    public void testConversion()
    {
        this.assertEquals('123', int2str(123));
        this.assertEquals('-2', int2str(-2));
    }
}

When you run the test in a toolbar runner for example you will immediately see "1 run, 0 failed". Great! Let's add more tests to our test class. Let's see how the framework handles tests that throw exceptions but those exceptions are expected. Because of that we don't want to fail the test method. There are two ways to do that. First one is that you will code the exception handling yourself:

     public void testExpectedException()
    {
        try
        {
            throw Exception::Error;
        }
        catch(Exception::Error)
        {
            return;
        }
        this.fail('An expected exception wasn\'t thrown!');
    }

As you can see when we expect an exception and it is not thrown then it is considered a failure. SysTest offers a better way to do the same.

     public void testExpectedException()
    {
        this.parmExceptionExpected(true);
        throw Exception::Error;
    }

Exceptions are more complex in DynamicsAX. Usually we don't just throw an error exception but we throw an error message. How do we handle that in SysTest?

public void testExpectedError() { ; this.parmExceptionExpected(true, 'Oops -- error message'); throw error('Oops -- error message'); } }

When you run the test you will get "3 run, 0 failed". Let's now see what happens when a failure occurs. We will begin by adding a failing test.

     public void testFailure()
    {
        this.assertEquals('0', int2str(123));
    }

This is clearly a failing test. When we run the test class now we get "3 run, 1 failed". Toolbar displays all failures in Infolog window. For our test class we would see the following window:

On the other hand we don't have to rely on toolbar. Let's say that we want to see all messages (not just failures) and let's say that we want to write them into DynamicsAX Print window. First let's write our own runner. For a runner we have to specify the whole suite we want to run. That's easy and all you have to do is to create instance of SysTestSuite class and pass your test class name to SysTestSuite constructor. To run the suite call run method. Here is one little problem. Suite itself is not interested in results at all. The problem is that we are and if we want to know the result at the end we have to create special SysTestResult object and pass it to the suite when it runs our test.

     public static void main(Args _params)
    {
        SysTestSuite suite = new SysTestSuite(classstr(MySimpleTest));
        SysTestResult result = new SysTestResult();
        ;
        suite.run(result);
        print result.getSummary();
    }

The code above creates the suite for our test class and executes all the tests. At the end it prints the summary report to the Print window. If we want to print all messages (failures, information, but also when a test is started or completed) then we have to register the corresponding listener.

     public static void main(Args _params)
    {
        SysTestSuite suite = new SysTestSuite(classstr(MySimpleTest));
        SysTestResult result = new SysTestResult();
        ;
        result.addListener(new SysTestListenerPrint()); // <- LISTENER REGISTERED
        suite.run(result);
        print result.getSummary();
    }

In total our whole class looks like this:

 public class MySimpleTest extends SysTestCase
{
    public void testConversion()
    {
        ;
        this.assertEquals('123', int2str(123));
        this.assertEquals('-2', int2str(-2));
    }
    public void testExpectedException()
    {
        ;
        this.parmExceptionExpected(true);
        throw Exception::Error;
    }
    public void testExpectedError()
    {
        ;
        this.parmExceptionExpected(true, 'Oops -- error message');
        throw error('Oops -- error message');
    }
    public void testFailure()
    {
        ;
        this.assertEquals('0', int2str(123));
    }

    public static void main(Args _params)
    {
        SysTestSuite suite = new SysTestSuite(classstr(MySimpleTest));
        SysTestResult result = new SysTestResult();
        ;
        result.addListener(new SysTestListenerPrint()); // <- LISTENER REGISTERED
        suite.run(result);
        print result.getSummary();
    }
}

That's all. If you want more information then stay tuned. In the next post I will try to describe individual features of SysTest in more details.