【问题标题】:What's the best way to test a class static initializer?测试类静态初始化程序的最佳方法是什么?
【发布时间】:2014-12-11 11:56:16
【问题描述】:

我有一个具有相当复杂的静态初始化的类。我正在从目录中读取文件,然后解析这些 json 文件,映射到对象,并填写一个列表。你可以想象,可能会出现一些异常,我需要覆盖并测试这些代码分支。问题是这个静态初始化只运行一次/Testcase 文件。 我遇到的解决方案:

  • 每个行为的新测试用例文件
  • 卸载静态类
  • 新的 JVM

我对这些选项不感兴趣,难道没有更好的选择吗?

【问题讨论】:

标签: java junit static-initializer


【解决方案1】:

如果您无法避免静态初始化程序,请将它的主体提取到方法中。然后测试方法。你的静态初始化器看起来像static { myMethod(); },几乎不能被破坏。

【讨论】:

  • Nick Holt 做了更详细的回答,但基本思想是一样的。谢谢:)
【解决方案2】:

单元测试中的一个重要因素是构建代码以使其适合测试。

不幸的是,正如您所发现的,执行复杂 IO 操作的具有过多静态初始化的代码并不是一个易于测试的结构。

除了静态初始化之外,听起来您的代码违反了single-responsibility principle,就像一个类从外部源加载自身并且可能还有其他用途一样。

因此,您需要进行一些重构,例如,如果您的代码看起来像这样(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 方法现在如此简单,我可能不会费心了。

【讨论】:

  • 哇,感谢您提供如此详尽而详细的回答。 IO 繁重任务的单独阅读器类是个好主意。
  • 还有System.loadLibrary(...)等场景是effectively static, regardless of code design。在某些情况下,无法控制所有正在测试的类。 OP 的问题有其优点,尽管不是在他的确切用例中。
猜你喜欢
  • 1970-01-01
  • 2012-01-21
  • 2010-09-17
  • 1970-01-01
  • 2018-08-22
  • 2012-01-28
  • 2013-08-08
  • 2016-02-09
  • 1970-01-01
相关资源
最近更新 更多