単体テストの重要な要素は、コードをテストに適した構造にすることです。
残念ながら、お気づきのように、複雑な 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
方法が非常にシンプルになっているので気にしないでしょう。