Testing Serializers and Parsers

Currently the build generates the serializers and parsers from a provided mspec specification.

A typical full round-trip test for the model, parsers and serializers would look as follows:

  • Starting from a byte array

  • The parser is used to parse the byte array

  • The parsed model instance is compared with an expected model

  • If the expected model matched the expected one the model is serialized back to a byte array

  • The resulting byte array is compared to the original byte array

  • If the byte arrays are identical, the round-trip is regarded ok

Doing this manually would require a lot of manual object construction and validation, so we created a framework for creating such tests.

As XML, no matter what you think about it, allows simple and easy readable descriptions these tests are provided as XML files.

All generated model classes allow parsing and serializing to XML via Jackson.

Structure of a test

A typical test looks like this:

  <testcase>
    <name>Read Input Registers Request</name>
    <raw>000000000006ff0408d20002</raw>
    <root-type>ModbusTcpADU</root-type>
    <parser-arguments>
      <response>false</response>
    </parser-arguments>
    <xml>
      <ModbusTcpADU className="org.apache.plc4x.java.modbus.readwrite.ModbusTcpADU">
        <transactionIdentifier>0</transactionIdentifier>
        <unitIdentifier>255</unitIdentifier>
        <pdu className="org.apache.plc4x.java.modbus.readwrite.ModbusPDUReadInputRegistersRequest">
          <startingAddress>2258</startingAddress>
          <quantity>2</quantity>
        </pdu>
      </ModbusTcpADU>
    </xml>
  </testcase>

As you can see, the name provides a simple human readable name for the test which is used for reporting success and failure.

The raw element contains the hex-representation of the binary input.

After that the root-type specifies the base type used for parsing this data. In above example the test will use the ModbusTcpADUIO.serialize and ModbusTcpADUIO.parse methods for serializing and parsing.

Some parsers require additional parameters for parsing. In above example the Modbus protocol can’t decide if something is a request or response from the data itself, so we have to pass that information in using a parser-arguments argument.

The final element is the xml element, which contains the XML representation of the parsed object.

Each test implements exactly the test-strategy sketched above, however for comparing the parsed and the expected model, we use XMLUint to serialize the parsed model to XML and compare that to the given XML in the test-case declaration.

Structure of a testsuite

Multiple tests are usually wrapped into a testsuite document.

In general this is just a container with a given testsuite name and a number of testcase elements.

One important setting however controls the endianness of the protocol in general.

This is controlled with a bigEndian attribute in the testsuite root element.

An example testsuite document looks as follows:

<?xml version="1.0" encoding="UTF-8"?>
<test:testsuite xmlns:test="https://plc4x.apache.org/schemas/parser-serializer-testsuite.xsd"
                bigEndian="true">

  <name>Allen-Bradley DF1</name>

  <testcase>
    ...
  </testcase>

  <testcase>
    ...
  </testcase>

  <testcase>
    ...
  </testcase>

  <testcase>
    ...
  </testcase>

</test:testsuite>

The Junit runner

All logic is implemented in the plc4j-utils-test-utils module, so make sure to add the following test-dependency:

    <dependency>
      <groupId>org.apache.plc4x</groupId>
      <artifactId>plc4j-utils-test-utils</artifactId>
      <version>{project.version}</version>
      <scope>test</scope>
    </dependency>

In order to run these tests as part of the build, as a last step we need to create a test-runner class.

This is generally just a hand-full of boilerplate code, telling the test which document to use for testing.

Following code snippet sort of looks the same for every testsuite:

package org.apache.plc4x.java.modbus;

import org.apache.plc4x.test.parserserializer.ParserSerializerTestsuiteRunner;

public class ModbusIOTest extends ParserSerializerTestsuiteRunner {

    public ModbusIOTest() {
        super("/testsuite/ModbusTestsuite.xml");
    }

}

Here the document ModbusTestsuite.xml is located in the directory: src/test/resources/testsuite/.