5

I am starting with an Activity based off of this ShakeActivity and I want to write some unit tests for it. I have written some small unit tests for Android activities before but I'm not sure where to start here. I want to feed the accelerometer some different values and test how the activity responds to it. For now I'm keeping it simple and just updating a private int counter variable and a TextView when a "shake" event happens.

So my question largely boils down to this:

How can I send fake data to the accelerometer from a unit test?

Corey Sunwold
  • 10,194
  • 6
  • 51
  • 55

5 Answers5

5

My solution to this ended up way simpler then I expected. I'm not really testing the accelerometer so much as I am testing the application's response to an event raised by the accelerometer, and I just needed to test accordingly. My class implements SensorListener and I wanted to test what happens onSensorChanged. The key then was to feed in some values and check my Activity's state. Example:

public void testShake() throws InterruptedException {
    mShaker.onSensorChanged(SensorManager.SENSOR_ACCELEROMETER, new float[] {0, 0, 0} );
    //Required because method only allows one shake per 100ms
    Thread.sleep(500);
    mShaker.onSensorChanged(SensorManager.SENSOR_ACCELEROMETER, new float[] {300, 300, 300});
    Assert.assertTrue("Counter: " + mShaker.shakeCounter, mShaker.shakeCounter > 0);
}
Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
Corey Sunwold
  • 10,194
  • 6
  • 51
  • 55
4

How can I send fake data to the accelerometer from a unit test?

AFAIK, you can't.

Have your shaker logic accept a pluggable data source. In the unit test, supply a mock. In production, supply a wrapper around the accelerometer.

Or, don't worry about unit testing the shaker itself, but rather worry about unit testing things that use the shaker, and create a mock shaker.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • That certainly makes sense and does sound like a better idea. Do you have any examples of using a "pluggable data source" like this? – Corey Sunwold May 11 '10 at 18:53
  • Is this answer still up to date? Or has something new come out? – TinyTimZamboni Apr 24 '14 at 19:05
  • 1
    @TinyTimZamboni: I seem to recall that the Android Tools team was working on a way to use an Android device as sensor input for an emulator, but I don't know where that stands. Otherwise, I am not aware of any way to provide fake sensor input. – CommonsWare Apr 24 '14 at 19:26
  • Isn't it possible to send `sensor` commands via a telnet connection? http://stackoverflow.com/questions/3921467/how-can-i-simulate-accelerometer-in-android-emulator/20361772#20361772 – Dan Dascalescu Apr 20 '15 at 09:28
1

Well, you can write an interface.

interface IAccelerometerReader {
    public float[] readAccelerometer();
}

The write an AndroidAccelerometerReader and FakeAccelerometerReader. Your code would use IAccelerometerReader but you can swap in the Android or Fake readers.

Nikhil
  • 16,194
  • 20
  • 64
  • 81
Ray
  • 2,974
  • 20
  • 26
0

No need to test the OS's accelerometer, just test your own logic that responds to the OS - in other words your SensorListener. Unfortunately SensorEvent is private and I could not call SensorListener.onSensorChanged(SensorEvent event) directly, so had to first subclass SensorListener with my own class, and call my own method directly from the tests:

public  class ShakeDetector implements SensorEventListener {

     @Override
     public void onSensorChanged(SensorEvent event) {

         float x = event.values[0];
         float y = event.values[1];
         float z = event.values[2];

         onSensorUpdate(x, y, z);
     }

     public void onSensorUpdate(float x, float y, float z) {
         // do my (testable) logic here
     }
}

Then I can call onSensorUpdated directly from my test code, which simulates the accelerometer firing.

private void simulateShake(final float amplitude, int interval, int duration) throws InterruptedException {
    final SignInFragment.ShakeDetector shaker = getFragment().getShakeSensorForTesting();
    long start = System.currentTimeMillis();

    do {
        getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                shaker.onSensorUpdate(amplitude, amplitude, amplitude);
            }
        });
        Thread.sleep(interval);
    } while (System.currentTimeMillis() - start < duration);
}
Marc Attinasi
  • 5,644
  • 1
  • 16
  • 7
0
  public  class SensorService implements SensorEventListener {
/**
     * Accelerometer values
     */
    private float accValues[] = new float[3];
     @Override
     public void onSensorChanged(SensorEvent event) {

          if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            accValues[0] = sensorEvent.values[0];
            accValues[1] = sensorEvent.values[1];
            accValues[2] = sensorEvent.values[2];
        }

     }
} 

you can test above piece of code by following way

@Test
    public void testOnSensorChangedForAcceleratorMeter() throws Exception {
        Intent intent=new Intent();
        sensorService.onStartCommand(intent,-1,-1);

        SensorEvent sensorEvent=getEvent();
        Sensor sensor=getSensor(Sensor.TYPE_ACCELEROMETER);
        sensorEvent.sensor=sensor;
        sensorEvent.values[0]=1.2345f;
        sensorEvent.values[1]=2.45f;
        sensorEvent.values[2]=1.6998f;
        sensorService.onSensorChanged(sensorEvent);

        Field field=sensorService.getClass().getDeclaredField("accValues");
        field.setAccessible(true);
        float[] result= (float[]) field.get(sensorService);
        Assert.assertEquals(sensorEvent.values.length,result.length);
        Assert.assertEquals(sensorEvent.values[0],result[0],0.0f);
        Assert.assertEquals(sensorEvent.values[1],result[1],0.0f);
        Assert.assertEquals(sensorEvent.values[2],result[2],0.0f);
    } 




private Sensor getSensor(int type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
            Constructor<Sensor> constructor = Sensor.class.getDeclaredConstructor(new Class[0]);
            constructor.setAccessible(true);
            Sensor sensor= constructor.newInstance(new Object[0]);

            Field field=sensor.getClass().getDeclaredField("mType");
            field.setAccessible(true);
            field.set(sensor,type);
            return sensor;
        }



private SensorEvent getEvent() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<SensorEvent> constructor = SensorEvent.class.getDeclaredConstructor(int.class);
        constructor.setAccessible(true);
        return constructor.newInstance(new Object[]{3});
    }
Sanjoy Saha
  • 133
  • 3
  • 14