Тестирование функций расширения внутри классов
Если мы хотим протестировать функцию расширения для типа, мы можем создать экземпляр этого типа, вызвать функцию и проверить возвращаемое значение. Но как насчет тестирования функций расширения, определенных внутри классов?
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
? Это вообще возможно?
Я знаю, что могу изменить дизайн, чтобы проверить его. Две возможности произошли со мной:
Не используйте функции расширения. ¯\_(ツ)_/¯
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()
и проверьте возвращаемое значение.Создать дополнительные функции расширения с
internal
видимость просто для проверки логики.class SubClass1: AbstractClass<Int>() { override fun String.foo(): Int = internalFoo() } internal fun String.internalFoo(): Int = 1
Тогда мы можем создать объект
String
, вызовinternalFoo()
и проверьте возвращаемое значение. Тем не менее, мне не нравится это решение, потому что мы могли бы изменить телоoverride fun String.foo(): Int
и наш тест пройдет.
Итак, возможно ли проверить функции расширения внутри классов? Если нет, как бы вы изменили свой дизайн, чтобы проверить их логику?
3 ответа
Поскольку тесты должны быть написаны с точки зрения клиента, я не уверен, что это будет правильный тест. Но я нашел один способ проверить это.
@Test
fun `test extension function`() {
var int = 0
SubClass1().apply {
int = "blah".foo()
}
assertThat(int, `is`(1))
}
Методы расширения хороши, и они обеспечивают красивый свободный синтаксис, такой как в 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
}
Решение @David, использующее функцию области видимости в экземпляре класса, который определяет функцию расширения, определенно работает для тестирования этого сценария. Однако возвращает объект контекста, а не результат лямбда-выражения, что требует объявленияvar
во внешней области, которая затем назначается внутри лямбда-выражения. Если вы используете вместоapply
это можно еще больше упростить, потому чтоrun
возвращает результат лямбда-выражения:
val int = SubClass1().run {"blah".foo()}
assertThat(int, `is`(1))