Pages

Showing posts with label development. Show all posts
Showing posts with label development. Show all posts

Tuesday, February 3, 2015

Mutation Testing

End of January I attended the OOP2015 conference in Munich. Among the load of interesting sessions was one that left a mark. It was the workshop conducted by Filip van Laenen and Markus Schirp about Mutation Testing (slides here), which I'd like to summarize in this post.

What is Mutation Testing?

Mutation testing is a method to ensure the quality of your Test. With mutations testing you verify if your tests not only invokes your product code pretending to cover lines and branches but that the tests actually reflects the semantics of your code. While your tests guard your product code from bugs, mutation testing guards your tests suite from critical gaps.

How does it work?

Mutation testing is available for a set of languages. The implementation for Java is the PIT. PIT modifies the compiled byte code by applying a set of predefined rules, so call Mutators. Each change to the byte code is called a Mutant. Against the altered byte code your unit tests are executed, resulting in three outcomes:
  • The tests fails. This proves, that your tests detects the changes to the byte code correctly and reports an error. This outcome is called a Killed Mutant.
  • The test does not fail, but the line of code is covered by the test execution. This proves, that your test does not cover the full semantics of the product. This outcome is called a Survived Mutant (I call them Survivors).
  • The product code is not covered by a test at all but contains a mutant. This is outcome is an Uncovered Mutant (I call them Lurker), which can also be indicated by a Code Coverage tool such as Cobertura or jacoco.

What's the use of it?

Every mutant not killed by a test is a blind spot of your test suite and may become an actual bug in the long run. With mutation testing you can determine, whether your Unit Test - the detail specification - covers all the semantics of your product code. For every uncovered mutant you may either decide if your specification (test) is incomplete or your product code contains unneeded semantics which should be removed. That way mutation testing leads to smaller and simpler code and further is an indicator, when you are done with testing.

Its no silver bullet

Mutation testing is no silver bullet as it does not replace the need for properly designed tests in the first place. Testing for all combinations of Mutations may become a quite resource consuming operation, requiring to reduce the scope of mutation testing in larger projects on the crucial parts.
Further, some survived mutants may have to be accepted. Survived mutants may easily occur and may not necessarily be an indicator that your coverage is insufficient. For a simple example, think about logging code or the equality in some comparison cases, like ( a > b ? a : b) which in Java is equal to (a >=b ? a : b).

How to use it?

To use it on a maven project, an official maven plugin is avaible (see link for a good documentation how to use it). There are options for command line or Ant as well. This will create a report in html or xml. You may narrow the scope of the mutation testing using include and exclude parameter. The report itself run on a regular basis is already a good source of information for conduction code reviews.
For using it with SonarQube, a plugin is available, but its current version 0.5 is only compatible with SonarQube prior to version 4.2. There is a fork on github, making it compatible with Sonar API version 4.3 and I am working on a version for SonarQube 5.0 with some more rules. Both have to be compiled and deployed manually to Sonar.

TL;DR

Mutation Testing alters your product code in a deterministic way and verifies if your test suite finds the induced bug. This leads to smaller and simpler source code and you can tell when your're done with testing.

Tuesday, August 26, 2014

User Acceptance Testing with Selenium and Cucumber

In the last implementation project I participated in we applied the Behavior Driven Development approach where the user stories are defined in Given-When-Then style. In this article I want to describe how I combined Cucumber with Selenium in order to automate our User-Acceptance Tests using Behavior Driven Development.

For running automated BDD tests there are some free frameworks available (a brief comparison of BDD frameworks). We decided to go for Cucumber because it served all our requirements, has a good documentation with good examples and is pretty easy to get it up and running

Cucumber JUnit Test

The following listing shows a simple example that is practically the archetype for all our Cucumber tests.

@RunWith(Cucumber.class)
@CucumberOptions(
  features = { "classpath:features/example/" },
  glue = { "my.project.uat." },
  tags = { "@Example" })
