Как лучше всего протестировать статический инициализатор класса?


У меня есть класс, который имеет довольно сложную статическую инициализацию. Я читаю файлы из каталога, затем анализирую эти файлы json, сопоставляю их с объектами и заполняю список. Вы можете себе представить, что могут возникнуть некоторые исключения, и мне нужно охватить и проверить эти кодовые транши. Проблема в том, что эта статическая инициализация выполняется только один раз/TestCase file. Решения, с которыми я сталкиваюсь:

  • новый файл testcase для каждого поведения
  • выгрузить статический класс
  • новый JVM

Я не в восторге от этих вариантов, разве нет ничего лучше?

2 4

2 ответа:

Важным фактором модульного тестирования является структурирование кода таким образом, чтобы он был пригоден для тестирования.

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

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

Итак, вам нужно выполнить рефакторинг, например, если ваш код будет выглядеть примерно так (разбор JSON, замененный разбором CSV для ясности):

public MyClass
{
  private static List<MyObject> myObjects = new ArrayList<>();

  static
  {
    try
    {
      try (BufferedReader reader = new BufferedReader(new FileReader(myfile.csv))
      {
        String line;

        while ((line = reader.readLine()) != null) 
        {
          String[] tokens = line.split(",");
          myObjects.add(new MyObject(tokens[0], tokens[1], tokens[2]));
        }
      }
    }
    catch (IndexOutOfBoundsException e)
    {
      ...
    }
    catch (IOException e)
    {
      ...
    }
  }
}

Затем вы можете извлечь эту основную часть логики в пользовательский класс reader , что-то вроде этого:

public class MyObjectReader implements Closeable
{
  private BufferredReader reader;

  public MyObjectReader(Reader reader)
  {
    this.reader = new BufferredReader(reader);
  }

  public MyObject read() throws IOException
  {
    String line = reader.readLine();

    if (line != null)
    {
      String[] tokens = line.split(",");

      if (tokens.length < 3)
      {
        throw new IOException("Invalid line encountered: " + line);
      }

      return new MyObject(tokens[0], tokens[1], tokens[2]);
    }
    else
    {
      return null;
    }
  }

  public void close() throws IOException
  {
    this.reader.close();
  }
}

Класс MyObjectReader полностью тестируемый и, что важно, не зависит от наличия файлов или других ресурсов, поэтому вы можете протестировать его следующим образом:

public MyObjectReaderTest
{
  @Test
  public void testRead() throws IOException
  {
    String input = "value1.1,value1.2,value1.3\n" +
      "value2.1,value2.2,value2.3\n" +
      "value3.1,value3.2,value3.3";

    try (MyObjectReader reader = new MyObjectReader(new StringReader(input)))
    {
      assertEquals(new MyObject("value1.1", "value1.2", "value1.3"), reader.read());
      assertEquals(new MyObject("value2.1", "value2.2", "value2.3"), reader.read());
      assertEquals(new MyObject("value3.1", "value3.2", "value3.3"), reader.read());
      assertNull(reader.read());
    }
  }

  @Test(expected=IOException.class)
  public void testReadWithInvalidLine() throws IOException
  {
    String input = "value1.1,value1.2";

    try (MyObjectReader reader = new MyObjectReader(new StringReader(input)))
    {
      reader.read();
    }
  }
}

Не видя вашего код или зная формат файла трудно расширить на этом, но, надеюсь, вы получите суть.

Наконец, ваша статическая инициализация будет тогда Просто:

public MyClass
{
  private static List<MyObject> myObjects = new ArrayList<>();

  static
  {
    loadMyObjects(new FileReader("myfile.csv"));
  }

  /* package */ static void loadMyObjects(Reader reader)
  {
    try
    {
      try (MyObjectReader reader = new new MyObjectReader(reader))
      {
        MyObject myObject;

        while ((myObject = reader.read()) != null) 
        {
          myObjects.add(myObject);
        }
      }
    }
    catch (IOException e)
    {
      ...
    }
  }
}    

Может быть, стоит проверить счастливый путь здесь, но лично метод loadMyObjects теперь настолько прост, что я, вероятно, не стал бы беспокоиться.

Если вы не можете избежать статического инициализатора, извлеките его тело в метод. Затем испытайте метод. Ваш статический инициализатор будет выглядеть как static { myMethod(); }, который вряд ли можно сломать.