Тестирование функций расширения внутри классов

Если мы хотим протестировать функцию расширения для типа, мы можем создать экземпляр этого типа, вызвать функцию и проверить возвращаемое значение. Но как насчет тестирования функций расширения, определенных внутри классов?

abstract class AbstractClass<T> {
    fun doStuff(): T = "Hello".foo()

    abstract fun String.foo(): T
}

class SubClass1: AbstractClass<Int>() {
    override fun String.foo(): Int = 1
}

class SubClass2: AbstractClass<Boolean>() {
    override fun String.foo(): Boolean = true
}

Как мы проверяем логику методов foo() в классах SubClass1 и SubClass2? Это вообще возможно?

Я знаю, что могу изменить дизайн, чтобы проверить это. Мне пришли в голову две возможности:

  1. Не используйте функции расширения. ¯ \ _ (ツ) _ / ¯

    abstract class AbstractClass<T> {
        fun doStuff(): T = foo("Hello")
    
        abstract fun foo(string: String): T
    }
    
    class SubClass1: AbstractClass<Int>() {
        override fun foo(string: String): Int = 1
    }
    

    Затем мы можем создать объект SubClass1, вызвать foo() и проверить возвращаемое значение.

  2. Создайте дополнительные функции расширения с видимостью internal просто для проверки логики.

    class SubClass1: AbstractClass<Int>() {
        override fun String.foo(): Int = internalFoo()
    }
    
    internal fun String.internalFoo(): Int = 1
    

    Затем мы можем создать объект String, вызвать internalFoo() и проверить возвращаемое значение. Однако мне не нравится это решение, потому что мы могли бы изменить корпус override fun String.foo(): Int, и наш тест прошел бы.

Итак, можно ли тестировать функции расширения внутри классов? Если нет, как бы вы изменили свой дизайн, чтобы проверить их логику?

Можете ли вы подробнее рассказать о своем варианте использования? Тогда намного проще давать какие-либо рекомендации.

michalbrz 05.06.2018 17:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
7
1
2 173
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

@Test
fun `test extension function`() {
    var int = 0

    SubClass1().apply {
        int = "blah".foo()
    }

    assertThat(int, `is`(1))
}

О, мы действительно можем их протестировать! Этот код компилируется, потому что тип функции { int = "blah".foo() } - SubClass1.() -> Unit, и он работает так, как если бы код блока был вычислен внутри SubClass1, который является приемником функции. Подробнее о ресиверах в Котлине в stackoverflow.com/a/45875492/1861769.

JavierSA 06.06.2018 15:05

Что, если у функции переопределения есть параметры? Как бы вы включили параметры?

Jevon 29.07.2020 08:46

Методы расширения великолепны, и они обеспечивают красивый плавный синтаксис, как в LINQ, Android KTX.

Однако методы расширения плохо работают с подклассами. Это потому, что расширение - это, по сути, не объектно-ориентированный. Они напоминают статические методы в Java, которые можно скрыть, но не переопределить в подклассах.

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

В вашем первом примере нет абсолютно ничего плохого и нет веских причин настаивать на методах расширения:

abstract class AbstractClass<T> {
    fun doStuff(): T = foo("Hello")

    abstract fun foo(string: String): T
}

class SubClass1: AbstractClass<Int>() {
    override fun foo(string: String): Int = 1
}

Спасибо за ваш ответ и ссылки. Подводя итог, не стоит злоупотреблять функциями расширения.

JavierSA 06.06.2018 15:04

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