Статьи

Ложные статические методы с PowerMock

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

Но что происходит, когда у вас нет внедрения зависимостей и вы используете стороннюю библиотеку, которая содержит классы определенного класса, содержащие статические методы? Один из способов — изолировать эти классы, написав обертку или адаптер вокруг них и используя это для обеспечения изоляции во время тестирования; Однако есть и другой способ: использование PowerMock. PowerMock — это фреймворк-фреймворк, расширяющий другие фреймворк-фреймворки для предоставления столь необходимой дополнительной функциональности Чтобы перефразировать старую рекламу: «она обновляет части, которые другие фальшивые рамки не могут достичь»

В этом блоге рассказывается о способности PowerMock имитировать статические методы, и приводится пример насмешки над классом JDK ResourceBundle , который, как многие из вас знают, использует ResourceBundle.getBundle (…) для, ну… загрузки пакетов ресурсов.

Я, как и многие другие блогеры и писатели, обычно представляю какой-то очень надуманный сценарий, чтобы осветить проблему. Сегодня все по-другому, у меня просто есть класс, который использует ResourceBundle с именем: UsesResourceBundle:

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
public class UsesResourceBundle {
 
  private static Logger logger = LoggerFactory.getLogger(UsesResourceBundle.class);
 
  private ResourceBundle bundle;
 
  public String getResourceString(String key) {
 
    if (isNull(bundle)) {
      // Lazy load of the resource bundle
      Locale locale = getLocale();
 
      if (isNotNull(locale)) {
        this.bundle = ResourceBundle.getBundle("SomeBundleName", locale);
      } else {
        handleError();
      }
    }
 
    return bundle.getString(key);
  }
 
  private boolean isNull(Object obj) {
    return obj == null;
  }
 
  private Locale getLocale() {
 
    return Locale.ENGLISH;
  }
 
  private boolean isNotNull(Object obj) {
    return obj != null;
  }
 
  private void handleError() {
    String msg = "Failed to retrieve the locale for this page";
    logger.error(msg);
    throw new RuntimeException(msg);
  }
}

Вы можете видеть, что есть один метод: getResourceString (…), который с учетом ключа извлекает строку ресурса из пакета. Чтобы сделать эту работу немного более эффективной, я лениво загрузил свой пакет ресурсов и после загрузки я вызываю bundle.getString (key), чтобы получить свой ресурс. Чтобы проверить это, я написал тест PowerMock JUnit:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.powermock.api.easymock.PowerMock.mockStatic;
import static org.powermock.api.easymock.PowerMock.replayAll;
import static org.powermock.api.easymock.PowerMock.verifyAll;
 
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
 
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.easymock.annotation.Mock;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
 
@RunWith(PowerMockRunner.class)
@PrepareForTest(UsesResourceBundle.class)
public class UsesResourceBundleTest {
 
  @Mock
  private ResourceBundle bundle;
 
  private UsesResourceBundle instance;
 
  @Before
  public void setUp() {
    instance = new UsesResourceBundle();
  }
 
  @Test
  public final void testGetResourceStringAndSucceed() {
 
    mockStatic(ResourceBundle.class);
    expect(ResourceBundle.getBundle("SomeBundleName", Locale.ENGLISH)).andReturn(bundle);
 
    final String key = "DUMMY";
    final String message = "This is a Message";
    expect(bundle.getString(key)).andReturn(message);
 
    replayAll();
    String result = instance.getResourceString(key);
    verifyAll();
    assertEquals(message, result);
  }
 
  @Test(expected = MissingResourceException.class)
  public final void testGetResourceStringWithStringMissing() {
 
    mockStatic(ResourceBundle.class);
    expect(ResourceBundle.getBundle("SomeBundleName", Locale.ENGLISH)).andReturn(bundle);
 
    final String key = "DUMMY";
    Exception e = new MissingResourceException(key, key, key);
    expect(bundle.getString(key)).andThrow(e);
 
    replayAll();
    instance.getResourceString(key);
  }
 
  @Test(expected = MissingResourceException.class)
  public final void testGetResourceStringWithBundleMissing() {
 
    mockStatic(ResourceBundle.class);
    final String key = "DUMMY";
    Exception e = new MissingResourceException(key, key, key);
    expect(ResourceBundle.getBundle("SomeBundleName", Locale.ENGLISH)).andThrow(e);
 
    replayAll();
    instance.getResourceString(key);
  }
 
}

В приведенном выше коде я сделал необычный шаг, включив операторы import. Это должно подчеркнуть, что мы используем PowerMock версии статики импорта, а не EasyMock. Если вы случайно импортируете статику EasyMock, то все это просто не будет работать.

Существует четыре простых шага в настройке теста, который проверяет статический вызов:

1. Используйте бегунок PowerMock JUnit:

1
@RunWith(PowerMockRunner.class)

2. Объявите тестовый класс, над которым мы издеваемся:

1
@PrepareForTest(UsesResourceBundle.class)

3. Сообщите PowerMock имя класса, который содержит статические методы:

1
mockStatic(ResourceBundle.class);

4. Настройте ожидания, сообщая PowerMock ожидать вызова статического метода:

1
expect(ResourceBundle.getBundle("SomeBundleName", Locale.ENGLISH)).andReturn(bundle);

Все остальное — простое управление, вы устанавливаете ожидания для других вызовов стандартных методов и сообщаете PowerMock / EasyMock запустить тест, проверяя результаты:

1
2
3
4
5
6
7
final String key = "DUMMY";
final String message = "This is a Message";
expect(bundle.getString(key)).andReturn(message);
 
replayAll();
String result = instance.getResourceString(key);
verifyAll();

PowerMock может делать намного больше, например, конструировать насмешки и вызывать закрытые методы. Подробнее об этом позже, возможно …

Справка: Использование PowerMock для моделирования статических методов от нашего партнера JCG Роджер в блоге капитана Дебуга .

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