Cartesian Plane Lesson 16 Page 11: Testing the Line Properties Panel GUI, Functional Testing

LinePropertiesPanel, GUI Testing, Reflection

On this page we will develop the JUnit tests for validating the functional performance of the LinePropertiesPanel class. This will entail such tasks as making sure the values of the components in the panel match expected values maintained in the related LinePropertySets. Visual testing of the panel, such as making sure components are visible and displayed in the expected locations will be addressed on the next page.

GitHub repository: Cartesian Plane Part 16

Previous lesson: Cartesian Plane Lesson 16 Page 10: Testing the Line Properties Panel GUI, LPPTestDialog Utility

Class LinePropertiesPanelFuncTest

We’re going to write two JUnit tests for the LinePropertiesPanel. The first test will focus on the functionality of the panel, for example: if I change value of the stroke spinner and push the Apply button, is the PropertyManager correctly updated? On the next page we’ll address issues regarding the appearance of the GUI.

Most of the work of writing this JUnit test has already been done, via the LPPTestDialog class and its inner class CompConfig, but there are still some parts of the test that can be encapsulated in private utilities, such as testing two LinePropertySets for equality; an example of the use of this facility would look like this:

    select a button
    LinePropertySet set    = (get button's LinePropertySet)
    CompConfig      config = (get the component values from the 
                             LinePropertiesPanel)
    assertSetEquals( set, config )

Test Utilities

Our JUnit Test is going to have two maps that create an association between a specific LinePropertySet subclass and a LinePropertySet instance. For example:

  • At Initialization Time
    • In the ProprtyManager, initialize all line properties for all categories to predictable and, insofar as possible, unique values.
    • Create a map. The key to the map will be type LinePropertySet.class and the value will be a LinePropertySet object. Name the map setMapOrig.
    • Create an instance of each subclass of LinePropertySet, for example,
      LinePropertySet set = new LinePropertySetTicMajor();
    • Note that, since we haven’t applied any changes yet, set contains initial values obtained from the PropertyManager. Add these objects to setMapOrig.
      setMapOrig.put(LinePropertySetTicMajor.class, set);
  • During Testing
    • For Each Radio Button:
      • Click the button
      • Change all values on right side of the panel
    • Click Reset button
    • For Each Radio Button:
      • Click the button
      • Get the LinePropertySet from the button:
        LinePropertySet currSet = button.get();
      • Get the LinePropertySet containing the original values for this subclass of LinePropertySet:
        LinePropertySet origSet = setMapOrig.get(currSet.getClass());
      • Verify that the LinePropertySet contained in the button has been reset to its original values:
        assertSetsEqual( origSet, currSet );

To facilitate declaring and using the maps we’re going to make a small nested class that extends HashMap.

class LPSMap extends HashMap<…>
The motivation for this class is partly aesthetic. Without it we would end up with declarations like this one:
    private static final
    Map<Class<LinePropertySet>,LinePropertySet> o =
        new HashMap<>();

The above declaration is kind of long and confusing. In addition, we have compilation issues associated with type erasure. Consider the following code and the error message it produces:

LinePropertySet set1 = new LinePropertySetAxes();
map.put( set1.getClass(), set1);
// error: The method put(Class, LinePropertySet)
// in the type Map<Class<LinePropertySet>,LinePropertySet> is not
// applicable for the arguments (Class<capture#2-of ? extends
// LinePropertySet>, LinePropertySet)

To avoid all of the above, we’ll declare our nested class like this:
    private static class LPSMap extends
        HashMap<Class<? extends LinePropertySet>, LinePropertySet>

And now our declarations look like this:
    LPSMap setMapOrig = new LPSMap();

Decoding the LPSMap Declaration:

We want LPSMap to be a subclass of HashMap, so we start with:

class LPSMap extends HashMap

But HashMap is a generic type with two type parameters, <K,V> where K is the type of the key, and V is the type of the value, so we rewrite the class declaration like this:

class LPSMap extends
HashMap<Class,LinePropertySet>

Now we have a compiler warning because Class is a generic type; to get rid of the warning we change the declaration again:

class LPSMap extends
HashMap<Class<?>,LinePropertySet>

Finally, as conscientious programmers, we recognize that the type of the key shouldn’t be any old Class, but a Class that is a subclass of LinePropertySet giving us:

class LPSMap extends
HashMap<Class<? extends LinePropertySet>,LinePropertySet>

Another advantage to having our own subclass of HashMap is that we can simplify the put and get operations. For example, without simplification, all our puts would look like this:
    map.put( set1.getClass(), set1);
When we call Map.get it will be for a reason such as find the original values for the LinePropertySet that currently resides in buttonA. Without simplification, the invocation would look like this:
    LinePropertySet currSet = buttonA.get();
    LinePropertySet origSet = setMapOrig.get( currSet.getClass() );

But if we overload put and get as shown in the code below, our puts and gets can be simplified to:
    map.put(set1);
and
    origSet = setMapOrig.get( currSet );

Here’s the complete code for our nested class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private static class LPSMap  
    extends HashMap<Class<? extends LinePropertySet>, LinePropertySet>
{
    public void put( LinePropertySet set )
    {
        put( set.getClass(), set );
    }

    public LinePropertySet get( LinePropertySet setIn )
    {
        LinePropertySet setOut  = get( setIn.getClass() );
        assertNotNull( setOut );
        return setOut;
    }
}

newInstance( LinePropertySet setIn )
An operation we’ll perform frequently will be something like: we have an object of a particular type; give me a new object of the same type. For example:

  • For every button:
    • Click the button.
    • Change all the component values on the right side of the LinePropertiesPanel.
  • Push the Apply button.
  • For every button:
    • Get the LinePropertySet from the button:
      LinePropertySet set = button.get();
    • Get a new LinePropertySet of the same type as set; note that the new property set will be initialized with values from the PropertyManager, which should be the values established with the apply operation:
      LinePropertySet newSet = (a new LinePropertySet of the same type as set)
    • Verify the the property values in newSet (that is, the properties obtained from the PropertyManager) have the values that should have been saved when the Apply button was pushed.

The issue here is how do we get (a new LinePropertySet of the same type as set)? One way would be with nested if statements:
    if ( set instanceof LinePropertySetAxes)
      newSet = new LinePropertySetAxes();
   else if ( set instanceof LinePropertySetGridLines )
      newSet = new LinePropertySetGridLines();
   ...

But we can do better than that using reflection:

  • Get the Class class from set.
  • Get the default Constructor object from the Class class.
  • Use the Constructor object to make a new instance of a LinePropertySet; the new instance will be the same type of object as set.

Note: For more about using reflection to instantiate objects, see Creating New Class Instances, in the Oracle Java tutorial.

We’ll write the method newInstance to do this for us. Here’s the annotated code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static LinePropertySet newInstance( LinePropertySet setIn )
{
    Class<?>        clazz   = setIn.getClass();
    LinePropertySet setOut  = null;
    try
    {
        Constructor<?>  ctor    = clazz.getConstructor();
        Object          inst    = ctor.newInstance();
        assertTrue( inst instanceof LinePropertySet );
        setOut = (LinePropertySet)inst;
    }
    catch ( 
        NoSuchMethodException 
        | InvocationTargetException
        | IllegalAccessException 
        | InstantiationException exc )
    {
        String  msg = "New LinePropertySet not instantiated.";
        fail( msg, exc );
    }
    
    return setOut;
}
  • Line 1: We’ve declared the method’s parameter to be type LinePropertySet, but recall that it’s actually going to be a subclass of that type, such as LinePropertySetAxes or LinePropertySetGridLines.
  • Line 3: Gets the Class class from the given LinePropertySet. Note that I’ve declared the type parameter of clazz to be the wildcard type, because that’s what’s returned by getClass() for reasons having to do with type erasure. I could declare it to be type Class<LinePropertySet>, but then we’d have to deal with casts and unchecked cast compiler warnings.
  • Line 4: Declares the variable we’re using as a return value.
  • Line 5: Starts the try block we need to catch the exceptions thrown by the reflection methods.
  • Line 7: Obtains the Constructor<?> object representing the target class’s default constructor. Once again, the actual type of the constructor could be Constructor<? extends LinePropertySet>.
  • Line 8: Obtains a new Object by invoking the class’s default constructor.
  • Line 9: Asserts that the object returned by the class’s default constructor is a LinePropertySet as expected. Technically we could do away with line 9 altogether, but then we’d have to deal with an unchecked cast compiler warning on the next line.
  • Line 10: Casts the Object returned by ctor.newInstance() to type LinePropertySet.
  • Lines 12-16: Prepares to catch any of the many exceptions that could be thrown by the above code.
  • Lines 18,19: If an exception is thrown, fails the operation with an appropriate error message. Note that fail is a JUnit assertion, and we’re using the overload that let’s us pass the Throwable object that describes the cause of the failure.

newLinePropertySet( LinePropertySet setIn )
This method creates a new LinePropertySet with the same type as setIn (LinePropertySetAxes, LinePropertySetGridLines, etc.) and having values that are distinct from the value encapsulated in setIn. The new set will be used in tests involving changing property values, for example:

  • Get the selected radio button.
  • Get the LinePropertySet associated with the button:
    LinePropertySet currSet = button.get();
  • Obtain a new LinePropertySet with values that are distinct from the original:
    LinePropertySet newSet = newLinePropertySet( currSet );
  • Change all the values of the components on the right hand side of the LinePropertiesPanel to the values in newSet.
  • Click on a different radio button.
  • Verify that all the new values have been copied to the LinePropertySet stored in the original LinePropertySet:
    assertSetsEqual( newSet, currSet );

Here’s the code for this method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private static LinePropertySet 
newLinePropertySet( LinePropertySet setIn )
{
    LinePropertySet         setOut  = newInstance( setIn );
    
    if ( setIn.hasDraw() )
        setOut.setDraw( !setIn.getDraw() );
    if ( setIn.hasStroke() )
        setOut.setStroke( setIn.getStroke() + 5 );
    if ( setIn.hasLength() )
        setOut.setLength( setIn.getLength() + 10 );
    if ( setIn.hasSpacing() )
        setOut.setLength( setIn.getSpacing() + 15 );
    if ( setIn.hasColor() )
    {   
        int     rgb     = setIn.getColor().getRGB() + 20;
        setOut.setColor( new Color( rgb ) );
    }
    
    return setOut;
}

getButton( Predicate<PRadioButton<LinePropertySet>> pred )
selectOther()
Method getButton gets the radio button that satisfies the given Predicate. It’s useful, for example, for finding the currently selected radio button:
    button = getButton( b -> b.isSelected() );

The selectOther method selects the first button it finds that is not already selected. It is useful in tests such as the one cited for newLinePropertySet, above:

  • Change all the values of the components on the right hand side of the LinePropertiesPanel.
  • Select another radio button:
    selectOther()
  • Verify that the original button’s LinePropertySet object is updated with the new component values.

The code for these methods follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private PRadioButton<LinePropertySet> 
getButton( Predicate<PRadioButton<LinePropertySet>> pred )
{
    PRadioButton<LinePropertySet>   button  = 
        dialog.getRadioButtons().stream()
            .filter( pred )
            .findFirst().orElse( null );
    assertNotNull( button );
    return button;
}

private void selectOther()
{
    PRadioButton<LinePropertySet>   other   =
        getButton( b -> !b.isSelected() );
    dialog.doClick( other );
}

assertSetsSynched( LinePropertySet set1, LinePropertySet set2 )
This method verifies that two LinePropertySets are equal. The process has two parts:

  1. The two sets must support the same properties; the values of set1.hasStroke() and set2.hasStroke(), for example, must be equal.
  2. For each supported property, the corresponding property values in set1 and set2 must be equal.

The code looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static void 
assertSetsSynched( LinePropertySet set1, LinePropertySet set2 )
{
    boolean hasStroke = set1.hasStroke();
    boolean hasLength = set1.hasLength();
    boolean hasSpacing = set1.hasSpacing();
    boolean hasColor = set1.hasColor();
    boolean hasDraw = set1.hasDraw();
    
    assertEquals( hasStroke, set2.hasStroke() );
    assertEquals( hasLength, set2.hasLength() );
    assertEquals( hasSpacing, set2.hasSpacing() );
    assertEquals( hasColor, set2.hasColor() );
    assertEquals( hasDraw, set2.hasDraw() );
    
    if ( hasStroke )
        assertEquals( set1.getStroke(), set2.getStroke() );
    if ( hasLength )
        assertEquals( set1.getLength(), set2.getLength() );
    if ( hasSpacing )
        assertEquals( set1.getSpacing(), set2.getSpacing() );
    if ( hasColor )
        assertEquals( set1.getColor(), set2.getColor() );
    if ( hasDraw )
        assertEquals( set1.getDraw(), set2.getDraw() );
}

assertPropertiesSynched( LinePropertySet set1 )
This method will look a lot like assertSetsSynched, but this time we’re going to verify that the state of a LinePropertySet matches the component configuration on the right hand side of the LinePropertiesPanel. We start by getting the current state of the LinePropertiesPanel, and verifying that components on the right of the panel are enabled or disabled depending on whether or not the corresponding property is supported for the currently selected button:

private void assertPropertiesSynched( LinePropertySet set1 )
{
    LPPTestDialog.CompConfig    config  = dialog.getAllProperties();
    boolean hasStroke = set1.hasStroke();
    boolean hasLength = set1.hasLength();
    boolean hasSpacing = set1.hasSpacing();
    boolean hasColor = set1.hasColor();
    boolean hasDraw = set1.hasDraw();
    
    assertEquals( hasStroke, config.strokeSpinnerEnabled );
    assertEquals( hasStroke, config.strokeLabelEnabled );
    assertEquals( hasLength, config.lengthSpinnerEnabled );
    // ...
}

Then, for each supported property, we verify that the value of the property in the LinePropertySet is equal to the value displayed by the corresponding component:

private void assertPropertiesSynched( LinePropertySet set1 )
{
    // ...
    if ( hasStroke )
        assertEquals( set1.getStroke(), config.stroke );
    if ( hasLength )
        assertEquals( set1.getLength(), config.length );
    // ...
}

The complete code for this method can be found in the GitHub repository.

JUnit Tests

⏹ Test Strategy
Our test strategy for the LinePropertiesPanel is going to look similar to that for the LinePropertySet:

  1. Verify initial values; for each radio button:
    • Click the button.
    • Validate the values of the LinePropertiesPanel components against the button’s LinePropertySet.
  2. Verify traversal logic; for each button:
    • Click the button.
    • Give the components on the right side of the panel new values.
    • Click a different button.
    • Verify that the original button’s LinePropertySet is updated with the values from the components on the right side of the panel.
  3. Reset logic (note that all the values of all the buttons’ properties sets were changed in the previous test):
    • Push the Reset button.
    • Verify that each button’s property set is returned to its original value.
    • Verify that the component values for the currently selected button have returned to the original values.
  4. Apply logic:
    • Change all the values of all the properties.
    • Push the apply button.
    • Verify that the PropertyManager has been updated with all the new property values.
  5. Close logic:
    • Push the Close button.
    • Verify that the dialog containing the LinePropertiesPanel is closed.
    • Make the dialog visible.
    • Verify that all of the buttons’ property sets have the correct values.
    • Verify that the LinePropertiesPanel components have the correct values for the currently selected button.

During my own testing I came up with one special case that deserved individual attention:

  • Apply/Reset test case:
    • Select any radio button.
    • Choose a component on the right hand side of the LinePropertiesPanel; call it compA.
    • Change the value of compA.
    • Push the Apply button.
    • Push the Reset button.
    • Verify that the selected button’s property set has the correct configuration.
    • Verify that the components on the right hand side of the LinePropertiesPanel have the correct configuration.

Our testing strategy relies on the test cases listed above being executed in the given order, but recall that, by default, JUnit tests are executed in a random order. One way to ensure that the steps are executed in the correct order is to have one JUnit test that calls five different helper methods to perform the tests; that’s what we did with the LinePropertySet test. But, as we saw in a previous lesson, we can also force JUnit to execute the tests in a specific order. To do that, we declare the JUnit test class with the @TestMethodOrder tag. Then we tag each each @Test method with an ordinal, and JUnit will execute the tests from lowest ordinal to highest ordinal:

@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LinePropertiesPanelFuncTest
{
    // ...
    @Order( 5 )
    @Test
    public void testInit()
    {
        // ...
    }
    @Order( 10 )
    @Test
    public void testSynchOnTraversal()
    {
        // ...
    }
    // ...
}

⏹ Initialization
We’re going to start our test class by giving predictable values to all the important properties in the PropertyManager; we can use LinePropertySetInitializer.initProperties() from the …test_utils package for that. Then we’ll create two maps:

  • LPSMap setMapOrig will contain LinePropertySets with the original property values from the PropertyManager before we executed any tests.
  • LPSMap setMapNew will contain LinePropertySets with property values guaranteed to be different from the original PropertyManager values.

We’re also going to need one LPPTestDialog. We’ll create it once, at the start of the test, and utilize it throughout all our tests. Here is our initialization logic with annotations:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class LinePropertiesPanelFuncTest
{
    private static final LPSMap     setMapOrig  = new LPSMap();
    private static final LPSMap     setMapNew   = new LPSMap();
    private static LPPTestDialog    dialog;
    
    @BeforeAll
    static void setUpBeforeClass() throws Exception
    {
        LinePropertySetInitializer.initProperties();
        dialog = LPPTestDialog.getDialog();
        dialog.setDialogVisible( true );
        setMapOrig.put( new LinePropertySetAxes() );
        setMapOrig.put( new LinePropertySetGridLines() );
        setMapOrig.put( new LinePropertySetTicMajor() );
        setMapOrig.put( new LinePropertySetTicMinor() );
        
        setMapOrig.values().stream()
            .map( s -> newLinePropertySet( s ) )
            .forEach( setMapNew::put );
    }
    // ...
}
  • Line 4: This is the map that contains LinePropertySets with the original values from the PropertyManager. It will have one LinePropertySet object for each of the LinePropertySet subclasses, for example LinePropertySetAxes, LinePropertySetGridLines, etc.
  • Line 5: This is the map that contains LinePropertySets with unique values. For each LinePropertySet in setMapOrig it will have an object of the same type but with guaranteed unique values.
  • Line 6: This is our sole LPPTestDialog.
  • Lines 8,9: The start of our initialization logic, executed just after the test class loads.
  • Line 11: Sets up values for all the properties we’re working with in the LinePropertiesPanel. Recall this has to be executed before the PropertiesManager class is loaded.
  • Lines 12,13: Gets the dialog containing the LinePropertiesPanel we use for testing and makes it visible.
  • Lines 14-17: Adds an object containing original values from the PropertyManager for each LinePropertySet subclass. Recall that we overloaded the put method in the LPSMap class like this:
        public void put( LinePropertySet set )
        {
            put( set.getClass(), set );
        }
  • Line 19: Streams all the values (LinePropertySet objects) from the map containing original property values.
  • Line 20: For a given LinePropertySet creates a new LinePropertySet of the same type, but with unique values.
  • Line 21: Adds the new LinePropertySet object to the map of unique property values.

We’ll conclude this section with a description of each JUnit test; see also Test Strategy, above.

Note: I’ve put lots of pauses in the code so that I could easily observe the tests as they run. But I have also verified that the tests run just fine without them.

⏹ Initialization Testing, Annotated Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Order( 5 )
@Test
public void testInit()
{
    dialog.getRadioButtons().stream()
        .peek( dialog::doClick )
        .map( b -> b.get() )
        .peek( s -> Utils.pause( 250 ) )
        .peek( s -> assertSetsSynched( s, setMapOrig.get(s ) ) )
        .forEach( s -> assertPropertiesSynched( s ) );
}
  • Line 5: Streams the radio buttons, one at a time.
  • Line 6: Clicks the button.
  • Line 7: Gets the button’s LinePropertySet.
  • Line 9: Verifies that the LinePropertySet contains the original values from the Propertymanager.
  • Line 10: Verifies that the components on the right hand side of the LinePropertiesPanel have the correct values.

⏹ Traversal Testing, Annotated Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@Order( 10 )
@Test
public void testSynchOnTraversal()
{
    dialog.getRadioButtons().forEach( button -> {
        LinePropertySet storedValues    = button.get();
        LinePropertySet newValues       = 
            setMapNew.get( storedValues );
        dialog.doClick( button );
        Utils.pause( 250 );
        dialog.synchRight( newValues );
        Utils.pause( 250 );
        selectOther();
        Utils.pause( 250 );
        assertSetsSynched( storedValues, newValues );
    });
}
  • Line 5: Streams each radio button.
  • Line 6: Gets the button’s LinePropertySet.
  • Lines 7,8: Gets a LinePropertySet containing new values.
  • Line 9: Clicks the button.
  • Line 11: Assigns the new property values to the components on the right hand side of the LinePropertiesPanel.
  • Line 13: Clicks a different radio button.
  • Line 15: Verifies that the original button’s LinePropertySet has been updated with the new values introduced at line 11.

⏹ Reset Testing, Code
After the previous test, all the properties have new values. Press the Reset button, and verify that all the properties return to their original values.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Order( 15 )
@Test
public void testReset()
{
    dialog.reset();
    dialog.getRadioButtons().forEach( button -> {
        LinePropertySet storedValues    = button.get();
        LinePropertySet origValues      = 
            setMapOrig.get( storedValues );
        assertSetsSynched( storedValues, origValues );
    });
}

⏹ Apply Testing, Annotated Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Order( 20 )
@Test
public void testApply()
{
    dialog.getRadioButtons().forEach( button -> {
        LinePropertySet storedValues    = button.get();
        LinePropertySet newValues       = 
            setMapNew.get( storedValues );
        dialog.doClick( button );
        Utils.pause( 250 );
        dialog.synchRight( newValues );
        Utils.pause( 250 );
        selectOther();
        Utils.pause( 250 );
        assertSetsSynched( storedValues, newValues );
    });
    
    dialog.apply();
    Utils.pause( 250 );
    dialog.getRadioButtons().forEach( button -> {
        LinePropertySet storedValues    = button.get();
        LinePropertySet newValues       = 
            setMapNew.get( storedValues );
        LinePropertySet pMgrValues      =
            newInstance( storedValues );
        assertSetsSynched( storedValues, newValues );
        assertSetsSynched( newValues, pMgrValues );
    });
}
  • Line 5: For each radio button…
  • Line 6: Gets the button’s LinePropertySet
  • Lines7,8: Gets new values corresponding to the button’s LinePropertySet.
  • Line 9: Clicks the button.
  • Line 11: Assigns the new values to the components in the LinePropertiesPanel.
  • Lines 13,15: Clicks a different button, and verifies that the new values assigned to the components at line 11 are transferred to the original radio button’s LinePropertySet.
  • Line 18: Pushes the Apply button.
  • Line 20: For each radio button…
  • Lines 21-23: Gets the button’s LinePropertySet and the LinePropertySet containing the new property values assigned at line 11 and applied at line 18.
  • Lines 24,25: Gets a brand new LinePropertySet of the same type as the button’s LinePropertySet. Recall that, during construction, a LinePropertySet object is initialized with values obtained from the PropertyManager, so, if the PropertyManager was correctly updated at line 18, this new set will have the new values assigned at line 11.
  • Line 26: Verifies that the button’s LinePropertySet contains the new values.
  • Line 27: Verifies that the PropertyManager was updated with the new values.

⏹ Close Testing, Annotated Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Order( 25 )
@Test
public void testClose()
{
    PRadioButton<LinePropertySet>   button  = 
        getButton( b -> b.isSelected() );
    LinePropertySet buttonProperties    = button.get();
    
    assertPropertiesSynched( buttonProperties );
    assertTrue( dialog.isDialogVisible() );
    
    dialog.close();
    Utils.pause( 250 );
    assertFalse( dialog.isDialogVisible() );
    dialog.setDialogVisible( true );
    Utils.pause( 250 );
    assertTrue( dialog.isDialogVisible() );
    
    assertTrue( dialog.isSelected( button ) );
    LinePropertySet testProperties      = button.get();
    assertSetsSynched( testProperties, buttonProperties );
    assertPropertiesSynched( buttonProperties );        
}
  • Lines 5-7: Gets the currently selected button, and the button’s LinePropertySet.
  • Lines 9,10: Sanity check: verifies that the dialog is visible, and the LinePropertiesPanel components are configured correctly.
  • Line 12: Pushes the Close button.
  • Line 14: Verifies that the dialog was closed.
  • Line 15: Reopens the dialog.
  • Line 17: Sanity check: verifies that the dialog is open.
  • Line 19: Verifies that the button that was selected when the dialog was closed is currently selected.
  • Line 20: Gets the button’s property set.
  • Line 21: Verifies that the button’s LinePropertySet is the same as before the dialog was closed.
  • Line 22: Verifies that the components on the right hand side of the LinePropertiesPanel have the correct values.

⏹ Apply/Reset Testing, Annotated Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Order( 25 )
@Test
public void testApplyReset()
{
    final int   newVal  = 10;
    PRadioButton<LinePropertySet>   button  = 
        getButton( b -> b.isSelected() );
    LinePropertySet origSet = button.get();
    LinePropertySet testSet = newInstance( origSet );
    assertPropertiesSynched( origSet );
    assertNotEquals( newVal, origSet.getStroke() );
    
    testSet.setStroke( newVal );
    dialog.synchRight( testSet );
    dialog.apply();
    dialog.reset();
    
    LPPTestDialog.CompConfig  config  = dialog.getAllProperties();
    assertEquals( newVal, config.stroke );
    assertEquals( newVal, origSet.getStroke() );
}
  • Line 5: New value to be assigned to the stroke component on the right hand side of the LinePropertiesPanel.
  • Lines 6,7: Gets the currently selected button, and the button’s encapsulated LinePropertySet.
  • Line 8: Create a new LinePropertySet of the same type as the button’s current LinePropertySet.
  • Lines 9,10: Sanity check: verifies that the component values in the LinePropertiesPanel are in synch with the current button’s current property set, and that the new stroke value is different from the old stroke value.
  • Line 13: Sets the new stroke value in the new LinePropertySet.
  • Line 14: Applies the property values in the new LinePropertySet to the components on the right side of the LinePropertiesPanel.
  • Line 15: Pushes the Apply button.
  • Line 16: Pushes the Reset button.
  • Line 18: Gets the current values of the components on the right hand side of the LinePropertiesPanel.
  • Line 19: Verifies that the value of the stroke component has the correct value.
  • Line 20: Verifies that the LinePropertySet object encapsulated in the selected radio button has the correct stroke value.

Summary

On this page we performed functional testing of the LinePropertiesPanel. This entailed the following major tasks:

  • Configuring the LinePropertiesPanelFuncTest JUnit Test class to execute tests in a specific order. In order to accomplish this we had to declare the test class with the @TestMethodOrder(MethodOrderer.OrderAnnotation.class) tag, and each test case had to be declared with an ordinal provided through the @Order tag.
  • Creating the static nested class LPSMap in the LinePropertiesPanelFuncTest class. This is a very small class that made it easier to create and manage a HashMap that maps a LinePropertySet Class class to a LinePropertySet object.
  • Using reflection to instantiate objects of a specific class.

On the next page we’ll begin visual testing of the LinePropertiesPanel, verifying that the panel appears correctly on the screen.

    Next: Visual Testing of the LinePropertiesPanel