3.4. Sureassert Named Instances

A Sureassert named instance is just an Object with a name. You can assign names to Objects used in testing and then refer to these in your Exemplars.

3.4.1. Special named instances

3.4.1.1. null

Use null in SIN Expressions to refer to a null object:

@Exemplar(args={"null"}, expect="-1")
public static int getLength(String str) {

    if (str == null)
        return -1;
    else
        return str.length();
}

3.4.1.2. Referring to the Exemplar’s Return Value – retval

Use retval in SIN Expression expect statements to refer to the value returned by the method-under-test in an Exemplar:

@Exemplar(args={"'test'","10","','"}, expect="=(retval.length(),49)")
public static String repeat(String str, int numTimes, String separator) {

    StringBuilder retStr = new StringBuilder();
    for (int i = 0; i < numTimes; i++) {
        retStr.append(str);
        if (i < numTimes - 1) {
            retStr.append(separator);
        }
    }
    return retStr.toString();
}

3.4.1.3. Referring to the Object-Under-Test – this

Use this in SIN Expressions to refer to the object under test:

public class Foo {

    private String errorMsg;

    @Exemplar(args={"new RuntimeException('testMsg')"},
                    expect="this.errorMsg.equals('RuntimeException: testMsg')")
    public void setError(Throwable e) {

        errorMsg = e.getClass().getSimpleName() + ": " + e.getMessage();
    }
}

3.4.1.4. Referring to Arguments of the Method-Under-Test – $argN

Use $argN in SIN Expressions to refer to an argument object or value, where N is the argument number (starting with 1 for the first argument):

@Exemplar(args={"l:'entry1','entry2'","'entry3'"}, expect="=($arg1.size(),3)")
public static  void addToListNoNull(List list, E obj) {

    if (obj != null)
        list.add(obj);
}

3.4.1.5. Referring to the Argument Value in Stub and Verify Matcher Expressions – arg

Use arg in the matcher part of SIN Expressions used for stubs and verify in order to refer to the argument being matched. See the sections on test isolation, stubbing and behaviour verification for more information.

 

3.4.2. Naming instances

You can name objects created by and used within the Sureassert testing context in order that they be used within Exemplars throughout your projects.

3.4.2.1. Setting name

Set the name Exemplar property to assign a name to the value or object returned by the method under test. The name can be anything with the following exceptions:

  • It cannot contain the special named instances (retval, null, this, $arg, arg)
  • It cannot start with a numeric character
  • It cannot contain any of the characters: . , [ ] { } ( ) :
  • It cannot contain spaces

Named instances are automatically prefixed with the simple name of the Class to which they belong, separated by the given name with “/”. For instance, naming an instance testInstance1 in class myproject.myackage.Foo results in a named instance of Foo/testInstance1. Typically you might simplify the name to i1 or use a name that makes sense in the context of the class and the test e.g. Package/heavy or Flight/full.

When referring to a named instance from within the same class it is declared, it is not necessary to include the class name prefix. For example when referring to Foo/i1 within class myproject.mypackage.Foo, you should just specify i1. When referring to a named instance from another class, use the full name.

If you specify a named instance with a qualified name, you can use anything on the left-hand-side. For example if you had two classes in your workspace package1.Foo and package2.Foo, you might specify an instance name within package2.Foo as P2Foo/i1.

Naming instances in this way also helps you find where they are declared, as you can Ctrl+click on class name portion of the named instance to take you to the declaring class in Eclipse.

public class Counter {

    private int val;
    private int threshold;

    @Exemplar(name="i1-5", args={"1","5"})
    public Counter(int val, int threshold) {

        this.val = val;
        this.threshold = threshold;
    }

    @Exemplar(instance="i1-5", expect="=(this.val,2)")
    public void increment() {

        if (val < threshold)
            val++;
        }
    }
}

3.4.2.2. A word on constructors

As is the case in the example above, it’s common to use a named Exemplar to create objects-under-test for use in other Exemplars in your workspace (not necessarily within the same class). The retval named instance when used in an Exemplar on a constructor, is the instance created by the constructor.

The name parameter can of course be used on any non-void method however, not just constructors.

3.4.2.3. Referring to Classes by Name

Classes may be referred to by their simple name if they are classes in the source folders of the projects in your workspace, or if they are java.lang classes (e.g. String). Otherwise you must use the fully-qualified class name, with the package names separated with “/” rather than “.”. For example:

String.valueOf(5)

MyProjectClass.someField

com/fooproject/ALibraryClass/someMethod()

In addition you can assign your own name to a class. This is generally not recommended but can be useful in some situations, for instance where you have two project classes with the same simple name and don’t want to use the fully-qualified names in your Exemplars. Use the @NamedClass annotation to do this. Named classes follow the same naming rules as named instances but it’s recommended you don’t use the same convention in order to avoid confusing a class with an instance.

