在 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&lt;String&gt;,因为在模拟 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 中测试私有方法的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 中的私有构造函数

在 Kotlin 中访问私有变量 [重复]

Kotlin 中这些东西的用途是啥?

KotlinKotlin 与 Java 互操作 ① ( 变量可空性 | Kotlin 类型映射 | Kotlin 访问私有属性 | Java 调用 Kotlin 函数 )

是否可以测试这个 Kotlin 类?

Kotlin coroutine单元测试的更好方法