public class ExampleCucumberTest {
  //empty
}
The annotations of the example in detail are:
  • @RunWith declares the TestRunner for this test, which is the Cucumber class. The test won't run without it.
  • @CucumberOptions define various options for this tests. The options are optional but are quite helpful in controlling the behavior of the test
    • features: declares a path were the BDD feature files (text files) are found. The example points to a location in the classpath. All feature files (.feature extension) below that location are considered. Multiple locations can be defined as an array.
    • glue: defines the packages where Steps and Hooks are located. Steps and Hooks contain the actual code for the tests. Multiple packages can be defined as an array.
    • tags: defines which stories should be executed. If you omit this option, all Stories are executed, otherwise only those that have one of the tags set will be run.
Of course there are more options available (see Cucumber apidoc)  but these are the options I use most. As soon as you have your first story written, you can start right away with this simple test.

BDD Feature 

The following exaple story is taken from the Cucumber documentation

@Example
Feature: Search courses
  Courses should be searchable by topic
  Search results should provide the course code

Scenario: Search by topic
    Given there are 240 courses which do not have the topic "biology"
    And there are 2 courses, A001 and B205, that each have "biology" as one of the topics
    When I search for "biology"
    Then I should see the following courses:
      | Course code |
      | A001        |
      | B205        |

With that feature in the right location you can run the above test with JUnit. Of course it will not be successful. Actually, with the default settings it will ignore all the steps unless you use the @CucumberOption(strict=true) which is recommended when you run the test as part of a quality gate.
The Cucumber documentation provides some good descriptions on Features and their syntax. You can define Backgrounds that are executed for each scenario of the feature (similar to JUnit 4 @BeforeClass) or Scenario Outlines to run the feature against a set of data. It is even possible to write your BDDs in different languages. Therefore you have to start the feature with the line following line and have all the keywords in the according language.

#language: de
Funktionalität: ...

But you have to be careful with the encoding of the Feature files and special characters. It's best to use UTF-8 as default encoding. A complete list of the keywords in other languages can be found in the Cucumber apidoc.

Cucumber Steps

When the test for the feature is run and the steps or part of it are not yet implemented, it will produce an output like this:

You can implement missing steps with the snippets below:

@Given("^there are (\\d+) courses which do not have the topic \"([^\"]*)\"$")
public void there_are_courses_which_do_not_have_the_topic(int arg1, String arg2) throws Throwable {
    // Express the Regexp above with the code you wish you had
    throw new PendingException();
}

@Given("^there are (\\d+) courses, A(\\d+) and B(\\d+), that each have \"([^\"]*)\" as one of the topics$")
public void there_are_courses_A_and_B_that_each_have_as_one_of_the_topics(int arg1, int arg2, int arg3, String arg4) throws Throwable {
    // Express the Regexp above with the code you wish you had
    throw new PendingException();
}
...

The test prints out skeletons for unimplemented steps. What you do now is to create a new step which is a simple Java class and put in one of the packages defined in the glue. Copy the skeletons into the class and implement it.
So the steps are basically what is executed for each line. It is possible to pass parameters to the steps that are extracted and converted so that steps can be reused with different values. Its also possible to define entire tables as input.

Hooks

Hooks are basically the same as steps but fulfill a similar role like the JUnit @Before and @After annotated methods, the even use the similar annotations (actually, the are named the same but are in a different package). You can trigger certain hooks using tags like shown in the following example:

@WithFirefox
Scenario: Response times with 10 users
...

and the according hook in Java will be

@Before("@WithFirefox")
public class BrowserHook {
 ...
  public void setupScenario_Firefox() {
   ...
  }
 ...

Dependencies between Hooks and Steps

In order to reuse existing code or to access the state of a particular Hook or Step instance you can create a dependency between the classes by defining a constructor that accepts a particular Hook or Step. The Cucumber JUnit runner will create instances of the according classes and inject them in classes that are dependent.
public class MyBrowserSteps {

  private BrowserHook browserHook;

