2009 Implementers Group Meeting Program Development Best Practices

Development Best Practices

Venue: Breakaway 1

Unit testing - automated framework to test applications.

A unit test is one small unit of work. Functional tests to test a range of actions.

  • Define tests w/ specific names - easy to write and debug
  • Junit4 - add @Test annotation to the class
  • Behaviour-driven tests - testing one use case "shouldGetAllDeclairdedFueldsGivenChoice"
    • OpenMRS wrote class to link actual method to test
    • look at javadoc in example class - see @should annotation - @should get all declared fields on given class
    • Eclipse plugin to generate tests from the @should's - creates stub test class
    • Right-click Run as Unit test, displays unit test view.
      • String comparison in right panel
      • In example, the test already identified a potential NPE
      • To run a whole package, right-click on the package in Project explorer and select Run as junit test.
      • In openmrs, we do not check in failing unit tests

2 use cases when tests

  • New user - help her check if her changes are ok.
  • Merging branches from other people

Takes 2 mins to run through entire suite.

Ant task - provides html output from tests.
Continuous integration server.

Writing a new unit test (example)

All of the implementations should have the same "@shoulds" as their interfaces, so the @shoulds should be on the interface.

In-memory database in the test framework.

  • contains demo data: 1-2 rows per table
  • Dataset found in test framework.
  • db defined w/ xml and uses dbunit

In example, line 186 - form definition - form 1

In unit test:

@Test
@Verified
public void getForm_shouldReturnGivenFormNotNull() {
Assert.assertNotNull("Form should not be null", Context.getFormService.getForm(1));
}

To add more fields to test, you may add another xml file w/ the fields
Copy and paste existing xml file and change the rows

Adding a new method:

Best practice:
Add @should's as you are writing code.

Example javadoc:

@param argument1
@param argument2
@should not fail given name
@should get forms that start with names
@should not get forms that only contain names
public List getFormsByName(String name);

Open FormServiceImpl, use overwrite/implement method

Return to FormService
Use plugin to generate tests from @shoulds

Best practice:

  • If your unit test is not doing and assert, it is not doing any good...
  • Don't use System.out.println...
  • Only asserts and exceptions deliver reds and greens (aka failures and successes) in junit.
  • The more specific the assert the better.

Magic :
Shows field answers and form table contents

TestUtil.printOutTableContents(getConnection(), "field_answer")

5 sec startup time to create database and the FormService object via Spring

Adding to Initial Data Set

To add something to field answer table and dump to an xml file:
This uses the DB defined in runtime.properties
In TestUtil.createIntitialDataset class
modify this class.- change the initialDataset.addTable.to point to the table
Run like a normal unit test
creates a new output.xml file describing the rows

need to remove @ignore from CreateInitialDataset - a junit annotation

To run this dataset before I run the unit test.
This test extends BaseContextSensitiveTest, which provides useful method - executeDataset
takes package hierarchy of the file.
uses INITIAL_FIELDS_XML

Best practice:

  • Shouldn't execute test against your db, use the in-memory db (which is set up with the xml files)
  • If you run against your own mysql db, someone else may not be able to use your test if they don't have mysql and your specific dataset

Error message - the in-memory db does not know column - define in hibernate mapping.

Every time test runs, it will roll back changes to the database. Resets db.

@Verifies - created by eclipse plugin to figure out which unit test has already been created

  • org.openmrs.test
  • Tests named same as interface w/ "Test" appended.
  • Not required by Junit.