Правильное (и упрощенное) тестирование источника данных - PullRequest
0 голосов
/ 26 марта 2019

Я недавно начал заниматься тестированием (TDD), и мне было интересно, сможет ли кто-нибудь пролить свет на практику, которую я делаю.Например, я проверяю, доступен ли поставщик местоположения, реализую класс контракта (источник данных) и оболочку, например:

LocationDataSource.kt

interface LocationDataSource {

  fun isAvailable(): Observable<Boolean>

}

LocationUtil.kt

class LocationUtil(manager: LocationManager): LocationDataSource {

  private var isAvailableSubject: BehaviorSubject<Boolean> = 
      BehaviorSubject.createDefault(manager.isProviderEnabled(provider))

  override fun isAvailable(): Observable<Boolean> = locationSubject

}

Теперь, когда я тестирую, я не уверен, что делать дальше.Первое, что я сделал, это издевался над LocationManager и isProviderEnabled методом:

class LocationTest {

  @Mock
  private lateinit var context: Context

  private lateinit var dataSource: LocationDataSource
  private lateinit var manager: LocationManager

  private val observer = TestObserver<Boolean>()

  @Before
  @Throws(Exception::class)
  fun setUp(){
    MockitoAnnotations.initMocks(this)

    // override schedulers here

    `when`(context.getSystemService(LocationManager::class.java))
        .thenReturn(mock(LocationManager::class.java))

    manager = context.getSystemService(LocationManager::class.java)
    dataSource = LocationUtil(manager)
  }

  @Test
  fun isProviderDisabled_ShouldReturnFalse(){
    // Given
    `when`(manager.isProviderEnabled(anyString())).thenReturn(false)

    // When
    dataSource.isLocationAvailable().subscribe(observer)

    // Then
    observer.assertNoErrors()
    observer.assertValue(false)
  }

}

Это работает.Тем не менее, во время моего исследования о том, как сделать то и это , время, потраченное на то, чтобы выяснить, как имитировать LocationManager, было достаточно большим, чтобы (я думаю) нарушить одно из общих правил в TDD -тестовая реализация не должна занимать слишком много времени.

Итак, я решил, что будет лучше (и все еще в рамках области TDD) просто протестировать сам контракт (LocationDataSource)?Насмешка dataSource и последующая замена вышеприведенного теста на:

@Test
fun isProviderDisable_ShouldReturnFalse() {
    // Given
    `when`(dataSource.isLocationAvailable()).thenReturn(false)

    // When
    dataSource.isLocationAvailable().subscribe(observer)

    // Then
    observer.assertNoErrors()
    observer.assertValue(false)
}

Это (очевидно) дало бы тот же результат без проблем с издевательством над LocationManager.Но я думаю, что это противоречит цели теста - поскольку он фокусируется только на самом контракте, а не на фактическом классе, который его использует.

Я все еще думаю, что, возможно, первая практика все еще правильнапуть.Это поначалу просто нужно время, чтобы ознакомиться с насмешливыми классами Android.Но я хотел бы знать, что думают эксперты по TDD.

1 Ответ

1 голос
/ 27 марта 2019

Работа в обратном направлении ... это выглядит немного странно:

// Given
`when`(dataSource.isLocationAvailable()).thenReturn(false)

// When
dataSource.isLocationAvailable().subscribe(observer)

У вас mock(LocationDataSource) разговаривает с TestObserver.Этот тест не совсем бесполезен, но если я не ошибаюсь, запуск ничего нового не скажет;если код компилируется , то контракт выполняется.

На языке, где у вас есть надежная проверка типов, у выполненных тестов должен быть объект тестирования, являющийся производственной реализацией.Так что в вашем втором примере, если бы observer был испытуемым, это было бы «хорошо».

Я бы не прошел этот тест в обзоре кода - если только на расстоянии не происходит жуткая рекурсиянет причин для насмешки над вызовом метода, который вы собираетесь сделать в самом тесте.

// When
BehaviorSubject.createDefault(false).subscribe(testSubject);

время, потраченное на то, чтобы выяснить, как смоделировать LocationManager, было достаточно большим, чтобы (Я думаю) нарушить одно из общих правил в TDD - реализация теста не должна занимать слишком много времени.

Правильно - ваш текущий дизайн борется с вами, когда вы пытаетесь его протестировать.Это симптом;Ваша задача как дизайнера состоит в том, чтобы выявить проблему.

В этом случае код, который вы пытаетесь проверить, слишком тесно связан с LocationManager. обычно создает интерфейс / контракт, за которым можно скрыть конкретную реализацию.Иногда этот шаблон называется seam.

LocationManager::isProviderEnabled, снаружи, это просто функция, которая принимает String и возвращает логическое значение.Таким образом, вместо того, чтобы писать свой метод в терминах LocationManager, напишите его в терминах возможностей, которые он даст вам:

class LocationUtil(isProviderEnabled: (String) -> boolean ) : LocationDataSource {

  private var isAvailableSubject: BehaviorSubject<Boolean> = 
      BehaviorSubject.createDefault(isProviderEnabled(provider))

  override fun isAvailable(): Observable<Boolean> = locationSubject
}

По сути, мы пытаемся подтолкнуть «трудно проверить»"биты ближе к границам , где мы будем полагаться на другие методы для устранения рисков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...