Альтернативное решение можно найти в комментарии к этого запроса на улучшение . Он использует метод 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 .
Как обнаружил zammbi , Oracle был вынужден отказаться от изменения , которое удалило sun.reflect.Reflection.getCallerClass()
. Это все еще доступно в Java 8 (но это устарело).
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.
Ну а что важно в результате? Предположительно, он может что-то , иначе результат будет бесполезным. Что в этом случае отличало бы рабочий код от неработающего кода?
(Я не пытаюсь пролить свет на проблему - иногда действительно очень трудно поддерживать жесткую инкапсуляцию, но эффективно тестировать. Иногда стоит открыть объект немного, чтобы облегчить тестирование ...)
Что делает этот объект? Если вы не можете запрашивать поля и т. Д., Я предполагаю, что он действует на какой-то объект, который вы ему передаете? Если это так, можете ли вы передать фиктивный объект, реализующий тот же интерфейс, но проверяющий, что объект результата вызывает его с соответствующими аргументами, в ожидаемой последовательности и т. Д.?
eg
public class ResultObject {
public void actOn(ActedUpon obj) {
// does stuff
where ActedUpon
- это интерфейс, который будет иметь «нормальную» реализацию и реализацию проверки (идеальным было бы использование фреймворка типа JMock )
Во-первых, по соглашению, геттер не должен иметь побочных эффектов. Так что почти наверняка иметь
this.resultState = resultState;
в вашем получателе.
Тем не менее, возвращение resultState должно иметь некоторую цель, иначе зачем вы его возвращаете? Так что проверьте, делает ли resultState то, что должен.
Если вы хотите протестировать просто то, что после вызова o.setResultState (resultState) вы можете получить resultState с помощью o.getResultState (), проверка будет выглядеть очень просто:
ResultState resultState = new ResultState(...);
o.setResultState(resultState);
assertEquals(resultState, o.getResultState());
К сожалению, вы используете 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
, но обеспечивает открытый доступ к внутренним значениям своего родительского объекта для запроса в модульных тестах.
] Надеюсь, это имеет смысл, но не стесняйтесь просить разъяснений.
На основе этого кода. Я бы сказал, что проверки Not Null достаточно. Проверка функциональности возвращенного ResultState относится к тестам для ResultState. Вы пытаетесь проверить функциональность getResultState (). Единственное, что он делает, - это создает новый объект и сохраняет ссылку на него в this.resultState. Поэтому проверьте, создал ли он его, и посмотрите, сохранил ли он ссылку.
Выполнив новое в 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 перед его возвратом?»