Тестирование приватных методов в Котлине

Как протестировать приватные методы в Котлине? Я попытался добавить @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) из androidx.annotation.VisibleForTesting, но это не делает мою функцию частной

Вот как я это использую

@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>, потому что при издевательстве над Filefile.bufferedReader дает NullPointerExceptionНевозможно издеваться над классом BufferedWriter в junit

Аннотация не может сделать функцию частной. Это может помочь Android Studio предупредить вас об использовании этой функции не только из тестового кода, но и из других источников.

CommonsWare 27.08.2018 00:53

Вы не можете напрямую тестировать частные методы, и вы не можете сделать метод закрытым никаким другим способом, кроме ключевого слова private. Либо сделайте их internal, либо протестируйте только общедоступный API.

Louis Wasserman 27.08.2018 01:19

Тестирование частной функции - действительно плохая практика. Всегда проверяйте общедоступный API; не привязывайте свой тест к деталям реализации.

Mik378 27.08.2018 10:05

Для справки: разбиение на «,» - это примерно 5% того, что парсер CSV делает за вас. Серьезно: не делайте таких вещей. Используйте существующую надежную библиотеку парсера CSV. Вы изобретаете колесо и будете повторять все ошибки, с которыми люди сталкиваются при этом. Поверьте, я был там. Правильный синтаксический анализ CSV произвольный - жесткий.

GhostCat 28.08.2018 11:20

Я знаю @GhostCat. Это задание на собеседование :)

qbait 28.08.2018 16:12

Хорошо, тогда это имеет смысл ;-) ... и мой ответ наверняка не мешает другим людям приходить и отвечать по технической стороне дела!

GhostCat 28.08.2018 16:33

вы пробовали mockk lib? он может тестировать частные методы

deviant 05.02.2019 12:14
15
7
12 663
3

Ответы 3

На это есть только ответ один: даже не пытайтесь.

Вы пишете исходный код для связи намерение. Если вы делаете что-то private, то это внутренняя деталь реализации. Он может измениться в следующую секунду.

что, если вы все еще хотите проверить правильность работы этой частной функции?

Ilya Kovalyov 08.03.2020 22:27

он может быть изменен, но я все равно хочу, чтобы он вел себя должным образом

Filip Pranklin 23.03.2020 12:27

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

GhostCat 23.03.2020 12:31

Не отвечает на вопрос, OP не спрашивал, стоит ли тестировать частные методы, а скорее, как это сделать.

Attila 31.08.2020 00:24

@Attila Всегда есть другие люди, которые дают "технический" ответ на такие вопросы. Но помните: контент здесь написан не только для ОП, задающего вопрос, но и для всех будущих читателей. И дело в том: это идея плохой для тестирования частных методов. Но, к сожалению, многие люди делают эту часть не понимать. Вы можете написать свой ответ и объяснить людям, как правильно выстрелить себе в ноги. Я предпочитаю им сказать: не стреляйте себе в ногу, потому что это плохо. Что в конечном итоге полезнее, читатели могут решить сами.

GhostCat 31.08.2020 08:56

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

GhostCat 31.08.2020 08:59

@GhostCat, я ценю рациональность вашего ответа, он действительно имеет смысл и приносит дополнительную пользу. Однако не следует ли добавлять ваш «нетехнический / прямой» ответ в качестве комментария только к вопросу OP, а не в качестве правильного ответа на вопрос?

Attila 31.08.2020 10:39

@Attila По определению, комментарии на этом сайте непостоянны. Нет никакой гарантии, что кто-то их не отметит (больше не нужен или по какой-то другой причине) ... а затем какой-то модератор удалит комментарий. Поэтому, когда я хочу сказать что-то, что считаю «достойным, чтобы продлиться долго», я использую ответ вместо комментариев.

GhostCat 31.08.2020 10:43

нравится:

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
    }

вам нужно передать экземпляр объекта, для которого вы хотите вызвать метод, имя метода и необходимые аргументы

Вы можете использовать java-отражения:

Чтобы протестировать метод:

  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) )
  }

Другие вопросы по теме