Часто я участвую в разработке / реализации API, с которыми я сталкиваюсь перед этой дилеммой.
Я очень решительный сторонник сокрытия информации и пытаюсь использовать различные методы для этого.включая, но не ограничиваясь, внутренние классы, частные методы, квалификаторы закрытых пакетов и т. д.
Проблема этих методов заключается в том, что они имеют тенденцию препятствовать хорошей тестируемости.И хотя некоторые из этих методов можно разрешить (например, приватность пакета, поместив класс в один и тот же пакет), другие не так просты для решения и требуют либо магии отражения, либо других уловок.
Давайте посмотрим на конкретный пример:
public class Foo {
SomeType attr1;
SomeType attr2;
SomeType attr3;
public void someMethod() {
// calculate x, y and z
SomethingThatExpectsMyInterface something = ...;
something.submit(new InnerFoo(x, y, z));
}
private class InnerFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
InnerFoo(SomeType arg1, SomeType arg2, SomeType arg3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
}
@Override
private void methodOfMyInterface() {
//has access to attr1, attr2, attr3, arg1, arg2, arg3
}
}
}
Существуют веские причины не раскрывать InnerFoo
- ни один другой класс, библиотека не должна иметь к нему доступ, так как он не определяет публичный договор и автор намеренноне хотел, чтобы это было доступно.Однако, чтобы сделать его 100% TDD-кошерным и доступным без каких-либо трюков с отражением, InnerFoo
следует изменить следующим образом:
private class OuterFoo implements MyInterface {
private final SomeType arg1;
private final SomeType arg2;
private final SomeType arg3;
private final SomeType attr1;
private final SomeType attr2;
private final SomeType attr3;
OuterFoo(SomeType arg1, SomeType arg2, SomeType arg3, SomeType attr1, SomeType attr2, SomeType attr3) {
this.arg1 = arg1;
this.arg2 = arg2;
this.arg3 = arg3;
this.attr1 = attr1;
this.attr2 = attr2;
this.attr3 = attr3;
}
@Override
private void methodOfMyInterface() {
//can be unit tested without reflection magic
}
}
В этом примере используется только 3 атрибута, но вполне разумно иметь 5-6, и конструктор OuterFoo
должен будет принять 8-10 параметров!Добавьте методы getter сверху, и у вас уже есть 100 строк совершенно бесполезного кода (для получения этих атрибутов также потребуется получить эти атрибуты для тестирования).Да, я мог бы сделать ситуацию немного лучше, предоставив шаблон компоновщика, но я думаю, что это не только чрезмерное проектирование, но и наносит ущерб цели самого TDD!
Другим решением этой проблемы было бы разоблачениеЗащищенный метод для класса Foo
, расширьте его до FooTest
и получите необходимые данные.Опять же, я думаю, что это тоже плохой подход, потому что protected
метод действительно определяет контракт и, выставляя его, я теперь неявно подписал его.
Не поймите меня неправильно. Мне нравится писать тестируемый код . Мне нравятся лаконичные, чистые API, блоки короткого кода, удобочитаемость и т. Д. Но мне не нравится, когда идет на жертвы, когда речь идет о сокрытии информации только потому, что легчемодульный тест .
Кто-нибудь может высказать какие-либо мысли по этому поводу (в целом, и в частности)?Есть ли другие, лучшие решения для данного примера?