在 Kotlin 中测试私有方法
Posted
技术标签:
【中文标题】在 Kotlin 中测试私有方法【英文标题】:Testing private methods in Kotlin 【发布时间】:2019-02-01 11:28:24 【问题描述】:如何?我尝试从androidx.annotation.VisibleForTesting
添加@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
,但它不会使我的函数私有
这就是我的使用方式
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun doSomething()
[编辑]
我知道我不应该测试 private
方法,但它现在总是微不足道的。下面的情况呢。
我有一个 CsvReader 类
class CsvReader(private val inputStream: InputStream, private val separator: String = "\t")
fun read(): List<String>
return read(inputStream.bufferedReader())
private fun read(bufferedReader: BufferedReader): List<String>
val line = bufferedReader.use it.readLine() // `use` is like try-with-resources in Java
return parse(line)
private fun parse(line: String): List<String>
return line.split(separator)
我为它写了测试
class CsvReaderTest
private val stream = mock<InputStream>()
private val reader = CsvReader(stream)
private val bufferedReader = mock<BufferedReader>()
@Test
fun read()
whenever(bufferedReader.readLine()).thenReturn("Jakub\tSzwiec")
reader.read(bufferedReader) shouldEqual listOf("Jakub", "Szwiec")
@Test
fun readWhenEmpty()
whenever(bufferedReader.readLine()).thenReturn("")
reader.read(bufferedReader) shouldEqual listOf("")
@Test
fun throwIOExceptionWhenReadingProblems()
whenever(bufferedReader.readLine()).thenThrow(IOException::class.java)
val read = reader.read(bufferedReader)
read shouldThrow IOException::class
不幸的是,对于测试,我需要调用私有函数 fun read(bufferedReader: BufferedReader): List<String>
,因为在模拟 File
时,file.bufferedReader
会给出 NullPointerException
Unable to mock BufferedWriter class in junit
【问题讨论】:
注解不能使函数成为私有的。它可以帮助 Android Studio 警告您在测试代码之外使用该功能,但仅此而已。 您不能直接测试私有方法,也不能通过关键字private
以外的任何其他方式将方法设为私有。要么将它们设为internal
,要么只测试公共 API。
测试私有函数是一种非常糟糕的做法。始终测试公共 api;不要将您的测试与实现细节联系起来。
为了记录:用“,”分割就像 CSV 解析器为您所做的 5%。严肃地说:不要做这样的事情。使用现有的强大 CSV 解析器库。你发明了***,你会重复人们在这样做时遇到的所有错误。相信我,我去过那里。正确解析 arbitrary CSV 是hard。
我知道@GhostCat。那是面试任务:)
【参考方案1】:
对此只有一个答案:不要尝试。
您编写源代码来传达意图。如果你做了一些private
的东西,那么这就是一个内部实现细节。它可能会在下一秒发生变化。
【讨论】:
如果你还想测试这个私有函数是否正常工作怎么办? 那么您应该测试导致该私人详细信息的使用路径。 没有回答这个问题,OP 不是在问测试私有方法是否是一个好主意,而是问如何去做。 @Attila 总是有其他人对此类问题给出“技术”答案。但请记住:这里的内容不仅是为提出问题的 OP 编写的,而且是为所有未来的读者编写的。问题是:测试私有方法是一个坏的想法。但不幸的是:很多人不理解那部分。你可以写下你自己的答案,并向人们解释如何正确地让自己站起来。我更愿意告诉他们:避免朝自己的脚开枪,因为那是一件坏事。到底有什么帮助,读者自己决定。 @GhostCat,我很欣赏你的回答背后的理性,它确实有意义并带来了附加值。但是,不应该将您的“非技术/直接”答案添加为仅对 OP 问题的评论,而不是对该问题的正确答案?【参考方案2】:像这样:
fun callPrivate(objectInstance: Any, methodName: String, vararg args: Any?): Any?
val privateMethod: KFunction<*>? =
objectInstance::class.functions.find t -> return@find t.name == methodName
val argList = args.toMutableList()
(argList as ArrayList).add(0, objectInstance)
val argArr = argList.toArray()
privateMethod?.apply
isAccessible = true
return call(*argArr)
?: throw NoSuchMethodException("Method $methodName does not exist in $objectInstance::class.qualifiedName")
return null
您需要传递要调用该方法的对象的实例、方法名称和所需参数
【讨论】:
【参考方案3】:你可以使用java反射:
测试方法:
class ClassUnderTest
private fun printGreetings(name: String): String
return "Hello, $name"
够了:
private val classUnderTest = spyk(ClassUnderTest())
@Test
fun `should return greetings`()
val method = classUnderTest.javaClass.getDeclaredMethod("printGreetings", String::class.java)
method.isAccessible = true
val parameters = arrayOfNulls<Any>(1)
parameters[0] = "Piotr"
assertEquals("Hello, Piotr", method.invoke(classUnderTest, *parameters) )
【讨论】:
这里的spyk
是什么?
仅供参考:在没有spyk
的情况下工作
spyk 来自 MockK 测试库,请参阅mockk.io/#spy以上是关于在 Kotlin 中测试私有方法的主要内容,如果未能解决你的问题,请参考以下文章
KotlinKotlin 与 Java 互操作 ① ( 变量可空性 | Kotlin 类型映射 | Kotlin 访问私有属性 | Java 调用 Kotlin 函数 )