Developer Blog

Connect IQ 2: Run No Evil

07/26/16 @ 08:21 AM

Magic Monkey!
For more advanced testing and debugging capabilities, the Connect IQ 2.1 SDK adds Run No Evil, an automated unit testing framework found in the Test module. Run No Evil operates only within the Connect IQ simulator and provides the ability to add asserts and unit test methods to your app. These tools allow you, the developer, to create higher quality apps by letting us do the testing work for you!

Asserts

Asserts are a useful way to check for conditions at critical points in your code and will always execute when your app is launched in the simulator. For example, if your app always expects the value of x and y to not be equal:

function onShow() {
    var x = 1;
    var y = 1;
    // Prints an error to the console when x and y are equal
    Test.assertNotEqualMessage(x, y, "x and y are equal!");
}

The code above produces the following output in the console when the app is run in the Simulator:

Device Version 0.1.0
Device id 1 name "A garmin device"
Shell Version 0.1.0
ASSERTION FAILED: x and y are equal!

Assert code requires no special compiler commands to execute within the simulator, and are removed by the compiler when building release code. Run No Evil has four different assert flavors:

//! Assert throws an exception if the test is false
//! @param [Boolean] test Expression to test for true.
function assert(test);
 
//! Assert throws an exception if the test is false
//! @param [Boolean] test Expression to test for true.
//! @param [String] the identifying message for the assert.
function assertMessage(test, message);
 
//! Throws an exception if value1 and value2 are not equal
//! @param [Object] Value to test for equality
//! @param [Object] Value to test for equality
function assertNotEqual(value1, value2);
 
//! Throws an exception if value1 and value2 are not equal
//! @param [Object] Value to test for equality
//! @param [Object] Value to test for equality
//! @param [String] the identifying message for the assert.
function assertNotEqualMessage(value1, value2, message);

Unit Tests

Unit tests are a great way to check discrete pieces of your app for pass/fail criteria. Each test is run independently, so if a test fails or causes a crash, the test will be marked as a failed test and the next test will automatically be executed. This allows an entire suite of tests to be run with a single command in an automated fashion.

Unit tests are written mostly like any other class, module, or function in Monkey C, but have the following requirements:

    Tests methods must be marked with the :test annotation
    Test methods must take a Logger object
    Tests methods that are not global (part of a test class or custom test module) must be static methods

Here is a simple example of a unit test method:

// Unit test to check if 2 + 2 == 4
(:test)
function myUnitTest(logger) {
    var x = 2 + 2; logger.debug("x = " + x);
    return (x == 4); // returning true indicates pass, false indicates failure
}

The unit tests include a handy logger with different logging levels for more meaningful error reporting. The sample code above uses a “debug” logging level, but the Logger contains a total of three logging levels that can be used to distinguish between different types of errors in your unit test output:

//! Write a debug string to the output stream. The String is prefixed with [DEBUG] and time stamp
//! @param [String] Output string
function debug(str);
 
//! Write a warning string to the output stream. The String is prefixed with [WARNING] and time stamp
//! @param [String] Output string
function warning(str);
 
//! Write an error string to the output stream. The String is prefixed with [ERROR] and time stamp
//! @param [String] Error string
function error(str);

Unlike asserts, which always execute in the simulator, unit tests only run when the compiler is told to run them. Unit tests must be executed from the terminal and are not currently available in the Eclipse plug-in. Fortunately, it’s a straight-forward, three-step process:

    Build the Project: Use the—unit-test flag on the build command to compile with unit tests. It’s usually easiest to copy and paste the build command from the Eclipse console and add the unit test flag.
    Launch the Simulator: You must use the connectiq script in your SDK’s bin directory to launch the simulator from the terminal (no arguments required).
    Run the App: Use the monkeydo script in your SDK’s bin directory with the /t flag to run the app with unit tests enabled - monkeydo.bat path\to\projects\bin\MyApp.prg /t

You may also supply a function name after /t to run the test associated with a single function. The sample unit test above produces the following output in the console:

Device Version 0.1.0
Device id 1 name "A garmin device"
Shell Version 0.1.0
------------------------------------------------------------------------------
Executing test myUnitTest…
DEBUG (14:16): x = 4
Pass
==============================================================================
RESULTS
Test:                           Status:
myUnitTest                      Pass
Ran 1 test
 
PASSED (failures=0, errors=0)
Connection Finished
Closing shell and port

Unit test code will not execute unless the compiler is explicitly told to run unit tests. All test code is automatically removed at compile time when your app is exported for use on devices.

Test, Test, Test!

Unit tests and asserts are a great way of improving the quality of your application. Once written they are there to let you know if something is wrong as soon as it happens - think of them as the safety net below while you walk the tight rope of writing awesome new features for your apps. You can focus less on “Did I break something?” and more on “How do I do this crazy awesome thing?”, and so with unit tests you should also be asking yourself “Why not?”.

Categories: Connect IQ SDK