SysTest part I.: Assertions and test classes

In the next series of posts I will demonstrate and explain some of the basic as well as some more advanced features of SysTest.

Test classes

Let's begin with a very simple test. To create a test, derive a class from SysTestCase class. This is all that is needed for SysTest to be able to run tests from this class. This class might have many different methods. How does SysTest know what are the main methods that should be run as unit tests?

SysTest is looking for all the methods with the following signature:

  public void test*()

All these methods are recognized as unit tests and executed.

Assertions

SysTest offers a wide range of assert methods for you to use in your tests. You can see them all in SysTestAssert class.

Method Meaning
assertEquals asserts that the two parameters are equal; when the parameters are classes implementing "equal" method then this method is called to compare them, otherwise they are compared using == operator
assertNotEqual oposite to assertEquals
assertSame compares the parameters using == operator
assertNotSame oposite to assertSame
assertRealEquals compares two real values with a provided delta; therefore if you pass number1, number2 and delta, then the assert passes when | number1 - number2 | <= delta
assertNull assert fails when the parameter is not null
assertNotNull oposite to assertNull
assertTrue assert fails when the parameter is evaluated as false
assertFalse assert fails when the parameter is evaluated as true

The class has two additional public members.

Method Meaning
info logs an information message (used for debug trace for example)
fail logs an error message and fails the current test

Let's now write a simple set of tests exercising these assert methods. The following two tests call assertEquals to verify string conversion functions:

  public void testConversionFromString()
 {
    this.assertEquals(123, str2int('123'));
    this.assertEquals(1234.56, str2num('1234.56'));
    this.assertEquals(28\7\1973, str2date('28.7.1973', 123));
 }
 
 public void testConversionToString()
 {
    this.assertEquals('123', int2str(123));
    this.assertEquals('1,234.56', num2str(1234.56, -1, 2, 1, 2));
    this.assertEquals('28.7.1973', date2str(28\07\1973, 123, 1, 2, 1, 2, 4));
 }

See that assertEquals takes anytype as a parameter and thus you can compare strings as well as dates and other base types.

Let's now try to compare objects. Assume we have the following class:

 public class ComparableObject
 { 
  str name;
   
  public str parmName(str _value = name)
  {
    ;
    name = _value;
    return name;
  }
   
  public static ComparableObject construct()
  {
    ;
    return new ComparableObject();
  }
  protected void new()
  {
    ;
    name = '';
  }
 
  public boolean equal(ComparableObject _compareWith)
  {
    ;
    return (strcmp(this.parmName(), _compareWith.parmName()) == 0);
  }
  public str toString()
  {
    ;
    return name;
  }
 }

Now we can compare instances of this class using our next test:

  void testObjectComparison()
 {
    #define.name('David Pokluda')
    
    ComparableObject object1 = ComparableObject::construct();
    ComparableObject object2 = ComparableObject::construct();
    ;
    object1.parmName(#name);
    object2.parmName(#name);
    
    this.assertEquals(#name, object1.parmName());
    this.assertEquals(#name, object2.parmName());

    this.assertNotSame(object1, object2);
    this.assertEquals(object1, object2);
 }

See that assertSame would fail because those are different instances but assertEqual passes just right. That's because assertEquals is calling object's equal method to determine whether the instances are equal or not.

Note: You might also notice how SysTest reports failures on objects. Try to change the call from assertNotSame to assertSame. The framework reports something like this:

 [mySimpleTests.testObjectComparison] Failure: Assertion failed! 
(Expected: David Pokluda; Actual: David Pokluda)

How does the framework know that the expected value is "David Pokluda"? Notice that I have added toString method to my object. That's what SysTest calls (when available) to report the expected and actual value for objects.

Let's now try the last two methods, info and fail.

  public void testInfo()
 {
    ;
    this.info('This is a debug message.');
 }
 
 public void testFail()
 {
    ;
    this.fail('Failed the test by calling fail method.');
 }

When you run the tests you will see that testFail failed and the message would be the one provided by us when calling fail method. But where is our info message?

If you use toolbar to run tests (Tools > Development Tools > Unit Test > Show toolbar) then in case of errors/failures, the error messages are displayed in the Infolog. Info messages are ignored in toolbar. If you want to see them you have to either use other test runner (we will cover this topic hopefully sometime later -- if requested) or you write a simple runner yourself. The following job would run our tests:

  static void RunMySimpleTests(Args _args)
 {
    SysTestSuite suite = new SysTestSuite(classstr(MySimpleTests));
    SysTestResult result = new SysTestResult();
    ;
    suite.run(result);
    print result.getSummary();
    pause;
 }

There are different listeners (we will cover this in detail sometime later). Toolbar runner is using a modification of SysTestListenerInfolog that displays only error messages. If you want to see all the messages use either SysTestListenerPrint or SysTestListenerInfolog. Or use both. Do you see how flexible this is?

  static void RunMySimpleTests(Args _args)
 {
    SysTestSuite suite = new SysTestSuite(classstr(MySimpleTests));
    SysTestResult result = new SysTestResult();
    ;
    result.addListener(new SysTestListenerPrint());
    suite.run(result);
    print result.getSummary();
    
    pause;
 } 

Don't worry for now what all those objects mean, just use this sample as a template for your own jobs. I prefer instead of jobs to add this to the test as main method:

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

Why do I do that? Well because during TestDrivenDevelopment I like to run the tests all the time and there is nothing easier than pressing F5 on my keyboard.


You can download these samples: Part01: Assertions.xpo


That's it for today. In the next post I will try to explain exception handling (expected exceptions) and hopefully much more.