如何对与System(或Android)类交互的测试方法进行单元化

时间:2016-11-24 17:19:51

标签: java android unit-testing mockito powermock

您如何设法编写与系统类交互的单元测试,即Android Framework类?

想象一下你有这些课程:

public class DeviceInfo {
    public final int screenWidth, screenHeight;
    public final String model;

    public DeviceInfo(int screenWidth, int screenHeight, String deviceModel) {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;
        this.model = deviceModel;
    }

}

public class DeviceInfoProvider {
    private final Context context;

    public DeviceInfoProvider(Context context) {
        this.context = context;
    }

    public DeviceInfo getScreenParams() {
        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
        int screenWidth = metrics.widthPixels;
        int screenHeight = metrics.heightPixels;
        String model= Build.MODEL;
        DeviceInfo params = new DeviceInfo(screenWidth, screenHeight, model);
        return params;
    }
}

如何编写测试以验证方法DeviceInfoProvider.getScreenParams()的正确行为。

以下测试通过,但它非常丑陋和脆弱:

@Test
public void testGetScreenParams() throws Exception {
    // Setup
    Context context = spy(RuntimeEnvironment.application);
    DeviceInfoProvider deviceInfoProvider = new DeviceInfoProvider(context);

    // Stub
    WindowManager mockWindowManager = mock(WindowManager.class);
    Display mockDisplay = mock(Display.class);
    when(context.getSystemService(Context.WINDOW_SERVICE)).thenReturn(mockWindowManager);
    when(mockWindowManager.getDefaultDisplay()).thenReturn(mockDisplay);
    doAnswer(new Answer() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            DisplayMetrics metrics = (DisplayMetrics) invocation.getArguments()[0];
            metrics.scaledDensity = 3.25f;
            metrics.widthPixels = 1081;
            metrics.heightPixels = 1921;
            return null;
        }
    }).when(mockDisplay).getMetrics(any(DisplayMetrics.class));

    // Run
    DeviceInfo deviceInfo = deviceInfoProvider.getScreenParams();

    // Verify
    assertThat(deviceInfo.screenWidth, equalTo(1081));
    assertThat(deviceInfo.screenHeight, equalTo(1921));
    assertThat(deviceInfo.model, equalTo(Build.MODEL));
}

你会如何改进?

注意:目前我正在使用Robolectric,Mockito和PowerMock

1 个答案:

答案 0 :(得分:6)

受测系统与实施问题紧密相关。尽量避免模仿你不拥有的课程。接口背后的抽象代码,并将责任委托给运行时接口后面的任何实现。

public interface DisplayProvider {
    public int widthPixels;
    public int heightPixels;
}

public interface BuildProvider {
    public string Model;
}

重构依赖类依赖于抽象而非结果(实现问题)。

public class DeviceInfoProvider {
    private final DisplayProvider display;
    private final BuildProvider build;

    public DeviceInfoProvider(DisplayProvider display, BuildProvider build) {
        this.display = display;
        this.build = build;
    }

    public DeviceInfo getScreenParams() {
        int screenWidth = display.widthPixels;
        int screenHeight = display.heightPixels;
        String model = build.Model;
        DeviceInfo params = new DeviceInfo(screenWidth, screenHeight, model);
        return params;
    }
}

单独进行单元测试

@Test
public void testGetScreenParams() throws Exception {
    // Arrange
    DisplayProvider mockDisplay = mock(DisplayProvider.class);
    BuildProvider mockBuild = mock(BuildProvider.class);        
    DeviceInfoProvider deviceInfoProvider = new DeviceInfoProvider(mockDisplay, mockBuild);

    when(mockDisplay.widthPixels).thenReturn(1081);
    when(mockDisplay.heightPixels).thenReturn(1921);
    when(mockBuild.Model).thenReturn(Build.MODEL);

    // Act
    DeviceInfo deviceInfo = deviceInfoProvider.getScreenParams();

    // Assert
    assertThat(deviceInfo.screenWidth, equalTo(1081));
    assertThat(deviceInfo.screenHeight, equalTo(1921));
    assertThat(deviceInfo.model, equalTo(Build.MODEL));
}