У меня есть скелетная реализация, как в пункте 18 из Effective Java (расширенное обсуждение здесь ). Это абстрактный класс, который предоставляет 2 открытых метода methodA () и methodB (), которые вызывают методы подклассов для «заполнения пробелов», которые я не могу определить абстрактно.
Сначала я разработал его, создав конкретный класс и написав для него модульные тесты. Когда пришел второй класс, я смог извлечь общее поведение, реализовать «пропущенные пробелы» во втором классе, и он был готов (конечно, для второго подкласса были созданы модульные тесты).
Время шло, и теперь у меня есть 4 подкласса, каждый из которых реализует 3 коротких защищенных метода, которые очень специфичны для их конкретной реализации, в то время как скелетная реализация выполняет всю общую работу.
Моя проблема в том, что когда я создаю новую реализацию, я пишу тесты снова и снова:
- Вызывает ли подкласс данный обязательный метод?
- Имеет ли данный метод данную аннотацию?
- Получу ли я ожидаемые результаты от метода А?
- Получу ли я ожидаемые результаты от метода B?
Пока вижу преимущества с таким подходом:
- Это документирует требования подкласса через тесты
- Может быстро потерпеть неудачу в случае проблемного рефакторинга
- Протестируйте подкласс в целом и через его открытый API («работает ли methodA ()?», А не «работает ли этот защищенный метод?»)
Проблема, с которой я столкнулся, заключается в том, что тесты для нового подкласса, в основном, просты: - тесты одинаковы во всех подклассах, они просто меняют тип возвращаемого значения методов (реализация скелета является общей) и свойства, которые я проверьте на утверждении часть теста. - Обычно подклассы не имеют специфичных для них тестов.
Мне нравится, как тесты фокусируются на результатах самого подкласса и защищают его от рефакторинга, но тот факт, что реализация теста стала просто «ручной работой», заставляет меня думать, что я делаю что-то не так.
Распространена ли эта проблема при тестировании иерархии классов? Как я могу избежать этого?
ps: я думал о тестировании скелетного класса, но также кажется странным создание макета абстрактного класса, чтобы иметь возможность его протестировать. И я думаю, что любой рефакторинг абстрактного класса, который изменяет поведение, которое не ожидается подклассом, не будет замечен так быстро. Мои смелости говорят мне, что тестирование подкласса "в целом" является предпочтительным подходом здесь, но, пожалуйста, не стесняйтесь сказать мне, что я ошибаюсь :)
PS2: я погуглил и нашел много вопросов по этому вопросу. Одним из них является тот, который имеет отличный ответ от Найджела Торна, мой случай был бы его "номером 1". Это было бы здорово, но я не могу рефакторинг на этом этапе, поэтому мне придется смириться с этой проблемой. Если бы я мог провести рефакторинг, у меня был бы каждый подкласс в качестве стратегии. Это было бы хорошо, но я все равно протестировал бы «основной класс» и протестировал бы стратегии, но я не заметил бы никакого рефакторинга, который нарушил бы интеграцию между основным классом и стратегиями.
PS3: я нашел несколько ответов о том, что «приемлемо» тестировать абстрактный класс. Я согласен, что это приемлемо, но я хотел бы знать, какой подход является предпочтительным в этом случае (я все еще начинаю с юнит-тестов)