  public MyBrowserSteps(final BrowserHooks browserHook) {
    this.browserHook = browserHook;
  }
The same applies to Steps so you can make one set of steps dependent on other steps.

Selenium Steps

So far I only described how to write any test with Cucumber, but for User Acceptance Testing you might want to test the actual solution. For web application that is the deployed application that is accessed by a browser. For browser automation the Selenium framework is widely known and framework of choice for most cases. It provides a recording tool (a plugin to Firefox) to record user interactions with the browser. It provides a model to access elements of the website using Java and various methods of locating elements in the browser.
For automated user-acceptance tests with Selenium and Cucumber the basic approach would be to
  1. Record actions with the Selenium Recorder
  2. Copy them to Steps classes that match your BDD
  3. Define assertions in Then step implementations
A step implemented with Selenium will look like in the following example

@When("^I push the button$")
public void i_push_the_button() throws Throwable {
  driver.findElement(By.cssSelector("div.v-select-button")).click();
}

In order to reuse steps for different browsers, I used the BrowserHook shown in on of the example on above and set-up the browser using a specific tag for each browser. The driver is first initialized upon the first call to getDriver(). In the step itself I retrieve the driver from the BrowserHook that got injected. The BrowserHook may be implemented like this

public final class BrowserHooks {
  private enum DriverType {
    headless,
    firefox,
    ie,
    chrome, ;
  }
  
  private WebDriver driver;

  public WebDriver getDriver() {
    if (driver == null) {
      switch (driverType) {
 case ie:
   driver = new InternetExplorerDriver();
   break;
        case firefox:
          driver = new FirefoxDriver();
          break;
        ...
    }
    return driver;
  }

  @Before("@WithFirefox")
  public void setupScenario_Firefox() {
    driverType = DriverType.firefox;
  }
  
  @Before("@WithIE")
  public void setupScenario_InternetExplorer() {
    driverType = DriverType.ie;
  }
  ...
}
And the step definition that uses it may look like
public class MyBrowserSteps {

  private BrowserHook browserHook;

  public MyBrowserSteps(final BrowserHooks browserHook) {
    this.browserHook = browserHook;
  }

  @When("^I push the button$")
  public void i_push_the_button() throws Throwable {
    this.browserHook.getDriver().findElement(By.cssSelector("div.v-select-button")).click();
  }
...

Aggregate Steps

One of the big advantages of a BDD framework like Cucumber is, that you can define steps that aggregate multiple steps. A good example for this is the Login Story. Although this is a point of typical discussions whether "Login User" is a valid Use Case or User Story (with regards to its business value) the requirement to allow a user to login does exists and its parameters need to be defined (whether it is via Single Sign On, Smartcard, Username/Password, Two-Factor or whatever else).
So let's assume you define a login user story such as
 Given the login screen is being displayed
 When I enter my username "xxx" and my password "yyy"
  And I push the login button
 Then I see the main screen of the application
  And I see my name being displayed in the user info box
Now you don't want to describe all these steps over and over again because the rest of the application under test requires a logged in user. So you could begin the other stories with
  • When the user "xxx" is logged in
Or even better by using a hook/tag before the story like
  • @Authenticated
Now, what you do in your code is to define a dependency to the steps class that contains the login step definitions and invoke each of them in the correct order either in a hook definition or in a step definition. The advantage of a hook is that you could combine it with other hooks to set up the test user or even persona.
public class LoginSteps {

  private BrowserHook browserHook;

  public LoginSteps (final BrowserHooks browserHook) {
    this.browserHook = browserHook;
  }


  @Given("^the login screen is being displayed$")
  public the_login_screen_is_being_displayed() {
    this.browserHook.getDriver().get(baseURL);
  }
  @When("^I enter my username \"([^\"]*)\" and my password \"([^\"]*)\"$")
  public void I_enter_my_username_and_my_password(String arg1, String arg2) throws Throwable {
    //with Selenium, put in the values in the login form
  }
  @When("^I push the login button$")
  public void I_push_the_login_button() throws Throwable {
    // with Selenium, locate the submit/login button and click it
  }
  ...
}

public class LoginHook {

  private LoginSteps loginSteps;
  private String testUser;
  private String testPassword;

