How can I unit test an Android Activity that acts on Accelerometer?
I am starting with an Activit开发者_如何学编程y 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});
}
精彩评论