поблочное тестирование метод, который возвращает не тривиальный объект

Альтернативное решение можно найти в комментарии к этого запроса на улучшение . Он использует метод getClassContext() пользовательского SecurityManager и, кажется, быстрее, чем метод трассировки стека.

Следующая программа проверяет скорость работы различных предлагаемых методов (наиболее интересный бит находится во внутреннем классе SecurityManagerMethod):

/**
 * Test the speed of various methods for getting the caller class name
 */
public class TestGetCallerClassName {

  /**
   * Abstract class for testing different methods of getting the caller class name
   */
  private static abstract class GetCallerClassNameMethod {
      public abstract String getCallerClassName(int callStackDepth);
      public abstract String getMethodName();
  }

  /**
   * Uses the internal Reflection class
   */
  private static class ReflectionMethod extends GetCallerClassNameMethod {
      public String getCallerClassName(int callStackDepth) {
          return sun.reflect.Reflection.getCallerClass(callStackDepth).getName();
      }

      public String getMethodName() {
          return "Reflection";
      }
  }

  /**
   * Get a stack trace from the current thread
   */
  private static class ThreadStackTraceMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return Thread.currentThread().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Current Thread StackTrace";
      }
  }

  /**
   * Get a stack trace from a new Throwable
   */
  private static class ThrowableStackTraceMethod extends GetCallerClassNameMethod {

      public String getCallerClassName(int callStackDepth) {
          return new Throwable().getStackTrace()[callStackDepth].getClassName();
      }

      public String getMethodName() {
          return "Throwable StackTrace";
      }
  }

  /**
   * Use the SecurityManager.getClassContext()
   */
  private static class SecurityManagerMethod extends GetCallerClassNameMethod {
      public String  getCallerClassName(int callStackDepth) {
          return mySecurityManager.getCallerClassName(callStackDepth);
      }

      public String getMethodName() {
          return "SecurityManager";
      }

      /** 
       * A custom security manager that exposes the getClassContext() information
       */
      static class MySecurityManager extends SecurityManager {
          public String getCallerClassName(int callStackDepth) {
              return getClassContext()[callStackDepth].getName();
          }
      }

      private final static MySecurityManager mySecurityManager =
          new MySecurityManager();
  }

  /**
   * Test all four methods
   */
  public static void main(String[] args) {
      testMethod(new ReflectionMethod());
      testMethod(new ThreadStackTraceMethod());
      testMethod(new ThrowableStackTraceMethod());
      testMethod(new SecurityManagerMethod());
  }

  private static void testMethod(GetCallerClassNameMethod method) {
      long startTime = System.nanoTime();
      String className = null;
      for (int i = 0; i < 1000000; i++) {
          className = method.getCallerClassName(2);
      }
      printElapsedTime(method.getMethodName(), startTime);
  }

  private static void printElapsedTime(String title, long startTime) {
      System.out.println(title + ": " + ((double)(System.nanoTime() - startTime))/1000000 + " ms.");
  }
}

Пример вывода с моего Intel Core 2 с частотой 2,4 ГГц Duo MacBook под управлением Java 1.6.0_17:

Reflection: 10.195 ms.
Current Thread StackTrace: 5886.964 ms.
Throwable StackTrace: 4700.073 ms.
SecurityManager: 1046.804 ms.

Метод внутреннего отражения намного быстрее, чем другие. Получение трассировки стека из вновь созданного Throwable быстрее, чем получение его из текущего Thread. И среди не внутренних способов определения класса вызывающего абонента обычай SecurityManager кажется самым быстрым.

Обновление

Как lyomi указывает в на этот комментарий , метод sun.reflect.Reflection.getCallerClass() был отключен по умолчанию в Java 7, обновление 40 и полностью удалено в Java 8 Подробнее об этом читайте в об этой проблеме в базе данных ошибок Java .

Обновление 2

Как обнаружил zammbi , Oracle был вынужден отказаться от изменения , которое удалило sun.reflect.Reflection.getCallerClass(). Это все еще доступно в Java 8 (но это устарело).

Обновление 3

3 года спустя: обновление по времени с текущей JVM.

> java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
> java TestGetCallerClassName
Reflection: 0.194s.
Current Thread StackTrace: 3.887s.
Throwable StackTrace: 3.173s.
SecurityManager: 0.565s.

5
задан 24 June 2009 в 14:37
поделиться

7 ответов

Ну а что важно в результате? Предположительно, он может что-то , иначе результат будет бесполезным. Что в этом случае отличало бы рабочий код от неработающего кода?

(Я не пытаюсь пролить свет на проблему - иногда действительно очень трудно поддерживать жесткую инкапсуляцию, но эффективно тестировать. Иногда стоит открыть объект немного, чтобы облегчить тестирование ...)

11
ответ дан 18 December 2019 в 07:10
поделиться

Что делает этот объект? Если вы не можете запрашивать поля и т. Д., Я предполагаю, что он действует на какой-то объект, который вы ему передаете? Если это так, можете ли вы передать фиктивный объект, реализующий тот же интерфейс, но проверяющий, что объект результата вызывает его с соответствующими аргументами, в ожидаемой последовательности и т. Д.?

eg

public class ResultObject {
   public void actOn(ActedUpon obj) {
      // does stuff

where ActedUpon - это интерфейс, который будет иметь «нормальную» реализацию и реализацию проверки (идеальным было бы использование фреймворка типа JMock )

5
ответ дан 18 December 2019 в 07:10
поделиться

Во-первых, по соглашению, геттер не должен иметь побочных эффектов. Так что почти наверняка иметь

this.resultState = resultState;

в вашем получателе.

Тем не менее, возвращение resultState должно иметь некоторую цель, иначе зачем вы его возвращаете? Так что проверьте, делает ли resultState то, что должен.

2
ответ дан 18 December 2019 в 07:10
поделиться

Если вы хотите протестировать просто то, что после вызова o.setResultState (resultState) вы можете получить resultState с помощью o.getResultState (), проверка будет выглядеть очень просто:

ResultState resultState = new ResultState(...);
o.setResultState(resultState);
assertEquals(resultState, o.getResultState());
0
ответ дан 18 December 2019 в 07:10
поделиться

К сожалению, вы используете getResultState () для создания самого объекта ResultState , который на самом деле является вашим блокировщиком. Рассмотрите возможность рефакторинга в следующий интерфейс:

protected ResultState initResultState ( )
{
    this.resultState = new ResultState();
    // Initialization Logic...
    return this.resultState;
} // END initResultState

public ResultState getResultState ( )
{
    if ( this.resultState === null )
    {
        return this.initResultState();
    }

    return this.resultState;
} // END getResultState()

С этой позиции легче протестировать ваш метод получения. Определите класс-потомок, который возвращает известную заглушку ResultState для initResultState () , которую можно запросить, то есть:

/**
 * The test fixutre's initResultState() method merely returns a simple Stub of
 * the ResultState object, which can be more easily interrogated.
 */
public ResultState initResultState ( )
{
    return new Stub_ResultState();
} // END initResultState

Это по-прежнему оставляет вам защищенный initResultState () , конечно. Рассмотрим следующий рефакторинг:

protected ResultState initResultState ( ResultState newResultState )
{
    this.resultState = newResultState;
    // Initialization Logic...
    return this.resultState;
} // END initResultState

public ResultState getResultState ( )
{
    if ( this.resultState === null )
    {
        return this.initResultState( new ResultState() );
    }

    return this.resultState;
} // END getResultState

Это позволяет вам переопределить метод getResultState () в классе-потомке, чтобы вы могли передать другой объект-заглушку ResultState для последующего манипулирования и опроса, например:

/**
 * The test fixture's getResultState() method always acts as a passthrough for
 * initResultState(), which is protected, so that the output of the protected
 * method can be interrogated.
 */
public ResultState getResultState ( )
{
    return this.initResultState( new Stub_ResultState() );
} // END getResultState

С этой позиции, вам понадобятся два тестовых инструмента: один, который переопределяет поведение initResultState () для проверки функциональности геттеров; другой, который отменяет поведение getResultState () , чтобы проверить поведение initResultState () . Оба используют экземпляр класса Stub_ResultState , который обязательно является потомком класса ResultState , но обеспечивает открытый доступ к внутренним значениям своего родительского объекта для запроса в модульных тестах.

] Надеюсь, это имеет смысл, но не стесняйтесь просить разъяснений.

другой, который отменяет поведение getResultState () , чтобы проверить поведение initResultState () . Оба используют экземпляр класса Stub_ResultState , который обязательно является потомком класса ResultState , но обеспечивает открытый доступ к внутренним значениям своего родительского объекта для запроса в модульных тестах.

] Надеюсь, это имеет смысл, но не стесняйтесь просить разъяснений.

другой, который отменяет поведение getResultState () , чтобы проверить поведение initResultState () . Оба используют экземпляр класса Stub_ResultState , который обязательно является потомком класса ResultState , но обеспечивает открытый доступ к внутренним значениям своего родительского объекта для запроса в модульных тестах.

] Надеюсь, это имеет смысл, но не стесняйтесь просить разъяснений.

2
ответ дан 18 December 2019 в 07:10
поделиться

На основе этого кода. Я бы сказал, что проверки Not Null достаточно. Проверка функциональности возвращенного ResultState относится к тестам для ResultState. Вы пытаетесь проверить функциональность getResultState (). Единственное, что он делает, - это создает новый объект и сохраняет ссылку на него в this.resultState. Поэтому проверьте, создал ли он его, и посмотрите, сохранил ли он ссылку.

2
ответ дан 18 December 2019 в 07:10
поделиться

Выполнив новое в getter, вы соглашаетесь с конкретной реализацией ResultState. В вашем текущем случае это означает, что реализация непрозрачна в отношении своего состояния.

Представьте на мгновение, что вместо создания нового ResultState на месте вы получили его с фабрики, и что фабрика предоставила какой-то способ заменить фиктивный ResultState, чтобы вы могли проверить, что getResultState () выполняла действия с объектом ResultState между его созданием и возвратом.

Простой способ добиться этого эффекта - разбить создание ResultState на замещаемый метод. Например,

public ResultState getResultState() {
    ResultState resultState = createResultState();
    ...

protected ResultState createResultState() {
    return new ResultState();
}

Затем, предполагая, что ваш класс Test находится в том же пакете, вы можете создать встроенный подкласс, который переопределяет getResultState (), чтобы вернуть имитацию.

Хотя это работает, это хрупкий способ написания тестов .

Альтернативный подход - направиться к чему-то вроде

public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

Для этого необходимо, чтобы вы выяснили соответствующий способ внедрения ResultStateFactory в объект, для чего отлично подходит среда внедрения зависимостей. Для тестов внедрите фабрику, которая возвращает фиктивный ResultState с соответствующими ожиданиями. Это уменьшает проблему тестирования до «правильно ли getResultState () манипулирует объектом ResultState перед его возвратом?»

предполагая, что ваш класс Test находится в том же пакете, вы можете создать встроенный подкласс, который переопределяет getResultState () для возврата имитации.

Хотя это работает, это хрупкий способ написания тестов.

Альтернативный подход - чтобы направиться к чему-то вроде

public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

. Это требует, чтобы вы выяснили подходящий способ внедрения ResultStateFactory в объект, для чего отлично подходит среда внедрения зависимостей. Для тестов внедрите фабрику, которая возвращает фиктивный ResultState с соответствующими ожиданиями. Это уменьшает проблему тестирования до «правильно ли getResultState () манипулирует объектом ResultState перед его возвратом?»

предполагая, что ваш класс Test находится в том же пакете, вы можете создать встроенный подкласс, который переопределяет getResultState () для возврата имитации.

Хотя это работает, это хрупкий способ написания тестов.

Альтернативный подход - чтобы направиться к чему-то вроде

public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

. Это требует, чтобы вы выяснили подходящий способ внедрения ResultStateFactory в объект, для чего отлично подходит среда внедрения зависимостей. Для тестов внедрите фабрику, которая возвращает фиктивный ResultState с соответствующими ожиданиями. Это уменьшает проблему тестирования до «правильно ли getResultState () манипулирует объектом ResultState перед его возвратом?»

Альтернативный подход - направиться к чему-то вроде

public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

. Это требует, чтобы вы выяснили соответствующий способ внедрения ResultStateFactory в объект, для чего отлично подходит структура внедрения зависимостей. Для тестов внедрите фабрику, которая возвращает фиктивный ResultState с соответствующими ожиданиями. Это уменьшает проблему тестирования до «правильно ли getResultState () манипулирует объектом ResultState перед его возвратом?»

Альтернативный подход - направиться к чему-то вроде

public ResultState getResultState() {
    ResultState resultState = _resultStateFactory.newResultState();
    ...

. Это требует, чтобы вы выяснили соответствующий способ внедрения ResultStateFactory в объект, для чего отлично подходит структура внедрения зависимостей. Для тестов внедрите фабрику, которая возвращает фиктивный ResultState с соответствующими ожиданиями. Это уменьшает проблему тестирования до «правильно ли getResultState () манипулирует объектом ResultState перед его возвратом?»

0
ответ дан 18 December 2019 в 07:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: