При работе с этим унаследованным кодом был один класс с очень плохим поведением, который широко распространен, и вся команда снова и снова спотыкается.
Чтобы защитить виновных, я назову его г-ном Х, хотя его настоящее имя — SitePropertiesManager, поскольку оно управляет свойствами веб-сайта. Это плохо ведет себя, потому что это:
- нарушает принцип единоличной ответственности
- использует одноэлементный шаблон, обобщенный методом фабрики getInstance (),
- имеет метод init (), который должен быть вызван перед любым другим методом,
- загружает свои данные, используя прямой доступ к базе данных, а не используя DAO,
- использует сложную карту карт для хранения своих данных,
- обращается к файловой системе для кэширования данных, возвращаемых базой данных
- имеет таймер, чтобы решить, когда обновлять свой кэш.
- был написан до обобщения и имеет множество лишних методов findXXXX ().
- не реализует интерфейс,
- использует много программ копирования и вставки.
Это затрудняет создание заглушек при написании модульных тестов для нового кода и оставляет унаследованный код замусоренным:
|
1
|
SitePropertiesManager propman = SitePropertiesManager.getInstance(); |
В этом блоге рассматриваются способы борьбы с неуклюжими персонажами и демонстрируется, как создавать для них заглушки, одновременно сводя на нет эффект шаблона Singleton. Как и в моих предыдущих блогах «Методы тестирования», я основываю свой демонстрационный код на примере своего веб-приложения Address.
В других блогах этой серии я демонстрировал, как протестировать AddressService, и этот блог ничем не отличается. В этом сценарии, однако, AddressService должен загрузить свойство сайта и решить, следует ли возвращать адрес, но прежде чем мы посмотрим на это, мне прежде всего понадобился плохо написанный SitePropertiesManager для игры. Однако я не владею этим кодом, поэтому я написал версию для каскадеров, которая нарушает столько правил, сколько я могу себе представить. Я не буду утомлять вас подробностями, так как весь исходный код для SitePropertiesManager доступен по адресу: git: //github.com/roghughe/captaindebug.git
Как я уже говорил выше, в этом сценарии AddressService использует свойство сайта, чтобы определить, включено ли оно. Если это так, то он отправит обратно адресный объект. Я также собираюсь сделать вид, что AddressService — это некий устаревший код, который использует статический метод фабрики свойств сайта, как показано ниже:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
public Address findAddress(int id) { logger.info("In Address Service with id: " + id); Address address = Address.INVALID_ADDRESS; if (isAddressServiceEnabled()) { address = addressDao.findAddress(id); address = businessMethod(address); } logger.info("Leaving Address Service with id: " + id); return address; } private boolean isAddressServiceEnabled() { SitePropertiesManager propManager = SitePropertiesManager.getInstance(); return new Boolean(propManager.findProperty("address.enabled")); } |
Приручая этот тип класса, он первым делом должен прекратить использовать getInstance () для удержания и объекта, удалив его из вышеуказанного метода и начать использовать внедрение зависимостей. Должен быть по крайней мере один вызов getInstance () , но он может пойти куда-нибудь в коде запуска программы. В мире Spring решение состоит в том, чтобы обернуть класс с плохим поведением в реализации Spring FactoryBean, которая становится единственным местоположением getInstance () в вашем приложении — по крайней мере, для нового / расширенного кода.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class SitePropertiesManagerFactoryBean implements FactoryBean { private static SitePropertiesManager propsManager = SitePropertiesManager .getInstance(); @Override public SitePropertiesManager getObject() throws Exception { return propsManager; } @Override public Class getObjectType() { return SitePropertiesManager.class; } @Override public boolean isSingleton() { return true; }} |
Теперь это можно автоматически подключить к классу AddressService:
|
1
2
3
4
|
@Autowired void setPropertiesManager(SitePropertiesManager propManager) { this.propManager = propManager; } |
Эти изменения, однако, не означают, что мы можем написать некоторые правильные модульные тесты для AddressService , они просто готовят почву. Следующим шагом является извлечение интерфейса для SitePropertiesManager , что легко достигается с помощью eclipse.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public interface PropertiesManager { public abstract String findProperty(String propertyName); public abstract String findProperty(String propertyName, String locale); public abstract List findListProperty(String propertyName); public abstract List findListProperty(String propertyName, String locale); public abstract int findIntProperty(String propertyName); public abstract int findIntProperty(String propertyName, String locale);} |
При переходе к интерфейсам нам также необходимо вручную настроить экземпляр SitePropertiesManager в файле конфигурации Spring, чтобы Spring знал, какой класс подключать к какому интерфейсу:
|
1
|
<beans:bean id="propman" class="com.captaindebug.siteproperties.SitePropertiesManager" /> |
Нам также необходимо обновить аннотацию @Autowired в AddressService с помощью квалификатора:
|
1
2
3
4
5
|
@Autowired @Qualifier("propman") void setPropertiesManager(PropertiesManager propManager) { this.propManager = propManager; } |
С помощью интерфейса теперь мы можем легко написать простую заглушку SitePropertiesManager :
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class StubPropertiesManager implements PropertiesManager { private final Map propMap = new HashMap(); public void setProperty(String key, String value) { propMap.put(key, value); } @Override public String findProperty(String propertyName) { return propMap.get(propertyName); } @Override public String findProperty(String propertyName, String locale) { throw new UnsupportedOperationException(); } @Override public List findListProperty(String propertyName) { throw new UnsupportedOperationException(); } @Override public List findListProperty(String propertyName, String locale) { throw new UnsupportedOperationException(); } @Override public int findIntProperty(String propertyName) { throw new UnsupportedOperationException(); } @Override public int findIntProperty(String propertyName, String locale) { throw new UnsupportedOperationException(); }} |
Из-за наличия заглушки довольно легко написать модульный тест для AddressService, который использует заглушку и изолирован от базы данных и файловой системы.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
public class AddressServiceUnitTest { private StubAddressDao addressDao; private StubPropertiesManager stubProperties; private AddressService instance; @Before public void setUp() { instance = new AddressService(); stubProperties = new StubPropertiesManager(); instance.setPropertiesManager(stubProperties); } @Test public void testAddressSiteProperties_AddressServiceDisabled() { /* Set up the AddressDAO Stubb for this test */ Address address = new Address(1, "15 My Street", "My Town", "POSTCODE", "My Country"); addressDao = new StubAddressDao(address); instance.setAddressDao(addressDao); stubProperties.setProperty("address.enabled", "false"); Address expected = Address.INVALID_ADDRESS; Address result = instance.findAddress(1); assertEquals(expected, result); } @Test public void testAddressSiteProperties_AddressServiceEnabled() { /* Set up the AddressDAO Stubb for this test */ Address address = new Address(1, "15 My Street", "My Town", "POSTCODE", "My Country"); addressDao = new StubAddressDao(address); instance.setAddressDao(addressDao); stubProperties.setProperty("address.enabled", "true"); Address result = instance.findAddress(1); assertEquals(address, result); }} |
Все это довольно просто, но что произойдет, если вы не сможете извлечь интерфейс? Я сохраняю это для другого дня …
Ссылка: Создание заглушек для устаревшего кода — Методы тестирования 6 от нашего партнера по JCG в блоге Captain Debug
Статьи по Теме :
- Методы тестирования — не писать тесты
- Неправильное использование сквозных тестов — Методы тестирования 2
- Что следует тестировать? — Методы испытаний 3
- Регулярные юнит-тесты и заглушки — Методы испытаний 4
- Модульное тестирование с использованием макетов — Методы тестирования 5
- Подробнее о создании заглушек для устаревшего кода — Методы тестирования 7
- Почему вы должны писать модульные тесты — методы тестирования 8
- Некоторые определения — методы тестирования 9
- Использование FindBugs для создания значительно меньшего количества ошибочного кода
- Разработка и тестирование в облаке
