Есть ли способ повторно инициализировать статический класс в Java?

Я пытаюсь unit test класс, который ссылается на статические данные из другого класса. Я не могу "не" использовать этот статический класс, но, очевидно, запуск нескольких тестов стал проблематичным. Поэтому мой вопрос таков. Есть ли способ в junit-тест для повторной инициализации статического класса? Таким образом, один тест не выполняется предыдущим тестом?

Так, другими словами, какой-то способ сделать это:

Foo.setBar("Hello");

// Somehow reinitialize Foo

String bar = Foo.getBar(); // Gets default value of bar rather than "Hello"

К сожалению, я не могу изменить Foo, поэтому я застрял в использовании.

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

Ответ 1

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

final Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

field.set(null, value);

Ответ 2

Если вы используете PowerMock, вы можете издеваться над статическими методами - вот что вам следует делать.

Ответ 3

Вы можете использовать PowerMock (с Mockito) или JMockit, чтобы издеваться над статическим классом, чтобы он делал все, что вы хотите в каждом тесте.

Ответ 4

Три предложения,

  • Вызовите статический метод из @Before, установив его на некоторое известное значение.

  • Используйте ReflectionTestUtils для установки значения через отражение.

  • Обновите свой код, чтобы иметь класс оболочки экземпляра, который завершает вызов статического метода в методе/классе экземпляра. Обманите оболочку и введите в тестируемый класс.

Ответ 5

Я бы использовал шаблон Factory с статическими методами init и destroy, которые должны заботиться обо всех экземплярах.

Что-то вроде:

public class FooFactory {

  private static Foo mFoo  = null;

  public static Foo init(){

      if(mFoo == null){
          mFoo = new Foo();
      }
      return mFoo;
  }

  public static void destroy(){
      if(mFoo != null){
          mFoo = null;
      }
  } 
}

Так на единицу достаточно для запуска:

FooFactory.init();// on start

....

FooFactory.destroy();// on finish

Ответ 6

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

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

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

Ответ 7

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

Утилита:

public class Util {
    private static final String VALUE;

    static {
        String value = System.getProperty("value");

        if (value != null) {
            VALUE = value;
        } else {
            VALUE = "default";
        }
    }

    public static String getValue() {
        return VALUE;
    }
}

Тест jUnit:

import static org.junit.Assert.assertEquals;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

public class UtilTest {

    private class MyClassLoader extends ClassLoader {

        public Class<?> load() throws IOException {
            InputStream is = MyClassLoader.class.getResourceAsStream("/Util.class");

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b = -1;

            while ((b = is.read()) > -1) {
                baos.write(b);
            }

            return super.defineClass("Util", baos.toByteArray(), 0, baos.size());
        }
    }

    @Test
    public void testGetValue() {
        assertEquals("default", getValue());
        System.setProperty("value", "abc");
        assertEquals("abc", getValue());
    }

    private String getValue() {
        try {
            MyClassLoader myClassLoader = new MyClassLoader();
            Class<?> clazz = myClassLoader.load();
            Method method = clazz.getMethod("getValue");
            Object result = method.invoke(clazz);
            return (String) result;
        } catch (IOException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            throw new IllegalStateException("Error at 'getValue': " + e.getLocalizedMessage(), e);
        }
    }
}