概要
テスト自動化をするうえで、ファイルの検索・読込・書込は鬼門です。テストによって環境が変化することも、環境によってテスト結果が変化してしまうのも望ましくありません。
テスト時のみ一時ファイルに切り替えてもよいですが、それでもストレージへのアクセスがあるとテスト時間が長くなってしまいます。
そこで、ファイルシステムのモックを簡単につくれる、System.IO.Abstractionsを紹介します。
ファイルの内容を捜査するアプリ
修正前アプリケーションコード
公式ReadMeのサンプルを少し変更した以下のようなアプリを題材にします。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("思想捜査開始");
var myComp = new MyComponent();
myComp.Validate();
}
}
public class MyComponent
{
public void Validate()
{
foreach (var textFile in System.IO.Directory.GetFiles(@"D:\TestFolder", "2+2.txt", System.IO.SearchOption.TopDirectoryOnly))
{
var text = System.IO.File.ReadAllText(textFile);
if (text != "5")
{
System.IO.File.WriteAllText(textFile, "5");
throw new NotSupportedException("BIG BROTHER IS WATCHING YOU");
}
}
}
}
あるフォルダの中から"2+2.txt"というファイルを探して、中の答えが間違っていたら修正して警告するアプリケーションです。
しかしこのMyComponent
クラスはテストする上で以下の問題があります。
- 2+2.txt"ファイルを特定のディレクトリに用意する必要があります。もしそのディレクトリが作れない環境だとテストもできない
- "2+2.txt"ファイルの中身が間違っているケースのテストは実行するたびにファイルが正しくされるので、2回目からは失敗する
- テストのたびにストレージにアクセスするので、少し時間がかかる
- 間違った中身の"2+2.txt"ファイルを実際に自分のPCに置くのは身の危険を感じる
そこで、System.IO.Abstractionsを使って、この問題を解決します。
修正後アプリケーションコード
nugetで以下の2つを入れます。
dotnet add package System.IO.Abstractions
dotnet add package System.IO.Abstractions.TestingHelpers
先ほどのコードを修正します。
class Program
{
static void Main(string[] args)
{
Console.WriteLine("思想捜査開始");
var myComp = new MyComponentAb(new System.IO.Abstractions.FileSystem());
myComp.Validate();
}
}
public class MyComponentAb
{
private IFileSystem fileSystem;
public MyComponentAb(System.IO.Abstractions.IFileSystem fileSystem)
{
this.fileSystem = fileSystem;
}
public void Validate()
{
foreach (var textFile in fileSystem.Directory.GetFiles(@"D:\TestFolder", "2+2.txt", System.IO.SearchOption.TopDirectoryOnly))
{
var text = fileSystem.File.ReadAllText(textFile);
if (text != "5")
{
fileSystem.File.WriteAllText(textFile, "5");
throw new NotSupportedException("BIG BROTHER IS WATCHING YOU");
}
}
}
}
MyComponentAb
のコンストラクタで、実際のファイルシステムかモックかを切り替えます。コンソールアプリケーション側からは当然実際のファイルシステムSystem.IO.Abstractions.FileSystem()
を入れています。
動作は変わりません。
テストコード
次にMyComponentAb
のテストを作ります。テストライブラリはxUnitを使いますが、他のテストライブラリでもたいして変わらないと思います。
ソリューションにxUnitテストプロジェクトを追加します。テストしたいプロジェクトの参照を追加して、以下のnugetを追加します。
dotnet add package System.IO.Abstractions
dotnet add package System.IO.Abstractions.TestingHelpers
そしてテストコードを追加します。
public class UnitTest1
{
private string targetFilePath = @"D:\TestFolder\2+2.txt";
[Fact]
public void Test_MyComponent_Success()
{
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>()
{
[targetFilePath] = new MockFileData("5")
});
var myComp = new MyComponentAb(fileSystem);
myComp.Validate();
}
[Fact]
public void Test_MyComponent_ExceptionThrow()
{
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>()
{
[targetFilePath] = new MockFileData("4")
});
var myComp = new MyComponentAb(fileSystem);
Assert.Throws<NotSupportedException>(() => myComp.Validate());
Assert.Equal("5",
fileSystem.File.ReadAllText(targetFilePath));
}
}
正しいファイルと間違ったファイルの2つのテストを作り、両者とも成功しました。モックファイルシステムはDictionary
の形で保持しています。ファイルの修正が正しくできていることもモックから確認できます。
System.IO.Abstractionsはこれ以外にもFileSystemWatcherやFileInfoなどのモックがあります。ぜひ使ってみてください。
参考
全体コード
環境
VisualStudio 2019
C# 9
.NET 5
Microsoft.NET.Test.Sdk 16.9.4
xunit 2.4.1
xunit.runner.visualstudio 2.4.3
coverlet.collector 3.0.2
System.IO.Abstractions 13.2.38
System.IO.Abstractions.TestingHelpers 13.2.38