При работе с этим унаследованным кодом был один класс с очень плохим поведением, который широко распространен, и вся команда снова и снова спотыкается.
Чтобы защитить виновных, я назову его г-ном Х, хотя его настоящее имя — 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 для создания значительно меньшего количества ошибочного кода
- Разработка и тестирование в облаке