我正在为 MVP 项目的演示者编写单元测试。
class SplashPresenter {
override fun onAttach() {
if (dataManager.getAppLaunchFirstTime()) {
onAppOpenFirstTime()
} else {
// Other logic...
}
}
@VisibleForTesting
fun onAppOpenFirstTime() {
dataManager.setAppLaunchFirstTime(false)
splashView.openLoginScreen()
// May be I will add more functionalities in the future...
}
}
这里我们有2 种编写单元测试的方法。
第一种方法
直接验证将会发生什么:openLoginScreen()
&setAppLaunchFirstTime(false)
。不在乎他们怎么称呼。
@Test
fun onAttachTest_appLaunchFirstTime() {
Mockito.`when`(dataManager.getAppLaunchFirstTime()).thenReturn(true)
splashPresenter.onAttach()
verify(dataManager).setAppLaunchFirstTime(false)
verify(splashView).openLoginScreen()
}
第二种方法
我们使用spy()
来验证将调用onAppOpenFirstTime()
私有内部方法。然后为onAppOpenFirstTime()
编写另一个单独的测试。
@Test
fun onAttachTest_appLaunchFirstTime() {
`when`(dataManager.getAppLaunchFirstTime()).thenReturn(true)
val spyPresenter = Mockito.spy(splashPresenter)
spyPresenter.onAttach()
verify(spyPresenter).onAppOpenFirstTime()
}
@Test
fun onAppOpenFirstTimeTest() {
splashPresenter.onAppOpenFirstTime()
verify(dataManager).setAppLaunchFirstTime(false)
verify(splashView).openLoginScreen()
}
那么哪种方法更好呢?在将来扩展功能时,哪种方法将使项目更具可测试性?
我们需要为私有内部方法编写单元测试吗?
(您已经询问了哪种方法将使项目更具可测试性,但可测试性是被测系统的属性。 但是,此示例中测试套件的区别属性是测试代码维护的工作。
在您的特定示例中,第二种方法使您的测试不必要地依赖于可能会更改的实现细节:您有可能重命名onAppOpenFirstTime
,将其拆分为更多帮助程序函数,甚至将其删除并将其内联到onAttach
中。 在所有这些更改方案中,第二种方法将导致额外的维护工作,以保持测试套件正常工作。
但是,我强调,这里没有必要依赖onAppOpenFirstTime
。 这是因为采用第二种方法没有任何好处:第一种方法似乎能够找到与第二种方法相同的错误,第一种方法中的测试与第二种方法一样容易设置(甚至更容易),依此类推。
如果您只想知道如何解决所描述的特定问题,可以在此处停止阅读。 但是,在其他情况下,情况可能会有所不同。 我在下面添加评论,因为我不是经常说的口头禅"不要测试私人方法"的倡导者。
尝试使单元测试套件完全独立于实现细节可能会导致测试套件效率低下 - 也就是说,测试套件不适合查找可能找到的所有 bug。 而且,发现错误是测试的一个主要目标(参见Myers,Badgett,Sandler:软件测试的艺术,或Beizer:软件测试技术等)。
最终,错误存在于实现中。 不同的实现将有不同的错误。 因此,尝试使用与实现无关的测试套件可能无法找到错误。
考虑实现斐波那契函数的不同方法:作为迭代/递归函数,封闭形式表达式(Moivre/Binet),查找表:每个实现都会带来不同的潜在错误。 单元测试是测试金字塔底部的测试方法,所有更高级别的测试(集成或系统测试)都不太适合在实现细节中查找错误。
因此,最好的方法是尽可能多地使用与实现无关的有用单元测试。 此外,您可能需要额外的单元测试,旨在查找所选实现中的潜在错误。 最后,您的测试套件可能是两者的混合体:独立于实现的测试和特定于实现的测试。
依赖于实现的测试将更加需要维护,因此对于每个测试,您应该有一个原因,例如检测特定于实现的潜在错误。 特定于实现的测试本质上并不坏,但不要不必要地使用它们。 而且,实现方面的稳定性越低,您就越应该避免使测试依赖于它。
查看 Meszaros 测试自动化原则:确保相应的工作量
普遍的看法是,你不应该测试私有方法。由于您的私有方法会被某些公共方法调用,因此您应该只测试公共方法。
您测试类的功能,而不是单独测试方法。您永远不可能单独测试类中的所有方法,但您可以随时尝试测试类的功能(通过使用公共方法),这最终将导致您调用该类的所有方法,无论是私有的还是公共的,从而实现高代码覆盖率。
第一种方法似乎更好,因为它测试了您想要实现的目标。