For example if you had 2 classes called “Counter” in different packages, you might want to name them “Counter_1” and “Counter_2” :

package com.myproj1;

@NamedClass(name="Counter_1")
public class Counter {
    ...
}
package com.myproj2;

@NamedClass(name="Counter_2")
public class Counter {
    ...
}

3.4.2.4. The NamedInstance annotation

The @NamedInstance annotation assigns a name to the instance returned by the default constructor for classes that do not have any declared constructors. You can also use it to assign a name to initialized field values.

Defining a NamedInstance on a class:

@NamedInstance(name="i1")
public class Foo {

    // No constructors (has default no-arg constructor)
    ...
    @Exemplar(args="i1")
    public static doSomething(Foo foo) {
        ...
    }
}

Defining a NamedInstance on a value returned by a static field:

public class Foo {

    @NamedInstance(name="gridMtx_357")
    public static int[] gridMtx = new int[] {3, 5, 7};
    ...
}

Note however that you could also refer to this field directly via Foo.gridMtx so generally assigning a name is not necessary.

3.4.2.5. Naming the post-execution instance: instanceout

Often an Exemplar will set an instance to use, and the method-under-test will alter it in some way. You may want to re-assign the altered instance a new name, which other Exemplars can then use:

public class Counter {

    private int val;
    private int threshold;

    @Exemplar(name="i1-5", args={"1","5"})
    public Counter(int val, int threshold) {

        this.val = val;
        this.threshold = threshold;
    }

    @Exemplar(instance="i1-5", instanceout="i2-5", expect="=(this.val,2)")
    public void increment() {

        if (val < threshold)
            val++;
    }

    @Exemplar(instance="i2-5", expect="=(this.val,1)")
    public void decrement() {

        if (val > 0)
            val--;
    }
}

 

3.4.3. Cross-Project Dependencies

When you refer to a named instance in a SIN Expression, you introduce a dependency from your Exemplar to the one that names the instance. The naming Exemplar can be in a different project, but only if that project is declared as a dependant project in your project’s Java Build Path. Eclipse will determine the build order of your projects based on their intra-project dependencies; the same is true of Sureassert.

If you change the code under the Exemplar that names an instance used in other projects, Sureassert is still able to automatically run the Exemplars from the other projects.

 

3.4.4. About Atomic Unit Tests

Best practice dictates that tests should be isolated and independent from one-another, or “atomic”. This means that no given test should depend upon another given test to run, i.e. you should be able to run any tests, in any order, and get the same results.

Sureassert may appear to allow users to break this principle by allowing one Exemplar to define a named instance, and another to depend upon that instance.

However, at this point the execution model must be considered. With Sureassert UC, every Exemplar can potentially have a tree of other Exemplars on which it is dependent, but it is not possible to execute such an Exemplar without executing its dependency tree, in the same way you might run a single unit test. The execution model mandates that before running any Exemplar, any dependent are run first. It does this automatically.

When you introduce a dependency on another Exemplar, the execution engine ensures that Exemplars are run in the correct order, regardless of whether you’re running the whole suite or any part of it. You could think of an Exemplar as “containing” as a preparatory step the actions of other Exemplars on which it’s dependant.

However, it’s important to note that by introducing multiple dependencies on a mutable object, you will run into problems. If dependent Exemplars change the state of the mutable object, the test results may differ depending on which of the multiple dependent Exemplars are executed first. This is a bad place to be.

Do no use a single mutable named instance in more than one other Exemplar. Where you need to do this, you should use a copy of the instance instead by using the re-execution postfix (“!”). See the next section for details.

 

3.4.5. Controlling Re-Execution, Execution Order, and Creating Instance Copies

While it is useful to work with instances returned by other Exemplars, often you will want to do this with many Exemplars, some of which could potentially alter the state of the instance. This is not desirable: in this situation the results of your tests would depend on which of the dependent Exemplars is executed first. You can control this with by explicitly declaring the order of execution with the depends property, but this approach is not recommended.

Instead, you should work with a new copy of the named instance in each dependent Exemplar. This is simple to achieve: just postfix the instance name with an explanation mark (!). This instructs Sureassert to re-execute the Exemplar that defines the named instance before returning the result. The potential drawback is that re-executing the Exemplar introduces more work for the Sureassert Engine which could eventually start to slow your test execution. As long as the method-under-test of the re-executed Exemplar doesn’t perform any expensive processing, this isn’t usually a concern.

3.4.5.1. Caution – avoiding multiple dependencies on a single instance

Here is an example of how not to do it. A simple class whose 2 methods both alter the object’s state:

public class Counter {

    private int val;
    private int threshold;

    @Exemplar(name="i1-2", args={"1","2"})
    public Counter(int val, int threshold) {

        this.val = val;
        this.threshold = threshold;
    }

