JUnit testing

JUnit is a Java framework that supports writing unit tests that help ensure your code works as desired, and existing code is not broken by new changes.  It is often useful to create JUnit tests during development to verify that your code works as expected, and to keep and rerun the tests in the future to ensure that later changes in your (or someone else’s code) don’t unexpectedly break your code.

More information on JUnit testing philosophy is available at JUnit.org.

Note.  This document assumes that you use Eclipse.  However you can choose to use different IDE but then you have to find how to achieve the equivalent functionality that Eclipse provides.

Assuming you have an existing JUnit test class, you can execute them directly within Eclipse by:

·         Right-clicking on the class in Package Explorer

·         Run -> JUnit Test

All the tests for an application can be run from Eclipse by running the com.splwg.AllTests class in the "test" directory as a JUnit test.

Standard test cases

There are framework classes that are helpful for specific test cases:

Contents

Testing Searches

Testing Maintenance Classes

Testing Entity Page Maintenance Classes

Testing Business Entity Validation

Test handleChange / handleAdd / etc code

Testing for Warnings

Testing Searches

There is a convenient test superclass for search services, com.splwg.base.api.testers.SearchTestCase. This test class only requires that you override two methods:

·         String getServiceName() - this method specifies the service name, eg CILCACCS, for the search

·         List getSearchTrials() - this method should return a list of SearchTrials

A search trial describes information about a particular invocation of a search. You need to describe the inputs (the input fields and the search type), and then describe the expected output for that given input:

·         Some expected rows, in the order expected

In order to properly test searches, the expected results is not required to contain every search result- if new rows are added by some other process, they will not cause the test to fail. The search results, however, must contain at least all of the expected results, in the relative order they are added.

·         Possibly some prohibited rows, which the search should not find

In addition, there may be times when you want to guarantee that a certain row is definitely NOT found in the search result. This can be accomplished by adding a prohibitedRow, in the same manner as expected rows are added to the trial.

The search test FW will then use inputs from each search trial to execute the search, and compare the expected and prohibited results to the actual search results. It expects to find the expected rows in the order added, and should find all of them. Any different order or missing row results in a failure. What will not result in a test failure is if new rows have been added interspersed throughout the expected rows. These are fine. If a given search result row does not match the next expected result row, it is compared against all of the prohibited rows. If it matches any of them, the test fails.

The search framework will also examine the information about the search, and ensure that each search type (main, alternate, alternate2, ...) is executed at least once.

Here is a sample search test class:

package com.splwg.base.domain.batch.batchControl;

 

import com.splwg.base.api.lookup.SearchTypeLookup;

import com.splwg.base.api.testers.SearchTestCase;

import com.splwg.base.api.testers.SearchTestResult;

import com.splwg.base.api.testers.SearchTrial;

 

import java.util.ArrayList;

import java.util.List;

 

/**

 * @author bosorio

 * @version $Revision: #3 $

 */

public class BatchControlSearchService_Test

    extends SearchTestCase {

 

    //~ Methods ----------------------------------------------------------

 

    protected String getServiceName() {

        return "CILTBTCS";

    }

 

    /**

     * @see com.splwg.base.api.testers.SearchTestCase#getSearchTrials()

     */

    protected List getSearchTrials() {

        List list = new ArrayList();

 

        // Search using Main Criteria

        SearchTrial trial = new SearchTrial("Main search");

        trial.setSearchType(SearchTypeLookup.constants.MAIN);

 

        trial.addInput(BatchControlSearchService.INPUT_MAIN.BATCH_CD,

              "ADM");

        SearchTestResult expectedResult = trial.newExpectedResult();

        expectedResult.put(BatchControlSearchService.RESULT.BATCH_CD,

              "ADM");

        list.add(trial);

 

        // Search using Alternate Criteria

       

        trial = new SearchTrial("Search by description");

        trial.setSearchType(SearchTypeLookup.constants.ALTERNATE);

 

        trial.addInput(BatchControlSearchService.INPUT_ALT.DESCR,

              "AcCount D");

        expectedResult = trial.newExpectedResult();

        expectedResult.put(BatchControlSearchService.RESULT.BATCH_CD,

              "ADM");

        expectedResult.put(BatchControlSearchService.RESULT.DESCR,

              "Account debt monitor");

        list.add(trial);

 

        return list;

    }

}

