2016-11-24 6 views
4

Как вам удается записывать модульные тесты, которые взаимодействуют с системными классами, т.е. классы Android Framework?Как модульные методы тестирования, которые взаимодействуют с классами System (или 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

+0

абстрактный контекст конкретного кода, так что он может быть издевались для модульного тестирования. старайтесь избегать насмешек, которыми вы не владеете. – Nkosi

+0

- это «DeviceInfoProvider», предназначенный для использования в качестве зависимости? – Nkosi

+0

вопрос в том, почему вы хотите проверить это? Не доверяете ли вы парням Google? Вы должны сначала выполнить тестирование * своего * кода и выставить из строя зависимости, за которые вы несете ответственность. –

ответ

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

Спасибо за ваш ответ, это хороший совет. Но если вы хотите протестировать новый DisplayProvider и BuildProvider, вам нужно будет издеваться над системными классами в конце, верно? – Addev

+0

Опять же, эти классы будут охватывать проблемы с реализацией, которые уже были бы проверены их поставщиками. Это также приведет к удалению модуля тестирования домена и переходу на интеграционное тестирование. – Nkosi

+0

Как насчет насмешек ** Контекст **? –