【问题标题】:How to unit test methods that interact with System (or Android) classes如何对与系统(或 Android)类交互的方法进行单元测试
【发布时间】:2017-04-09 01:58:43
【问题描述】:

您如何编写与系统类(即 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

【问题讨论】:

  • 抽象上下文特定代码,以便对其进行模拟以进行单元测试。尽量避免嘲笑你不拥有的课程。
  • DeviceInfoProvider 打算用作依赖项吗?
  • 问题是:你为什么要测试这个?你不相信谷歌的人吗?您应该首先对您的代码进行单元测试,并模拟出您不负责的依赖项。

标签: java android unit-testing mockito powermock


【解决方案1】:

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

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

【讨论】:

  • 感谢您的回答,这是一个很好的建议。但是,如果你想测试新的 DisplayProvider 和 BuildProvider,你需要在最后模拟系统类,对吧?
  • 同样,这些类将包装已经由其提供者进行可扩展测试的实现问题。这也将离开单元测试领域并进入集成测试。
  • 模拟 Context 怎么样?
  • @IgorGanapolsky 你不模拟上下文。这是一个实现问题,只有派生的实现应该关注。抽象该依赖关系的全部原因是您不必模拟它,而是围绕它进行抽象。
猜你喜欢
  • 1970-01-01
  • 2013-07-03
  • 2023-03-17
  • 2013-12-12
  • 2020-06-22
  • 2012-06-20
  • 1970-01-01
  • 1970-01-01
  • 2011-01-15
相关资源
最近更新 更多