One of the big challenges developing applications for industrial mobile computers is testing. Typically, these applications will be built around data acquisition either through scanning barcodes, reading RFID tags, taking credit card payment, capturing images via the camera or capturing form data programmatically to extract text with OCR.
Any data capture will be challenging to test since it depends on manual interaction with the hardware. A barcode will be scanned or RFID tags read by pulling the device trigger, credit cards will need to be swiped with a reader generally connected via Bluetooth (with all the connectivity issues that further challenge a tester) and images or forms captured with the camera depend on user interaction to focus the camera and ensure the subject of the photo is in the field of view.
Consumer applications often rely on device emulators to expedite, automate and streamline their testing. Emulators can mock data from the orientation sensors, camera, GPS location etc. but since no emulators exist for industrial mobile devices and their data capture hardware, only a small portion of applications targeting those can be tested before moving to a real device. The move to the real device will almost always necessitate a corresponding move towards manual testing given the amount of physical interaction with the hardware required.
A Better Approach
Developers of consumer applications have for the longest time built automated testing into their application from day 1. This blog makes no attempt to give an in-depth explanation of testing frameworks since there are innumerable resources online for that and if you are looking for information on testing Android applications a good place to start is Google’s own documentation at https://developer.android.com/training/testing/index.html#start.
Testing takes two forms:
- Local tests for individual algorithms and classes, these run within the local JVM and therefore do not have access to Android APIs.
- Instrumented tests which comprise of both unit tests and more complex user interface tests. Instrumented tests run on the device.
On Android, it is recommended you would write your tests using JUnit and extend the capabilities of your tests to handle UI interaction using the Espresso framework. Again, the Android documentation goes into a lot more detail here and is essential reading for anybody unfamiliar with the topic.
THE GOAL IS TO CREATE AUTOMATED JUNIT TESTS WHICH EXERCISE THE DATA CAPTURE HARDWARE.
Mocking the Hardware
In many industries, the solution to test code modules that depend on external hardware is to mock that hardware, or at least the interface to it. As an application developer, to test data capture hardware in an automated fashion you would either want a full emulator or at least be able to mimic the interface to that hardware in your instrumented tests.
Unfortunately, as previously mentioned no full emulators are available for industrial mobility devices and integration with test frameworks is not offered out of the box.
Options for Capturing Data
Concentrating on Zebra Android mobile computers such as the TC51 and TC75 there are a number of ways to interact with the data capture hardware:
- Through a dedicated Android API
- Through a dedicated Xamarin API
- Through Intents (DataWedge)
- Through some combination of the above e.g. Android API + DataWedge.
And each device has dedicated hardware for capturing data:
- Barcode scanner
- SimulScan to capture form data and perform OCR
- RFID reader (on selected models)
- NFC reader (on selected models)
- Bluetooth to connect with:
- Payment devices
- Barcode scanners
- Card reader (on selected models)
- USB connected hardware
Clearly a single testing solution to cover all scenarios is unrealistic and Zebra do not offer any test variants of their device interfaces. It is therefore left as an exercise to the developer to mock the hardware interface as required.
One Possible Solution
As previously mentioned, the number of combinations of API languages and data capture hardware is large. I therefore present a single solution to the most common use case, testing barcode scanning through the Android API. Obviously, it would be possible to mock other APIs but please take this as a proof of concept.
The EMDK Barcode API
The EMDK Barcode API is the API used to interface with the barcode scanning hardware on Zebra mobile computers and is documented here:
The main class (com.symbol.emdk.barcode) returns:
- Status to the application via the Scanner.StatusListener
- Data (decoded barcodes) to the application via the Scanner.DataListener
- Scanner connectivity changes to the application via the BarcodeManager.ScannerConnectionListener. This is used primarily for Bluetooth connected scanners.
In order to test an application that utilizes the barcode scanner we need to be able to invoke these interfaces with test data whilst running our instrumented tests, for example.:
// Click the start scan button
// Simulate a barcode being scanned
ScanDataCollection scanDataCollection = mockedInterface.ReportScan(success);
// Trigger the data listener
// Test that the correct data was scanned
// Click the stop scan button
It feels as though the Barcode interface was designed specifically to make it difficult to mock the returned data, though I am sure this is not the case(!) The data types returned by the various interface methods only have private constructors, for example the payload of the onStatus method is a StatusData object. In order to mock the onStatus call we need to create a StatusData object but no public constructors exist (even the default constructor) and there are no setters exposed on the public interface.
We are therefore required to use Java reflection to create these interface payload objects. Obviously since this technique is not officially supported by Zebra there is a possibility they will change their internal data structures or refactor the private code at some point but for the purposes of this blog, all samples have been written to work with EMDK 6.0, the latest release at the time.
I have done the hard work of stubbing the listener payload objects in a helper class here: [Github Link]. There are a number of methods:
- Returns a StatusData object which can be passed to the StatusListener. Takes the desired status as a parameter.
- A single barcode scan (trigger press) can contain multiple decoded barcodes, for this reason mocking a scan has two phases and in the first you call this method multiple times to add all the data you want the scan to return.
- Each call to AddScanData takes the barcode data you want returned, the symbology of the read barcode and a timestamp.
- Once all the barcode data has been given through AddScanData, call this method to return a ScanDataCollection object with the mocked data. ScanDataCollection is the payload passed to the DataListener. You can also choose to have the scanner report that the read failed through this API.
- Creates a mocked ScannerInfo object using the passed attributes to describe the scanner. Defining a scanner takes a lot of attributes so you may wish to refer to the example at [Github Link].
- The onConnectionChange interface method takes a ScannerInfo object along with a BarcodeManager.ConnectionState object to indicate whether that scanner is connected or disconnected.
I have put together a very simple example of how to test the barcode scanner with mocked data:
- The example is available on GitHub, https://github.com/darryncampbell/Instrumented-EMDK-Barcode-Testing.
- Clone the repository and load into Android Studio.
- Take care to modify the app build.gradle file to point to your EMDK installation within your Android add-ons directory.
- You must run the sample on a Zebra mobile computer, you cannot run on an emulator as the mocked interface depends on libraries available on the device.
- If you have an older JellyBean device you may need to update the device runtime, see the EMDK release notes.
- The tests are built around Zebra’s ubiquitous BarcodeScanner1 sample project
- Four tests are defined in the instrumented test file.
- Testing a successful scan
- Testing an unsuccessful scan
- Testing reception of a status error whilst scanning
- Testing a Bluetooth scanner disconnecting.
- All tests make use of a helper class which encapsulates the logic of reflecting the EMDK interface to create objects which can be returned through the listener interfaces, as described in the previous section