4

かなり複雑な静的初期化を持つクラスがあります。ディレクトリからファイルを読み取り、それらの json ファイルを解析し、オブジェクトにマッピングし、リストを埋めています。ご想像のとおり、いくつかの例外が発生する可能性があり、それらのコードブランチをカバーしてテストする必要があります。問題は、この静的初期化がテストケース ファイルで 1 回だけ実行されることです。私が遭遇するソリューション:

  • 各動作の新しいテストケース ファイル
  • 静的クラスをアンロードする
  • 新しい JVM

私はこれらのオプションに魅力を感じていません。もっと良いものはありませんか?

4

2 に答える 2

3

単体テストの重要な要素は、コードをテストに適した構造にすることです。

残念ながら、お気づきのように、複雑な IO 操作を実行する過剰な静的初期化を伴うコードは、簡単にテストできる構造ではありません。

静的な初期化以外に、クラスが外部ソースからそれ自体をロードし、おそらく他の用途がある限り、コードが単一責任の原則に違反しているように思えます。

したがって、たとえば、コードが次のようになっている場合は、いくつかのリファクタリングを行う必要があります (明確にするために、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)
    {
      ...
    }
  }
}

次に、この大量のロジックを次のようなカスタムリーダークラスに抽出できます。

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方法が非常にシンプルになっているので気にしないでしょう。

于 2014-10-16T08:23:21.200 に答える