Testing Maintenance Classes

There is a convenient test superclass for entity page maintenance, com.splwg.base.api.testers.EntityPageServiceTestCase. This test class requires several methods to be implemented to handle setting up the data and validating for each action (Add, Read, Change, Delete).

In case your maintenance doesn't support add and delete, i.e. it's read and change only, then implement this method:

    protected boolean isReadAndChangeOnly() {

        return true;

    }

 

The test framework will only exercise the read action.

Your maintenance test class must provide the name of the service being tested, eg:

    protected String getServiceName() {

        return "CILTBTCP";

    }

 

Contents

Testing Add on Maintenance Class

Testing Change on Maintenance Class

Testing Delete on Maintenance Class

Test default actions on Maintenance Class

Testing Add on Maintenance Class

First, in order to test an add, we need the data to add. This is provided in the method protected PageBody getNewEntity(). Here is an example:

protected PageBody getNewEntity() {

        PageBody body = new PageBody();

        body.put(Maintenance.STRUCTURE.BATCH_CD, "ZZTEST2");

        body.put(Maintenance.STRUCTURE.PROGRAM_NAME, "ZZPROG");

        body.put(Maintenance.STRUCTURE.ACCUM_ALL_INST_SW, Boolean.FALSE);

        body.put(Maintenance.STRUCTURE.DESCR, "Test service");

        body.put(Maintenance.STRUCTURE.LAST_UPDATE_DTTM,

             LAST_UPDATE_TIMESTAMP);

        body.put(Maintenance.STRUCTURE.LAST_UPDATE_INST, BigInteger.ZERO);

        body.put(Maintenance.STRUCTURE.NEXT_BATCH_NBR, BigInteger.ZERO);

 

        ItemList itemList =  body.newItemList

             (Maintenance.STRUCTURE.list_BCP.name);

        ListBody listBody = itemList.newListBody();

 

        listBody.put(Maintenance.STRUCTURE.list_BCP.BATCH_CD, "ZZTEST2");

        listBody.put(Maintenance.STRUCTURE.list_BCP.SEQ_NUM,

             BigInteger.valueOf(10));

        listBody.put(Maintenance.STRUCTURE.list_BCP.BATCH_PARM_NAME,

             "param1");

        listBody.put(Maintenance.STRUCTURE.list_BCP.BATCH_PARM_VAL, "val1");

        listBody.put(Maintenance.STRUCTURE.list_BCP.REQUIRED_SW,

             Boolean.FALSE);

        listBody.put(Maintenance.STRUCTURE.list_BCP.DESCR50, "Parameter 1");

        listBody.prepareToAdd();

 

        return body;

}

 

(This may look like an awful lot of typing, but any IDE like e.g. Eclipse that offers code-completion will make this kind of code entry very quick).

If the maintenance performs some server-side "defaulting" (changing of the data), and the result after the add differs from the data above, you will need to override protected PageBody getNewReadEntity(PageBody original). This method gets the original data from the method above, and allows manipulation to bring it to the expected form after a read from the database.

In order to actually perform the read, the read header should be specified in protected abstract PageHeader getReadHeader(). For example:

    protected PageHeader getReadHeader() {

        PageHeader header = new PageHeader();

        header.put(Maintenance.HEADER.BATCH_CD, "ZZTEST2");

        return header;

    }

 

Testing Change on Maintenance Class

Next, a new read is performed (using the same read header above), and you can perform a change to the page body in the method:

protected PageBody changedPageBody(PageBody original).


Here is an example:

    protected PageBody changedPageBody(PageBody original) {

        original.put(Maintenance.STRUCTURE.ACCUM_ALL_INST_SW, Boolean.TRUE);

 

        ItemList list = original.getList("BCP");

        ListBody param = (ListBody) list.getList().get(0);

        param.put(Maintenance.STRUCTURE.list_BCP.DESCR50,

             "Changed parameter 1");

        param.prepareToChange();

 

        return original;

    }

 

A read is performed after the above changes are sent, and the results are compared.

Testing Delete on Maintenance Class

Finally, a delete is issued on the data, and it is verified that the entity no longer exists.

Test default actions on Maintenance Class

In addition, all defaults that are registered for a page maintenance must also be tested. This should be done through separate tester methods for each default, calling the FW support method public PageBody executeDefault(PageBody pageBody, String defaultValue) :

    public void testDefaultChg() {

        PageBody input = new PageBody();

 

        // TODO populate inputs for default

        // e.g.

        input.put(Maintenance.STRUCTURE.FK, "FK CODE");

        PageBody output = executeDefault(input, Maintenance.DEFAULTS.CHG);

 

        // TODO compare the outputs

        //  e.g.

        assertEquals("FK Description",

             output.get(Maintenance.STRUCTURE.FK_DESCR));

    }

 

Here is an example to test the default on a field under a list.

 public void testDefaultAlogrithm() {

        PageBody input = new PageBody();

 

        ItemList itemList = input.newItemList

             (Maintenance.STRUCTURE.list_MRRA.name);

        ListBody listBody = itemList.newListBody();

        listBody.put(Maintenance.STRUCTURE.list_MRRA.MRR_ACTN_ALG_CD,

             "MRRCRESVCCC");

 

        PageBody output = executeDefault(input, Maintenance.DEFAULTS.AAD);

        ItemList outList = output.getList

             (Maintenance.STRUCTURE.list_MRRA.name);

        ListBody body = (ListBody) outList.getList().get(0);

        assertEquals(body.get(Maintenance.STRUCTURE.list_MRRA.MRRA_DESCR),

             "Create Service Customer Contact");

    }

 

The input page body should be populated with the expected inputs for the default action, while the output should be compared against the expected output.

 

Testing Entity Page Maintenance Classes

There is a convenient test superclass for entity page maintenance, com.splwg.base.api.testers.EntityListPageTestCase.
This test class requires several methods to be implemented to handle setting up the data and validating for each action (Add, Read, Change, Delete).

The maintenance test class must provide the name of the service being tested, eg:

    protected String getServiceName() {

        return "CILTBTCP";

    }

 

Contents

Testing Add on Entity Page Maintenance Class

Testing Change on Entity Page Maintenance Class

The Comparisons

Test default actions on Entity Page Maintenance Class

Testing Add on Entity Page Maintenance Class

First, in order to test an add, we need the data to add. This is provided in the method protected void populateRowForAdd(ListBody row). Here is an example:

protected void populateRowForAdd(ListBody row) {

        row.put("DESCR50", "description");

        row.put("XAI_IN_SVC_ID", "$");

}

 

We also need to know the ID field, and an example ID, eg

    protected String getMainHeaderField() {

        return "NT_DWN_TYPE_CD";

    }

 

    protected StringId getTestId() {

        return new NotificationDownloadType_Id("FOO");

    }

 

Testing Change on Entity Page Maintenance Class

Also, a change is attempted, using the same keyed row given by the testId method above.

    protected void populateChangedRow(ListBody row) {

        row.put("DESCR50", "changed description");

        row.put("XAI_IN_SVC_ID", "#");

    }

 

The Comparisons

After the adds and changes above (also a delete is done), the state of the row is compared against the new row. By default, the framework implementations should work fine, and you don't need to do anything. However, in the rare case, you may need to override the following methods:

protected void compareAddedRow(ListBody originalListBody,

    ListBody newListBody)

protected void compareChangedRow(ListBody originalListBody,

    ListBody newListBody)

Test default actions on Entity Page Maintenance Class

In addition, all defaults that are registered for a page maintenance must also be tested. This should be done through separate tester methods for each default, calling the FW support method public PageBody executeDefault(PageBody pageBody, String defaultValue) :

    public void testDefaultChg() {

        PageBody input = new PageBody();

 

        // TODO populate inputs for default

        // e.g.

        input.put("FK", "FK CODE");

        PageBody output = executeDefault(input, "CHG");

 

        // TODO compare the outputs

        //  e.g.

        assertEquals("FK Description", output.get("FK_DESCR"));

    }

 

Another example for testing the default on the field which in on the list.

 public void testDefaultAlogrithm() {

        ItemList itemList = new ItemList();

        itemList.setName("MRRA");

        List list = new ArrayList();

        itemList.setList(list);

        ListBody listBody = new ListBody();

        listBody.put("MRR_ACTN_ALG_CD", "MRRCRESVCCC");

        list.add(listBody);

 

        PageBody input = new PageBody();

        input.addList(itemList);

 

        PageBody output = executeDefault(input, "AAD");

        ItemList outList = output.getList("MRRA");

        List outputList = outList.getList();

        ListBody body = (ListBody) outList.getList().get(0);

        assertEquals(body.get("MRRA_DESCR"),

            "Create Service Customer Contact");

    }

 

The input page body should be populated with the expected inputs for the default action, while the output should be compared against the expected output.

Testing Business Entity Validation

To test our validation, a test class needs to be created. The one-off generation process has created one for each of the existing entities in the system. The following is the one it created for the Characteristic Type entity:

public class CharacteristicType_Test extends AbstractEntityTestCase {

 

    private static Logger logger = LoggerFactory.getLogger(CharacteristicType_Test.class);

 

    /**

      * @see com.splwg.base.api.testers.AbstractEntityTestCase#getChangeHandlerClass()

      */

    protected Class getChangeHandlerClass() {

        return CharacteristicType_CHandler.class;

    }

}

 

This is a JUnit test case. Let's run it. From within Eclipse, right-click on the test class from within the Package Explorer.


Below is the resulting output from JUnit:

JUnitFailsForPlaceHolders.JPG

As we see, the tests failed and told us that none of our three validation rules where validated. This is, of course true, but some explanation is necessary. When we run entity test cases, the framework looks up the change handler class being tested and collects all of its rules. Then it executes all the tests in the test class (basically every method starting with "test*"). At the end of each test, it looks to see if the last rule violated was one of the rules we are testing. At the end of all the tests, if there are still validation rules that weren't violated, the framework complains. At a minimum, the goal from this point is to create tests that violate each of our rules at least once. Preferably, tests should be created to violate the rules for all additional conditions that we can think of that might compromize the state of the entity.

Let's start fixing our tests with the third rule above the "Foreign Key Reference is required for FK Characteristic Values" rule. With a little head-scratching we determine that this is a "RequireRule" and we replace it as shown below:

public static ValidationRule

        foreignKeyReferencesRequiredForFkCharValueRule(){

    return RequireRule...someFactoryMethod...(

        "CharacteristicType:Foreign Key Reference is required for FK          

             Characteristic Values",

        "If the Characteristic Type Lookup is 'Foreign Key Value' then the

             Foreign Key Reference Cd is required",

      ... some fancy stuff ....

        fkReferencesRequiredForFKCharacteristicValueMessage);

}

 

Here's the test that was added to the test class to test it:

    /** Test foreignKeyReferencesRequiredForFkCharValueRule  */

    public void testFKReferencesOnlyForFKCharacteristics() {

        // create a new characteristic type

        CharacteristicType charType = createNewTestObject();

        CharacteristicType_DTO charTypeDTO = charType.getDTO();

 

        // set the characteristic value to null for some other type

        charTypeDTO.setForeignKeyReference("");

        charTypeDTO.setCharacteristicType

              (CharacteristicTypeLookup.PREDEFINEDVALUE);

 

        // this should be OK

        charType.setDTO(charTypeDTO);

 

        // Now make it a FK characteristic.  This should violated the rule

        charTypeDTO.setCharacteristicType

              (CharacteristicTypeLookup.FOREIGNKEYVALUE);

        try {

            charType.setDTO(charTypeDTO);

            fail("An error should have been thrown");

        } catch (ApplicationException e) {

            // Make sure the correct rule was violated.

            VerifyViolatedRule

               (CharacteristicType_CHandler.

               foreignKeyReferencesRequiredForFkCharValueRule());

        }

    }

 

Important note: Both a valid test AND an invalid test were added to the above method.

Finally, when the test is rerun, we have one less validation rule needing to be violated.

