Testing ASTRA Programs

The astra-unittest library lets you write unit tests for ASTRA agents in ASTRA. A test is an agent program that inherits the behaviour you want to test, and each test is a goal that the test agent tries to achieve: if the goal succeeds the test passes, and if it fails the test fails.

Setting up a project

Add the astra-unittest library to your project’s pom.xml:

<dependencies>
    <dependency>
        <groupId>com.astralanguage</groupId>
        <artifactId>astra-unittest</artifactId>
        <version>2.0.13</version>
    </dependency>
</dependencies>

If your project uses astra-base as its parent (as projects created from the archetype do), nothing else is needed: the parent already configures Maven to compile tests and run them.

Test agents go in the src/test/astra folder, next to your normal code in src/main/astra:

|-- src
|    |-- main
|    |     |-- astra
|    |           |-- calc
|    |                 |-- Calculator.astra
|    |-- test
|          |-- astra
|                |-- tests
|                      |-- CalculatorTests.astra
|-- pom.xml

Put both your agents and your tests in packages (here calc and tests). A test agent in a package cannot inherit from an agent in the default package, because of the way ASTRA programs are compiled to Java.

Writing a test

Suppose we want to test this agent:

package calc;

agent Calculator {
    types calc {
        formula total(int);
    }

    initial total(0);

    plan +!add(int X) : total(int T) {
        -total(T);
        +total(T + X);
    }

    plan +!square(int X, int R) {
        R = X * X;
    }
}

A test agent extends astra.unit.ASTRAUnitTest as well as the agent that it tests. ASTRAUnitTest provides a module called UT with the assertions described below.

package tests;

import astra.unit.*;

agent CalculatorTests extends ASTRAUnitTest, calc.Calculator {
    plan +!test_square(TestSuite suite) {
        !square(4, int R);
        UT.assertEquals(suite, 16, R);
    }

    plan +!test_add(TestSuite suite) : total(int T) {
        !add(5);
        query(total(int N));
        UT.assertEquals(suite, T + 5, N);
    }
}

The rules for writing tests are:

  • Each test is a plan for a goal whose name starts with test_ and which takes a single parameter, the TestSuite that is running the test. The suite is passed to every assertion.

  • A test passes if its goal succeeds, and fails if its goal fails. Assertions fail when the condition they check is false, which makes the test goal fail. Anything else that fails, such as a query(...) or a sub-goal, also fails the test.

  • Tests are run one after another, in alphabetical order of their goals, by a single test agent. Beliefs that one test changes are still there for the next one, so do not rely on the order, or reset what you need in each test.

  • Optional +!setup(TestSuite suite) and +!teardown(TestSuite suite) plans run once, before the first test and after the last test of the test agent.

Assertions

The UT module provides the following actions. Each takes the TestSuite as its first argument.

Action

Checks that…

UT.assertEquals(suite, expected, actual)

two int, long or string values are equal

UT.assertTrue(suite, condition)

a boolean condition is true

UT.assertTrue(suite, message, condition)

as above, with a message to report if it is false

UT.assertBelief(suite, agent, belief)

the named agent currently holds the belief (a funct, e.g. total(5))

UT.success(suite)

(always passes) records that the test succeeded

UT.fail(suite), UT.fail(suite, message)

(always fails) records that the test failed

Two further actions help when testing agents that react to their beliefs:

Action

Effect

UT.injectBelief(suite, agent, belief)

adds the belief to the named agent

UT.extractBelief(suite, agent, belief)

removes the belief from the named agent

Testing multi-agent behaviour

A test can create other agents with the System module, interact with them, and remove them when it has finished. For example, this test (from the astra-protocols library, see Prewritten Protocols) checks that a request made with the FIPA Request protocol succeeds:

import astra.unit.*;

agent FIPARequestTests extends astra.unit.ASTRAUnitTest, astra.protocol.FipaRequest {
    plan +!test_request(TestSuite suite) {
        system.createAgent("opp", "HelloResponder");

        !request_action("opp", hello());

        system.terminateAgent("opp");
        UT.success(suite);
    }
}

Running the tests

Run the tests with:

mvn test

Maven compiles your agents and your test agents, then runs every agent that extends ASTRAUnitTest. While the tests run, the test runner prints progress messages for each test goal; at the end, it prints a summary:

TEST RESULTS:
====================================================
[PASSED] tests.CalculatorTests.test_add (steps: 1) -> 5 == 5
[PASSED] tests.CalculatorTests.test_square (steps: 5) -> 16 == 16
====================================================
results: passed 2 of 2 tests

For a failed test, the message after -> is the reason that the test goal failed, such as the assertion that did not hold.

Note

In ASTRA 2.0.13, mvn test does not find any test agents and reports passed 0 of 0 tests. In addition, when a project has more than one test agent, the tests of the first are wrongly run again for the others. Both are known issues in the 2.0.13 test runner. Also note that in 2.0.13 the FIPA Request test above passes without reaching UT.success(suite), because !request_action(...) does not return (see the note in Prewritten Protocols).