# Test Driven Development using the AIMMSUnitTest Library¶

This article discusses some elements from the popular software methodology Test Driven Development in relation to the AIMMS Library AIMMSUnitTest.

1. Gather requirements from stakeholders
• What are small examples?
• What are the edge cases, including error cases, performance?
2. Implement requirements as unit tests and see them fail!
3. Development
1. Write the code for the functions
2. Execute tests and see them pass!
3. Refactor until performance is acceptable.
4. Repeat

## Unit tests in AIMMS projects¶

Prepare your AIMMS project with the below steps to declare unit tests.

1. Add repository library AIMMSUnitTest

2. Create a new library that holds the actual code

3. Create another new library containing tests on the actual code

4. Make sure all tests can be run, for instance by specifying MainExecution as shown below:

Procedure MainExecution {
Body: {
EnvironmentSetString("aimmsunit::RunAllTests","1");
aimmsunit::TestRunner;
}
}


## Example: implement “mean”¶

### Prototype the requirements¶

1. The mean should return the average value of a series of numbers

2. Prototype SelfDefinedMean (As both mean and average are key words in AIMMS, we need to come up with a new name):

Function SelfDefinedMean {
Arguments: (p);
Body: {
raise error "Not implemented yet";
SelfDefinedMean := 0 ;
}
Parameter p {
IndexDomain: i;
Property: Input;
}
Set S {
Index: i;
}
}


This is a dummy implementation, a function that meets the prototype requirements, but will obviously fail. Having a dummy implementation allows us to code the tests as detailed below.

### Write the tests¶

1. Small example test: Mean( 3, 5, 13 ) = 7

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Procedure pr_Test_Small_Example { Body: { S := data { a, b, c }; P := data { a: 3, b : 5, c: 13 }; r := ml::SelfDefinedMean( P(i)); aimmsunit::AssertTrue("The average of 3, 5, and 13 is 7.", r=7); } Comment: "first test: Mean( 3, 5, 13 ) = 7"" aimmsunit::TestSuite: MeanSuite; Set S { Index: i; } Parameter P { IndexDomain: i; } Parameter r; } 

Note that the aimmsunit::AssertTrue statement (line 6) is after the call to ml::SelfDefinedMean.

2. Edge case test: an empty series of numbers

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Procedure pr_Test_Empty_List { Body: { aimmsunit::AssertThrow("The average of an empty list cannot be computed."); S := data { }; P := data { }; r := ml::SelfDefinedMean(P(i)); } Comment: "Edge case, empty list."" aimmsunit::TestSuite: MeanSuite; Set S { Index: i; } Parameter P { IndexDomain: i; } Parameter r; } 

Note that the aimmsunit::AssertThrow(line 2) statement is **before** the call to ml::SelfDefinedMean.

The annotation aimmsunit::TestSuite: MeanSuite is added to the test function. You can add annotations this way:

1. Click add annotation in the attribute window
2. Select aimmsunit::TestSuite
3. Type in the name of the suite. In this example, we only use one suite: MeanSuite

Now, run the tests and with the above implementation of ml::SelfDefinedMean. They will fail as expected. Example result in file: log/AimmsUnit.xml

  1 2 3 4 5 6 7 8 9 10 11  

1. On line 3, which suite and which tests are run, it is also important the number of tests that failed. All the tests failed as expected (errors =”2”) and we can start coding the function now.
2. In lines 4 - 9, we see the details of the failure of our two tests. As the function hasn’t been implemented yet, it raised an error message in both the tests.

### Code the function¶

Mean is calculated by dividing the sum of the records by the count of records. This is implemented in the code below:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  Function SelfDefinedMean { Arguments: (p); Body: { p_NoElements := card(p); if p_NoElements then SelfDefinedMean := sum( i, p(i) ) / p_NoElements; else raise error "The average of an empty list cannot be computed." ; SelfDefinedMean := 0 ; endif ; } Parameter p { IndexDomain: i; Property: Input; } Set S { Index: i; } Parameter p_NoElements; } 

Running the test now gives the following results:

 1 2 3 4 5 6 7  

The log indicates that both the tests passed without any issue. So, everything is good to go. Or is it?

### Fix a bug¶

However, soon one of our stakeholders comes with a question:

Why does ml::SelfDefinedMean(3, 5, 0, 12) return 6.67 instead of 5?

Apparently, our set of requirements does not consider all edge cases. Now we will iterate on this by adding another requirement and test:

0 is a possible observation, and should count in the number of observations. So, SelfDefinedMean(3, 5, 0, 12) = 5
  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17  Procedure pr_Test_Zero_In_Observations { Body: { S := data { a, b, c, d }; P := data { a: 3, b : 5, c: 0, d: 12 }; r := ml::SelfDefinedMean(P(i)); aimmsunit::AssertTrue("The average of 3, 5, 0, and 12 is 5.", r=5); } Comment: "third test: Mean( 3, 5, 0, 12 ) = 5"" aimmsunit::TestSuite: MeanSuite; Set S { Index: i; } Parameter P { IndexDomain: i; } Parameter r; } 

Running the test suite again gives the below result:

  1 2 3 4 5 6 7 8 9 10  

Our unit test reproduces the bug. See failures=”1” in line 3. Notice the difference between failures and errors in the test report. Clearly, the mistake in the above implementation is that we divided by card(P) - the cardinality of the parameter which only counts non default values instead of card(S) - the cardinality of the set which counts all the elements. So, the function is updated as shown below:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20  Function SelfDefinedMean { Arguments: (p); Body: { p_NoElements := card(S); if p_NoElements then SelfDefinedMean := sum( i, p(i) ) / p_NoElements; else raise error "The average of an empty list cannot be computed." ; SelfDefinedMean := 0 ; endif ; } Parameter p { IndexDomain: i; Property: Input; } Set S { Index: i; } Parameter p_NoElements; } 

Running the test suite now should give the below result which indicates that the problem was fixed.

 1 2 3 4 5 6 7 8  

All the previously written tests (before this latest change) were also automatically run, saving us time and effort. The example project can be downloaded below:

AIMMS project download