Guided Tour

To visualize the functionality of the unit testing framework, we will start with a guided tour in which we create our first unit and test it with the unit testing framework. The tour will cover the following steps:

Please make sure that the framework has been properly installed if you wish to follow the guide. For the framework to work, the files from the procedures folder should be placed into the User Procedures Folder of your Igor Pro setup.

Creating a unit

We will start by creating a simple unit.

The following formula gives the diameter \(d\) of a carbon nanotube:

\[d = \frac{a_0}{\pi}\cdot\sqrt{n^2+m^2+nm}\]

The natural numbers \(n\) and \(m\) define the carbon nanotube type. \(a_0\) is the unit cell lattice constant of graphene (Understanding the background of the above formula is not required here).

The formula is easily translated into Igor Pro code:

Procedure
1
2
3
4
5
6
7
8
9
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3

// calculate carbon nanotube diameters
Function diameter(n, m)
    Variable n, m

    return 0.144 / 3.1415 * (3 * (n^2 + n*m + m^2))^(0.5)
End

Testing the unit

If we want to rely on this formula with other calculations, we have to test if the output of this function is both correct and within our required accuracy range. To perform these two tests, we define a Test Case.

test0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3

#include "unit-testing"

Function testDiameter()
    // the (6,5) type is 0.757nm in diameter
    REQUIRE_CLOSE_VAR(diameter(6, 5), 0.757, tol=1e-3)
    // this is the same value as for the (9,1) type.
    REQUIRE_EQUAL_VAR(diameter(6, 5), diameter(9, 1))
End

The test case testDiameter contains two checks. Both are required to pass the test suite. In the context of this framework we will refer to them as assertions. The first assertion REQUIRE_CLOSE_VAR compares the two floating point numbers within the given tolerance of 0.001nm. The second REQUIRE_EQUAL_VAR uses a mathematical peculiarity of the above formula to check if the calculation gives correct output.

The test case function can be placed anywhere inside the main procedure file, but it can be considered good practice to separate test cases into a procedure file of their own. Such a separate procedure file that only contains test cases is called a Test Suite. A test suite can for example perform all the necessary tests for a unit.

Executing the test

To execute the test suite we use the RunTest() directive. It accepts the name of our test suite (the procedure window) as an argument. In our example we have named the procedure window "test0".

•RunTest("test0")
  Start of test "Unnamed"
  Entering test suite "Unnamed"
  Entering test case "testDiameter"
  Leaving test case "testDiameter"
  Finished with no errors
  Leaving test suite "test0"
  Test finished with no errors
  End of test "Unnamed"

In the cosole output above, the highlighted line indicates that all tests within the current test suite have passed successfully. The unit is working properly. The full Igor Pro environment with our unit test should look like this:

_images/introduction-demo.png

Extending the test

Note, that we have defined a test case for the current capabilities of our function diameter(). The calculation is only exact up to the specified error range. The high error is caused by a fixated value of pi=3.1415. To emphasize this, we can add an assertion to the test case that will fail but will not affect the error counter. Such an assertion is done with a WARN_* directive. Every REQUIRE_* assertion also has a WARN_* variant, see:ref:AssertionTypes for a summary.

Function testDiameter()
    // the (6,5) type is 0.757nm in diameter
    REQUIRE_CLOSE_VAR(diameter(6, 5), 0.757, tol=1e-3)
    // this is the same value as for the (9,1) type.
    REQUIRE_EQUAL_VAR(diameter(6, 5), diameter(9, 1))
    // warn if accuracy is not exact
    WARN_CLOSE_VAR(diameter(6, 5), 0.7573453, tol=1e-7)
End

The output of RunTest() will now include a warning assertion without failing the test case:

•RunTest("test0")
  Start of test "Unnamed"
  Entering test suite "Unnamed"
  Entering test case "testDiameter"
  Entering test case "testDiameter"
  0.757368 ~ 0.757345 with strong check and tol 1e-07: is false
  Assertion "WARN_CLOSE_VAR(diameter(6, 5), 0.7573453, tol=1e-7)" failed in line 11, procedure "test0"
  Leaving test case "testDiameter"
  Finished with no errors
  Leaving test suite "test0"
  Test finished with no errors
  End of test "Unnamed"

If the program should be extended to a higher level of accuracy, this warning can be set to the corresponding REQUIRE assertion. The program diameter then has to be changed to reflect the new requirement. In the current example, \(pi\) would need to be used instead of only a handful of decimal places hardcoded.

In a test-driven workflow, the unit tests get extended before even changing anything at the code base. Defining the test case prior to any code production assures that the software development is not producing unnecessary (and untested) code.

A more elaborate example for defining a peak find functionality can be found in the examples section. For a quick start, also have a look at the first example.