OneLessError.JPG

 

Iterate Until Done

Test handleChange / handleAdd / etc code

Although there is no way to enforce testing of any coding in any of the methods

HandleRegisteredChange

     (BusinessEntity changedBusinessEntity,

      RegisteredChangeDetail changeDetail)

 

handleAdd(BusinessEntity newBusinessEntity)

 

handleChange(BusinessEntity changedEntity, DataTransferObject oldDTO)

 

It is still imperative that this code should also be exercised AND verified when testing the change handler. Please ensure that every path through these methods is exercised and the results verified.

In general, there is a specific set of classes or functionality that is required to have explicitly defined tests.

·         Every entity (and entity extension) class must have each of its validation rules explicitly tested. That is, each rule should fail once, with an explicit acknowledgement of the failed rule expected.

·         Every service must have a test.

·         Searches must test each search type once.

·         "Page" services must test their complete cycle that are available.

·         Queries must test read

·         Maintenances must test add/change/read/delete

·         Every maintenance extension must have a test class

·         Every algorithm implementation must have a test

Note.  Currently, the above "must have" tests may still not completely cover all the cases. For example, one search type may have several inputs, which trigger different code or queries to be executed. The testing FW as is can not know this, so only requires a single test case for that search type. However, it is strongly recommended that each specialized case possible be modeled with a test case, in order to achieve complete code coverage.

Note.  In addition, there is a desire to assure that each business component or business entity method has been tested. Currently these tests are not required. However, after a complete build server run, any business component methods or business entity methods that have not been explicitly tested will be reported.

Testing for Warnings

In both maintenances and entity change handlers, there is the possibility of issuing a warning. This code should be tested just as well as any other entity validation or default action.

Contents

Maintenances

Entity tests

Maintenances

Here is complete valid example of verifying that a maintenance default action issues a proper warning.

    public void testDefaultDEFAULT_FOR_ZONE_HNDL() {

        PageBody input = new PageBody();

        input.put(ContentZoneMaintenance.STRUCTURE.ZONE_CD, "CI_AFH");

 

        // test the default and expect to get a warning

        try {

            executeDefault(input, "ZH");

            fail("Should have a warning");

        } catch (ApplicationWarning e) {

            verifyWarningContains(e,

               MessageRepository.deleteZoneParametersWarning());

        }

 

        disableWarnings();

 

        // test the default and do not expect to get a warning or error

        PageBody output = executeDefault(input, "ZH");

        assertEquals(Boolean.TRUE, output.get("DELETE_SW"));

    }

 

Note.  By default, warnings are enabled, thus nothing special need be done. But you should put the normal try/catch block around the default execution, and catch an application warning. Once inside the catch block, you should verify that the warning(s) is/are valid expected ones. (This comparison is only done via the message category and message number. Thus, if there are parameters to the message construction, it matters not their values, since it may be difficult to get the values.)  Then, you should retry the default with warnings disabled, and ensure that you get the behavior otherwise expected.

Entity tests

There was no current use of warnings in entity tests that I could easily "improve", so for now I use a slightly contrived example. (This is slightly contrived, because Installation is a special record, and the change below is not actually allowed in the application do to some records on the Adjustment Type table, and a valdiation on Installation.)

    public void testChangeBillSegmentFreeze() {

        Installation installation = getValidTestObject();

        Installation_DTO instDto = (Installation_DTO) installation.getDTO();

 

        instDto.setBillSegmentFreezeOption

             (BillSegmentFreezeOptionLookup.FREEZE_AT_WILL);

        installation.setDTO(instDto);

 

        instDto.setBillSegmentFreezeOption

             (BillSegmentFreezeOptionLookup.FREEZE_AT_BILL_COMPLETION);

        installation.setDTO(instDto);

        verifyWarningsContain

             (MessageRepository.changeBillSegmentFreezeWarning());

    }

 

Again, by default warnings are enabled, so nothing need be stated at the outset. Additionally, the conversion of warnings to an exception occurs at a later point, so there is no ApplicationWarning to catch. Instead, after the offending statement (in this case the setDTO method) you should just verify that the current warnings contain the specified message.