  public LoginHook (final LoginSteps loginSteps) {
    this.loginSteps= loginSteps;
   
  }

  @Before(value="@PersonaXY", order=1)
  public void selectPersonaXY() {
     this.testUser = ...;
     this.testPassword = ...;
  }

  @Before(value="@Authenticated", order=2)
  public void login() {
    this.loginSteps.the_login_screen_is_being_displayed();
    this.loginSteps.I_enter_my_username_and_my_password(testUser, testUserPassword);
    this.loginSteps.I_push_the_login_button();
    ...
  }
}

And how it is used in a story

 @Authenticated @PersonaXY
 Given I see the meaningful screen
 When I do something purposeful
 Then I get a sensible result

 Conclusion

In this article I gave a brief introduction into Cucumber and how to write testcases with it. I showed how to define steps with Selenium to create meaningful, browser-based user acceptance testing and I showed how to combine thereby reuse steps and hook to create a rich user acceptance testing suite.

Tuesday, April 29, 2014

JUnit Testing with Jackrabbit

Writing unit tests for your code is not only best practices, it's essential for writing quality code. In order to write good unit tests, you should use mocking of code not under test. But what if you're using a technology or an API that would require quite a lot of complicated mocks?
In this article I'd like to describe how you write unit tests for code that accesses a JCR repository.

At first I really tried to mock the JCR API using Mockito, but stopped my attempt at the point where I had to mock the behavior of the Query Object Model. It became apparent, that writing mocks would outweigh the effort to write the actual production code by far. So I had to search for an alternative and found one.

The reference implementation of JCR is the Apache Jackrabbit project. This implementation comes with a set of JCR Repository implementations, one of these is the TransientRepository. The TransientRepository starts the repository on first login and shuts it down on the last session being closed. The repository is created in memory which works pretty fast and makes it the best solution for unit testing. But nevertheless, a directory structure is created for the repository and unless not specified a config file is created as well.

For writing unit tests against this repository, we need the following:
  • a temporary directory to locate the directory structure of the repository
  • a configuration file (unless you want one created on every startup)
  • the repository instance
  • a CND content model description to initialize the repository data model (optional) 
  • an admin session to perform administrator operations
  • a cleanup operation to remove the directory structure
  • the maven dependencies to satisfy all dependencies
Let's start with the Maven dependencies. You need the JCR Spec, the Jackrabbit core implementation and the Jackrabbit commons for setting up the repository.

<properties>
  <!-- JCR Spec -->
  <javax.jcr.version>2.0</javax.jcr.version>
  <!-- JCR Impl -->
  <apache.jackrabbit.version>2.6.5</apache.jackrabbit.version>
</properties>
...
<dependencies>
<!-- The JCR API -->
  <dependency>
    <groupId>javax.jcr</groupId>
    <artifactId>jcr</artifactId>
    <version>${javax.jcr.version}</version>
  </dependency>
  <!-- Jackrabbit content repository -->
  <dependency>
    <groupId>org.apache.jackrabbit</groupId>
    <artifactId>jackrabbit-core</artifactId>
    <version>${apache.jackrabbit.version}</version>
    <scope>test</scope>
  </dependency>
  <!-- Jackrabbit Tools like the CND importer -->
  <dependency>
    <groupId>org.apache.jackrabbit</groupId>
    <artifactId>jackrabbit-jcr-commons</artifactId>
    <version>${apache.jackrabbit.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies> 

Now let's create the directory for the repository. I recommend to locate it in a temporary folder so multiple test runs don't affect each other if cleanup failed. We use the Java TempDirectory facility for that:
//prefix for the repository folder
import java.nio.file.Files;
import java.nio.file.Path;
...
private static final String TEST_REPOSITORY_LOCATION = "test-jcr_";
...
final Path repositoryPath = 
      Files.createTempDirectory(TEST_REPOSITORY_LOCATION);

Next, you require a configuration file. If you already have a configuration file available in the classpath, i.e. in src/test/resource, you should load it first:
final InputStream configStream = 
  YourTestCase.class.getResourceAsStream("/repository.xml");

Knowing the location and the configuration, we can create the repository:
import org.apache.jackrabbit.core.config.RepositoryConfig;
import org.apache.jackrabbit.core.TransientRepository;
...
final Path repositoryLocation = 
      repositoryPath.toAbsolutePath();
final RepositoryConfig config = 
      RepositoryConfig.create(configStream, repositoryLocation.toString());
final TransientRepository repository = 
  new TransientRepository(config);

If you ommit the config parameter, the repository is created in the working directory including the repository.xml file, which is good for a start, if you have no such file.

Now that we have the repository, we want to login to create a session (admin) in order to populate the repository. Therefore we create the credentials (default admin user is admin/admin) and perform a login:
final Credentials creds = 
  new SimpleCredentials("admin", "admin".toCharArray());
final Session session = repository.login(creds);

With the repository running and an open session we can initialize the repository with our content model if require some extensions beyond the standard JCR/Jackrabbit content model. In the next step I import a model defined in the Compact Node Definition (CND) Format, described in JCR 2.0
import org.apache.jackrabbit.commons.cnd.CndImporter;
...
private static final String JCR_MODEL_CND = "/jcr_model.cnd.txt";
...
final URL cndFile = YourTestCase.class.getResource(JCR_MODEL_CND);
final Reader cndReader = new InputStreamReader(cndFile.openStream());
CndImporter.registerNodeTypes(cndReader, session, true);

All the code examples above should be performed in the @BeforeClass annotated method so that the repository is only created once for the entire test class. Otherwise a lot of overhead will be generated. Nevertheless, in the @Before and @After annotated methods, you should create your node structures and erase them again (addNode() etc).

Finally, after you have performed you test, you should cleanup the test environment again. Because a directory was created for the transient repository, we have to remove the directory again, otherwise the temp folder will grow over time.
There are three options for cleaning it up.
  1. Cleaning up in @AfterClass annotated method
  2. Cleaning up using File::deleteOnExit()
  3. Cleaning up using shutdown hook
I prefer combining 1 and 3 for fail-safe deletion. For option 1 we require a method to destroy the repository and cleaning up the directory. For deleting the directory I use Apache Commons FileUtil as it allows deletion of directory structures containing files and subdirectories. The method could look like this:

import org.apache.commons.io.FileUtils;
...
@AfterClass
public static void destroyRepository(){
  repository.shutdown();
  String repositoryLocation = repository.getHomeDir();
  try {
    FileUtils.deleteDirectory(new File(repositoryLocation));
  } catch (final IOException e) {
   ...
  }
  repository = null;
}

As fail-safe operation I prefer to add an additional shutdown hook that is executed when the JVM shuts down. This will delete the repository even when the @AfterClass method is not invoked by JUnit. I do not use the deleteOnExit() method of File as it requires the directory to be empty while I could call any code in the shutdown hook using my own cleanup implementation.


A shutdown hook can easily be added to the runtime by specifying a Thread to be executed on VM shutdown. We simply add a call to the destroy methode to the run() method.
Runtime.getRuntime().addShutdownHook(new Thread("Repository Cleanup") {
  @Override
  public void run() {
    destroyRepository();
  }
});

Now you should have everything to set-up you Test JCR Repositoy and tear-down the test environment. Happy Testing!

Tuesday, January 7, 2014

Technical Debt and Prototyping


Currently I am preparing for my IREB Requirements Engineering Certification and I read (and already knew before) that in Requirements Engineering creating a prototype is a method and tool to validate requirements.
A prototype should always answer a beforehand defined set of questions. Further, there are two basic types of prototypes.
  1. Throwaway Prototype - the prototype is not used further in the development process apart from being a technical example.
  2. Evolutionary Prototype - the prototype itself is continuously developed further until it becomes the final (or a shippable) product.
The question is, what role does prototyping play in agile development?

Let's first have a look at the dimensions of a prototype. The wikipedia article about prototyping describes the two dimensions of a prototype as
  • vertical (in german also known as "Durchstichprototyp"), in the meaning of a slice of the system to be developed, including frontend, business logic and persistent.
  • horizontal, in the meaning of an almost complete implementation of a single layer of a multi-layered architecture. I guess, the most common example of a horizontal prototype is a UI prototype. Also common are interface stubs.
I would assume, that vertical prototypes are closer to the principles of agile development, as they are more "something that is done" than a horizontal prototype.

Why is that?

In agile the concept of "technical debt" is known as a measure of design or software quality, or better, the lack of it. There are some strategies to deal with it, as described in the DAD's "11 Strategies to deal with technical debt".  Further, agile or scrum, aims at producing increments that provide value to the customer, in most of the cases, the "value" reflects some additions or changes to the UI, that the customer can validate relatively easy.

A vertical prototype is a potentially shippable or consumable piece of software in the meaning of agile. It is functional and it is done. Maybe it needs some refactoring, but basically the amount of technical debt is relatively low. One could even say, that producing an evolutionary, vertical prototype is doing agile (if done right).

On the opposite, a horizontal prototype produces only an artifact on one single layer of the software, which is in most cases the UI. The customer can validate, if the prototype reflects the requirements. But, the prototype itself provides no real value, as it is a dysfunctional piece of software, as all the stuff behind the UI is not implemented. In other words, a horizontal UI prototype is a huge pile of technical debt. The more complete the prototype is, the higher is the debt.
If a evolutionary, horizontal prototype is the starting point of the implementation (i.e. the product of a pre-project or the first iterations), it is relatively difficult to plan backlog entries for the further iterations with increments that "provide value" from a customer perspective. Ways to deal with it could be:
  • deliver only small increments that add value (i.e. slight adjustments to the UI) but that take more time than one would assume they would take. For example changing the background color takes a week. 
  • define the disabling of elements of the UI as added value and focus on only one element that is actually implemented, and the continue as ususal. This means actually to make the evolutionary prototype a throwaway or least major parts of it.
  • call a spade a spade and track it as chores that provide no actual value but a reduction of the technical debt and the stakeholders have to bite the bullet for getting several iterations with no added value
My suggestion would be to use either horizontal throwaway or vertical evolutionary prototypes.

The throwaway protoype - especially UI prototypes - could enrich the requirements descriptions as it provides a clearer vision, how the system should look like and partially how it behaves. Creating this prototype should be the job of the product owner or her assistant's. Wireframe models are a good example of such a prototype. The prototype can be decomposed into smaller user stories or tasks the development team implements, while keeping the entire prototype as a detailed version of a vision.

The evolutionary prototype is actually developed during iterations of the development process and it should always be of the vertical dimension which ensures, that every iterations produces something that is done.

Concluding, I would make the three suggestions:
  • if using a prototype as part of the requirements definition (job of the PO), use a horizontal throwaway prototype. I'd recommend a wireframe model.
  • creating a vertical evolutionary prototype is developing the product in an agile or at least iterative way
  • avoid creating horizontal evolutionary or vertical throwaway prototypes as they produce high technical debt or are unfinished work, which is bad.

tl;dr
horizontal prototypevertical prototype
throwaway prototypegood
done by PO as part of requirements
bad,
use only to demonstrate general feasibility to reduce high risks (proof-of-concept), produces waste
evolutionary prototypebad,
produces high technical debt, avoid if possible
good,
actual potentially shippable increment, done by development team

Friday, December 13, 2013

No EECH over EOS

Sounds cryptic, but if you remember, I tried to connect the EECH telemetry output via the siconbus with the Helios "Virtual Cockpit" using the EOS bus protocol. The bad news is: it won't work that way - for two reasons:
  1. the binary indicator information of EECH (various indicator lights) seem not to be exported. It seems to be a bug of a previous release that was either not fixed or reoccured. Anyway, there is no way at the moment to export the indicator light status and therefore there is no chance to map them to any Helios indicator light. The good news however is, the bridge from EECH-to-Helios over EOS works in principle, although it's not relevant with the current bug.
  2. There is no chance of mapping the telemetry information (altitude, speed, etc) to any of the analog inputs. The reason is, the analog EOS inputs have a discrete value range of 0 to 1024. For certain gauges (i.e. Altimeter), Helios only accepts continuous float values. The CommServer of EECH however does export float values. So there is simply one conclusion: EOS is not the right protocol to transport the telemetry data.
These two results lead to the following next steps.
First, I have to address the indicator export issues to the EECH developers community, so that those information will be exported again via CommServer.
Second, I have to find a different way of transporting the (float) telemetry data to Helios, and I already have an idea for that: Helios was initially created to receive the telemetry data from the LUA export of DCS. The format exported is a bit different to that of EECH (not much) and customizable (great!). Further, Helios provides a LUA export script for DCS out of the box, so I could use that. The major difference between DCS Lua Export and EECH CommServer is, that DCS pushes the data while EECH responds to requests. As Helios was intentionally designed to communicate with DCS, it expects the data to be pushed to it. And here comes in the siconbus. The idea is, to connect a "Pull"-Connector, that polls EECH for telemetry data with a "PUSH"-Connector that pushes the data to Helios. The polling interval is controlled by the Pull-Connector and the data is transformed on the bus.
Now I just need to find the time to implement this idea...

Thursday, December 12, 2013

Device Link

In the last weeks I've been a bit away from my simulator project as I was busy with education and certification activities. Anyway, I've finished the implementation of the device link support for the simulation interconnect bus (see last blog post). The device link protocol is rather simple, as it consists only of request and response messages with a set of key-value pairs. If its just a key, it marks a request for a value - usually sent in a request message - and if it contains a value, it denotes either a set request or a response to a value request. The protocol runs over UDP and I tested it with the Commserver of Enemy Engaged.
With Device Link API that I've implemented, it should be relatively easy to create a DeviceLink Client (or even Server) that is capable of sending or responding to requests. The API basically consist of two main interfaces:
  • DeviceLinkPacket - which could be a request or a response and may carry a list of parameters
  • DeviceLinkParameter - carrying the numerical id of the parameter and marks the parameter as either a getter or a setter parameter and may carry a value of the parameter's type
and a couple of supplemental interfaces (DeviceLinkParameterDefinition, DeviceLinkParameterSet and DeviceLinkPacketListener) and some default implmentations for the interfaces.
A central utility class (DeviceLink) provides methods for creating request and response packets of the API types as well as parse a string into a DeviceLinkParameterSet. The DeviceLinkClient allows to connect to a DeviceLinkServer, such as the Enemy Engaged CommServer. With the listener interface, functionality can be added to the client to react upon incoming packets.

For Enemy Engaged I defined an implementation of the DeviceLinkParameterSet (HelicopterType) and for the according DeviceLinkParameterDefinitions (EECHParameter) carrying the various parameters for the helicopters. Connecting to Enemy Engaged is rather simple and I implemented an example client (for my testing purposes).


try (DeviceLinkClient client = 
new DeviceLinkClient(HelicopterType.Any)){ 

  //eechhost is mapped to localhost in hosts file, port is 10000
  client.connect("eechhost", 10000);

  //output all packets to console
  client.addPacketListener(new DeviceLinkPacketListener(){

    public void onReceive(DeviceLinkPacket incoming) {
      for(DeviceLinkParameter p : incoming.getParameters()){
        System.out.println(p);
      }
    }

  });


    
  //poll the server periodically for values
  while(...){
    DeviceLinkPacket packet;
    ...                 
    final List params = new ArrayList<>();

    //request all telemetry parameters
    for(EECHParameter.Common c : EECHParameter.Common.values()){
      params.add(DeviceLink.createParameter(c));
    }
    packet = DeviceLink.createRequest(params);
  

    System.out.printf("\t --> (%s)\n", packet);
    client.send(packet);

  }
 

} catch (IOException e) {
 ...

}

To test and play with API you may download the source code from (Simulation Interconnect Bus) - its not packaged yet.
I'd be happy to know what you think of it.