Статьи

Создание заглушек для устаревшего кода — методы тестирования 6

Любой, кто читает этот блог, вероятно, поймет, что в настоящее время я работаю над проектом, который содержит целую кучу унаследованного кода, большого, обширного и написанного без каких-либо тестов.

При работе с этим унаследованным кодом был один класс с очень плохим поведением, который широко распространен, и вся команда снова и снова спотыкается.

Чтобы защитить виновных, я назову его г-ном Х, хотя его настоящее имя — 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

Статьи по Теме :