简体   繁体   中英

How can I unit test an Android Activity that acts on Accelerometer?

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?

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);
}

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.

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.

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);
}
  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});
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM