正确(和简化)数据源测试
Proper (and Simplified) Testing of a Data Source
我最近开始进行测试 (TDD),想知道是否有人可以阐明我正在做的练习。例如,我正在检查位置提供程序是否可用,我实现了一个合同(数据源)class 和一个包装器,如下所示:
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
的麻烦。但是,我认为这违背了测试的目的——因为它只关注合约本身——而不是使用它的实际 class。
我还是觉得,也许先练才是正道。最初,只需要时间来熟悉 Android classes 的 mocking。但我很想知道 TDD 专家的想法。
向后工作...这看起来有点奇怪:
// Given
`when`(dataSource.isLocationAvailable()).thenReturn(false)
// When
dataSource.isLocationAvailable().subscribe(observer)
您有一个 mock(LocationDataSource)
正在与一个 TestObserver
交谈。该测试并非完全没有价值,但如果我没记错的话 运行 没有告诉您任何新内容;如果代码 编译 ,则合同满足。
在具有可靠类型检查的语言中,执行的测试应该有一个作为生产实现的测试主题。所以在你的第二个例子中,如果 observer
是一个测试对象,那就是 "fine".
我不会在代码审查中通过该测试 - 除非在一定距离内发生令人毛骨悚然的递归,否则没有理由模拟您将在测试本身中进行的方法调用。
// When
BehaviorSubject.createDefault(false).subscribe(testSubject);
the time I spent figuring out how to mock the LocationManager was big enough to (I think) break one of the common rules in TDD -- a test implementation should not consume too much time.
是的 - 当您尝试测试时,您当前的设计正在与您作对。这是一个症状;作为设计师,您的工作是找出问题所在。
在这种情况下,您要测试的代码与 LocationManager
耦合得太紧了。 common 创建一个 interface/contract 可以隐藏特定的实现。有时这种模式被称为 seam
.
LocationManager::isProviderEnabled
,从外面看,只是一个接受 String
和 returns 布尔值的函数。因此,与其根据 LocationManager
来编写方法,不如根据它将为您提供的功能来编写方法:
class LocationUtil(isProviderEnabled: (String) -> boolean ) : LocationDataSource {
private var isAvailableSubject: BehaviorSubject<Boolean> =
BehaviorSubject.createDefault(isProviderEnabled(provider))
override fun isAvailable(): Observable<Boolean> = locationSubject
}
实际上,我们正试图将 "hard to test" 位推向更接近 boundaries 的位置,我们将依靠其他技术来解决风险。
我最近开始进行测试 (TDD),想知道是否有人可以阐明我正在做的练习。例如,我正在检查位置提供程序是否可用,我实现了一个合同(数据源)class 和一个包装器,如下所示:
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
的麻烦。但是,我认为这违背了测试的目的——因为它只关注合约本身——而不是使用它的实际 class。
我还是觉得,也许先练才是正道。最初,只需要时间来熟悉 Android classes 的 mocking。但我很想知道 TDD 专家的想法。
向后工作...这看起来有点奇怪:
// Given
`when`(dataSource.isLocationAvailable()).thenReturn(false)
// When
dataSource.isLocationAvailable().subscribe(observer)
您有一个 mock(LocationDataSource)
正在与一个 TestObserver
交谈。该测试并非完全没有价值,但如果我没记错的话 运行 没有告诉您任何新内容;如果代码 编译 ,则合同满足。
在具有可靠类型检查的语言中,执行的测试应该有一个作为生产实现的测试主题。所以在你的第二个例子中,如果 observer
是一个测试对象,那就是 "fine".
我不会在代码审查中通过该测试 - 除非在一定距离内发生令人毛骨悚然的递归,否则没有理由模拟您将在测试本身中进行的方法调用。
// When
BehaviorSubject.createDefault(false).subscribe(testSubject);
the time I spent figuring out how to mock the LocationManager was big enough to (I think) break one of the common rules in TDD -- a test implementation should not consume too much time.
是的 - 当您尝试测试时,您当前的设计正在与您作对。这是一个症状;作为设计师,您的工作是找出问题所在。
在这种情况下,您要测试的代码与 LocationManager
耦合得太紧了。 common 创建一个 interface/contract 可以隐藏特定的实现。有时这种模式被称为 seam
.
LocationManager::isProviderEnabled
,从外面看,只是一个接受 String
和 returns 布尔值的函数。因此,与其根据 LocationManager
来编写方法,不如根据它将为您提供的功能来编写方法:
class LocationUtil(isProviderEnabled: (String) -> boolean ) : LocationDataSource {
private var isAvailableSubject: BehaviorSubject<Boolean> =
BehaviorSubject.createDefault(isProviderEnabled(provider))
override fun isAvailable(): Observable<Boolean> = locationSubject
}
实际上,我们正试图将 "hard to test" 位推向更接近 boundaries 的位置,我们将依靠其他技术来解决风险。