Testing your first Android Things driver

Android with a person outfit. I like to travel and I love my 2 dogs. Sometimes I also like noisy things, like mechanical keyboards.

Controlling any peripheral device from an Android Things board requires a driver. If no driver is available it's down to you to write your own. Once that's written, how do you know it will keep working? You need to test it. Let’s take a look at unit testing for Android Things device drivers.


In my previous blog post we learnt about input/output protocols to write an Android Things driver for a new peripheral device. Taking the WS2801 chip protocol as an example and writing a driver to control its LEDs. In this blog post we’ll discuss how the driver can be unit tested to give us the confidence to use for a real project.

Testing to complete our hacking

This is probably the most important part of writing a driver, especially if you're going to share it with others: we have to make sure it works and we have to prove it with tests. If you haven't read this blog post on testing Android Things by my colleague Paul Blundell then I'd recommend you take a look now. It includes useful insights on how to tackle testing an Android Things app when peripheral devices are involved (spoiler: it doesn't involve peripherals).

As a TLDR: write your code as if the hardware was a replaceable component, just an implementation detail, so you can swap it with a fake implementation (or a mock) and test your code easily.

Android Things driver testing

Looking specifically at testing a driver, you can apply the same principle. This means the SpiDevice, Gpio or whatever component you use, should be an external collaborator that gets injected into the constructor (this may sound similar to the dependency inversion principle). This will allow you to use a mock object during your tests to abstract you from the platform. Here’s how the first iteration of Ws2801Test.java might look:

@RunWith(MockitoJUnitRunner.class)
public class Ws2801Test {

  @Mock
  private SpiDevice device;

  private Ws2801 driver;

  @Before
  public void setUp() throws IOException {
    driver = new Ws2801(device);
  }

  @Test
  public void configures1MHzClockFrequencyWhenCreated() throws Exception {
    verify(device).setFrequency(1_000_000);
  }

  @Test
  public void configuresClockToTransmitOnLeadingEdgeModeWhenCreated() throws Exception {
    verify(device).setMode(SpiDevice.MODE0);
  }

  @Test
  public void configuresBusToSend8BitsPerColorComponentWhenCreated() throws Exception {
    verify(device).setBitsPerWord(8);
  }

  @Test
  public void writesToSpiDeviceWhenWriting() throws Exception {
    int[] anyColors = {Color.RED, Color.DKGRAY, Color.GREEN, Color.WHITE, Color.YELLOW};
    driver.write(anyColors);

    verify(device).write(any(byte[].class), anyInt());
  }
}

One problem you may face when unit testing a driver is that you may be relying on some of Android’s built in utilities (mainly static methods) that aren’t available in a test environment as these tests will run on your computer using a mockable android.jar1.

In my case I had a method in Ws2801.java that returned a byte[] with the red, green and blue values of a given colour. It looked like this:

  byte[] getOrderedRgbBytes(int color) {
    int r = Color.red(color);
    int g = Color.green(color);
    int b = Color.blue(color);
    switch (ledMode) { // The LED mode is an enum that specifies the order the red, green and blue values will be sent to the LED strip
      case RBG:
        return new byte[]{(byte) r, (byte) b, (byte) g};
      case BGR:
        return new byte[]{(byte) b, (byte) g, (byte) r};
      case BRG:
        return new byte[]{(byte) b, (byte) r, (byte) g};
      case GRB:
        return new byte[]{(byte) g, (byte) r, (byte) b};
      case GBR:
        return new byte[]{(byte) g, (byte) b, (byte) r};
      default:
        throw new IllegalArgumentException(ledMode.name() + " is an unknown Mode.”);
    }
  }

When executing the above method during a JUnit test, a Stub! error would be thrown because the methods in Color couldn’t be mocked. Color is an Android framework class with static methods. To avoid this Stub! problem there’s a couple of things you can do:

  1. Use mocking framework that makes use of bytecode manipulation to mock static methods, such as Powermock.
  2. Extract the behaviour that relies on the non-mockable components and test them separately (without testing the framework).

I went for option 2, since mocking static methods felt a bit like cheating. I extracted a ColorUnpacker class to hold that method, which can be unit tested separately and mocked in the original tests. This is the result:

class ColorUnpacker {

  private final Mode ledMode;

  ColorUnpacker(Mode ledMode) {
    this.ledMode = ledMode;
  }

  byte[] unpack(int color) {
    int r = Color.red(color);
    int g = Color.green(color);
    int b = Color.blue(color);
    return getOrderedRgbBytes(ledMode, (byte) r, (byte) g, (byte) b);
  }

  static byte[] getOrderedRgbBytes(Mode ledMode, byte r, byte g, byte b) {
    switch (ledMode) {
      case RBG:
        return new byte[]{r, b, g};
      case BGR:
        return new byte[]{b, g, r};
      // … etc.
    }
  }

}

Now the setUp method of the Ws2801Test class should look something like this:

  @Mock
  private ColorUnpacker unpacker;

  @Before
  public void setUp() throws IOException {
    driver = new Ws2801(device, unpacker);
    when(unpacker.unpack(anyInt())).thenReturn(new byte[]{0, 1, 42}); // The values in the array don’t matter as long as it’s the right size
  }

With this, changing the direction of the LED strip is very easy to test because we only need to verify that the unpack method in ColorUnpacker gets called as many times as we expect with the expected values. Since the new class is small and simple, we can easily unit test it too:

public class ColorUnpackerTest {

  private static final byte R = (byte) 111;
  private static final byte G = (byte) 222;
  private static final byte B = (byte) 333;
  // The values of the constants above don’t matter

  @Test
  public void orderedBytesWhenModeIsRBG() {
    Ws2801.Mode mode = Ws2801.Mode.RBG;

    byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);

    assertBytesOrder(result, R, B, G);
  }

  @Test
  public void orderedBytesWhenModeIsBGR() {
    Ws2801.Mode mode = Ws2801.Mode.BGR;

    byte[] result = ColorUnpacker.getOrderedRgbBytes(mode, R, G, B);

    assertBytesOrder(result, B, G, R);
  }

  // … and so on

  private void assertBytesOrder(byte[] bytes, byte... order) {
    assertEquals(order[0], bytes[0]);
    assertEquals(order[1], bytes[1]);
    assertEquals(order[2], bytes[2]);
  }

}

Notice that in Ws2801Test.java we’re verifying the internal behaviour of the Ws2801 driver and ensuring it interacts correctly with the SpiDevice. In ColorUnpackerTest.java we’re asserting if a function returns the expected result given predefined inputs.

Here's the full source code for the unit tests.

Conclusion

We made it! At this point we can use this driver in multiple projects really easily and have the confidence from our test that it behaves correctly. We could even publish the driver in a maven repository so that it can be used by other developers.

I hope you enjoyed the process and found this informative. I think once you’ve made one driver, the next time should be a lot easier as you’ll be familiar with the terminology in datasheets and you’ll have some knowledge of which pins can be used for what in your Android Things device.

To learn more, download the Android Things SDK documentation. It will go up on the Android Developer website once it comes out of preview.

You can find the full source code of this blog post on Github and please feel free to chat with me about this on Twitter or Google+. Happy coding and happy hacking!

high paw

Last but not least I’d like to give a big thanks to Paul Blundell for reviewing this post.


  1. By default, the Android Plug-in for Gradle executes your local unit tests against a modified version of the android.jar library, which does not contain any actual code. Instead, method calls to Android classes from your unit test throw an exception. This is to make sure you test only your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked).

About Novoda

We plan, design, and develop the world’s most desirable Android products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact