Статьи

Тестирование внутреннего состояния объекта с помощью PowerMock

Большинство модульных тестов фокусируется на тестировании поведения объекта, чтобы доказать, что он работает. Это достигается написанием теста JUnit, который вызывает открытые методы объекта, а затем проверяет, соответствуют ли возвращаемые значения этих вызовов некоторый ранее определенный набор ожидаемых значений. Это очень распространенная и успешная техника; однако не следует забывать, что объекты также демонстрируют состояние; то, что в силу того, что оно скрыто, часто упускается из виду.

Книга Грэди Буча 1994 года « Объектно-ориентированный анализ и проектирование» , которую я впервые прочитал летом 1995 года, определяет состояние объекта следующим образом:

Состояние объекта охватывает все (обычно статические) свойства объекта плюс текущие (обычно динамические) значения каждого из этих свойств.

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

Я подозреваю, что на этом этапе вы совершенно справедливо будете утверждать, что явные поведенческие тесты действительно проверяют состояние объекта в силу того факта, что данный вызов метода вернул правильный результат и что для получения правильного результата состояние объекта также должно быть правильно … и я согласен Однако есть очень немного случаев, когда классическое поведенческое тестирование неприменимо. Это происходит, когда открытый вызов метода не имеет выходных данных и ничего не делает с объектом, кроме изменения его состояния. Примером этого может быть метод, который возвращает void или конструктор. Например, дан метод со следующей подписью:

1
public void init();

… как вы убедитесь, что он сделал свою работу? Оказывается, есть несколько методов, которые вы можете использовать для достижения этой цели …

  • Добавьте много методов получения в ваш класс. Это не очень хорошая идея, так как вы просто ослабляете инкапсуляцию у задней двери.
  • Расслабьте инкапсуляцию: сделайте приватные переменные пакета частными. Очень спорная вещь. Вы могли бы прагматично утверждать, что хорошо протестированный, правильный и надежный код может быть лучше, чем высокая степень инкапсуляции, но я здесь не слишком уверен. Это может быть краткосрочным исправлением, но может привести к возникновению всех видов проблем в будущем, и должен быть способ написания хорошо протестированного, правильного и надежного кода, который не включает нарушение инкапсуляции объекта
  • Напишите некоторый код, который использует отражение для доступа к внутреннему состоянию объекта. Это лучшая идея на сегодняшний день. Недостатком является то, что это изрядное количество усилий и требует разумного количества навыков программирования.
  • Используйте тестовый класс PowerMock Whitebox, чтобы выполнить тяжелую работу за вас.

Следующий полностью надуманный сценарий демонстрирует использование класса Whitebox в PowerMock. Требуется очень простой класс AnchorTag <a>, который будет создавать тег привязки после проверки правильности введенной строки URL.

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
public class AnchorTag {
 
  private static final Logger logger = LoggerFactory.getLogger(AnchorTag.class);
 
  /** Use the regex to figure out if the argument is a URL */
  private final Pattern pattern = Pattern.compile("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$");
 
  /**
   * A public method that uses the private method
   */
  public String getTag(String url, String description) {
 
    validate(url, description);
    String anchor = createNewTag(url, description);
 
    logger.info("This is the new tag: " + anchor);
    return "The tag is okay";
  }
 
  /**
   * A private method that's used internally, but is complex enough to require testing in its own right
   */
  private void validate(String url, String description) {
 
    Matcher m = pattern.matcher(url);
 
    if (!m.matches()) {
      throw new IllegalArgumentException();
    }
  }
 
  private String createNewTag(String url, String description) {
    return "<a href=\"" + url + "\">" + description + "</a>";
  }
}

Проверка правильности URL выполняется с использованием регулярного выражения и объекта Java Pattern. Использование класса Whitebox гарантирует, что объект шаблона настроен правильно и наш AnchorTag находится в правильном состоянии. Это продемонстрировано тестом JUnit ниже:

01
02
03
04
05
06
07
08
09
10
11
/**
   * Works for private instance vars. Does not work for static vars.
   */
  @Test
  public void accessPrivateInstanceVarTest() throws Exception {
 
    Pattern result = Whitebox.<pattern> getInternalState(instance, "pattern");
 
    logger.info("Broke encapsulation to get hold of state: " + result.pattern());
    assertEquals("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$", result.pattern());
  }

Суть этого теста — строка:

1
Pattern result = Whitebox.<pattern> getInternalState(instance, "pattern");

… Который использует отражение, чтобы вернуть частную переменную экземпляра объекта Pattern. Как только у нас есть доступ к этому объекту, мы просто спрашиваем его, правильно ли он был инициализирован, вызывая:

1
assertEquals("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,6}$", result.pattern());

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

Ссылка: тестирование внутреннего состояния объекта с помощью PowerMock от нашего партнера JCG Роджер в блоге капитана Дебуга .

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