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

How to unit test methods that interact with System (or Android) classes

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

假设你有那些 类:

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

被测系统与实现问题的耦合过于紧密。尽量避免嘲笑你不拥有的 classes。抽象接口背后的代码并将责任委托给在 运行 时间恰好在接口背后的任何实现。

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

public interface BuildProvider {
    public string Model;
}

重构依赖 class 以依赖抽象而非具体化(实现问题)。

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