    @Exemplar(instance="i1-2", expect="=(this.val,2)")
    public void increment() {

        if (val < threshold) 
            val++; 
    }

    @Exemplar(instance="i1-2", expect="=(this.val,0)")
    public void decrement() {

        if (val > 0)
            val--;
    }
}

We define one named instance for use when testing the class’s methods. However, it is not possible for both Exemplars to work as intended as they both work with the same instance of Counter (i1-2). Either the increment Exemplar will corrupt the test state for the decrement method, or vice-versa.

The execution order of the exemplars is undefined. You could try defining it with depends, but were there several other classes using i1-2, things would get messy.

3.4.5.2. Using instanceout to re-use a changing instance across a chain of Exemplars

In this example, you could solve the problem using only a single instance by referring to i1-2 only once, and defining instanceout, for instance:

public class Counter {

    private int val;
    private int threshold;

    @Exemplar(name="i1-2", args={"1","2"})
    public Counter(int val, int threshold) {

        this.val = val;
        this.threshold = threshold;
    }

    @Exemplar(instance="i1-2", instanceout="i2-2", expect="=(this.val,2)")
    public void increment() {

        if (val < threshold)
            val++;
    }     

    @Exemplar(instance="i2-2", instanceout="i1-2", expect="=(this.val,1)")
    public void decrement() {

        if (val > 0)
            val--;
    }
}

This may be the best approach if you’re happy to chain your tests, but isn’t always possible when things get complicated.

As a general rule, it isn’t good practice to directly use mutable named instances in other Exemplars.

3.4.5.3. Best practice – using the re-execution postfix

Instead, you should just create a new copy of i1-2 for each Exemplar to use. This means re-executing the i1-2 Exemplar. The engine takes care of this: you just need to add the re-execution postfix (“!”) to the instance name:

public class Counter {

    private int val;
    private int threshold;

    @Exemplar(name="i1-2", args={"1","2"})
    public Counter(int val, int threshold) {

        this.val = val;
        this.threshold = threshold;
    }

    @Exemplar(instance="i1-2!", expect="=(this.val,2)")
    public void increment() {

        if (val < threshold)             
                val++;     
    } 

    @Exemplar(instance="i1-2!", expect="=(this.val,0)")
    public void decrement() {

        if (val > 0)
            val--;
    }
}

When the Sureassert Engine re-executes an Exemplar to get a new named instance, it doesn’t re-evaluate the expects conditions as this will have already been done.

Note you can also re-execute Exemplars with a named instanceout – this is particularly useful where you use Exemplars to initialize the state of an Object and you want to re-use that state in several other Exemplars. For example:

public class Consignment {

    private int consignmentID;
    private Address address;
    private int deliveryStatus;

    @Exemplar(name="preinit", args={"1"})
    public Consignment(int consignmentID) {

        this.consignmentID = consignmentID;
    }

    @Exemplar(instance="preinit!", args={"'5a'","'AB1234'"}, instanceout="i1")
    public void setAddress(String houseNumber, String postalCode) {

        PostalCodeService postalCodeService = new PostalCodeService();
        address = postalCodeService.getAddress(houseNumber, postalCode);
        if (address == null) {
            address = new Address(houseNumber, "-", "-", "-", postalCode, "-");
        }
    }

    @Exemplars(set={
    @Exemplar(instance="i1!", args="1"),
    @Exemplar(instance="i1!", args="2") })
    public void deliver(int deliveryFirmID) {

        // Update deliveryStatus and/or other state
    }

}

In this example the deliver method alters the state of the Consignment object. We want to test it twice with 2 Exemplars for 2 different delivery firm IDs. Both of these Exemplars need to run with the instance with the set address (i1), but because deliver alters the state of the instance, it’s important each of the Exemplars is run on its own test instance (object-under-test). This is typically the case with any mutable objects (i.e. objects whose state can change). Therefore we use the re-execution postfix (i1!) to use a new copy of the instance configured by the Exemplar on setAddress.

The Sureassert UC Engine executes the Exemplars on methods in this example in the following order:


Execute Exemplar on constructor

1. Consignment

Execute Exemplar on setAddress

2. Consignment re-executed

3. setAddress

Execute Exemplar #1 on deliver

4. Consignment re-executed

5. setAddress re-executed

6. deliver

Execute Exemplar #2 on deliver

7. Consignment re-executed

8. setAddress re-executed

9. deliver

3.4.5.4. Storing and Using the Re-Executed Instance

When using the re-execution postfix (“!”), it is possible to store the result as a new named instance. This is achieved by adding a further name after the postfix e.g. i1!a. Adding a name after the “!” postfix has the effect of re-executing the Exemplar i1 in the usual way, and storing the result to i1!a. You can then refer to i1!a as a standard named instance elsewhere.

Previous Page   |   Next Page   |   Table of Contents

Comments are closed.