2

これが私のコードです:

public void GenerateCarDetailsFile(IList<int> carIds, string location)
{

   var cars = Uow.Query<Car>().Where(x => carIds.Contains(x.Id));

   var stringWriter = new StringWriter();

   stringWriter.WriteLine("Make, Model, Year");

   foreach(var car in cars)
   {
     stringWriter.WriteLine("{0},{1},{2}", car.Make, car.Model, car.Year);
   }

   SaveToFile(stringWriter, location);
}

public void SaveToFile(StringWriter stringWriter, string location)
{
   var bytes = new System.Text.UTF8Encoding().GetBytes(stringWriter.ToString());
   var file = System.IO.File.OpenWrite(location);
   file.Write(bytes, 0, bytes.Length);
   file.Close();
}

だから私はデータベースからたくさんの車を取得しています。それらをstringWriterに書き込んでから、ファイルに保存します。

私の質問は、正しい情報がファイルに保存されていることを単体テストする方法です。これはテスト不可能ですか?それは統合テストのようなものですか?

どちらのメソッドも void を返すため、その方法が想像できません。

4

5 に答える 5

2

保存されたファイルを読み込んで解析することで、正しいデータが保存されているかどうかをテストできます。データを正しく解析できれば、「正しく保存」されているはずです。コードが実際にファイルをディスクに保存するかどうかを尋ねている場合、それはクラスではなく FileStream クラスの単体テストです。

ただし、モック ライブラリを使用してクラスの動作をテストする方が簡単です。これは、C# コミュニティで人気があると思われる 3 つのフレームワークの比較です。Rhino Mocks vs Moq vs NSubstitute . NUnit (ナゲットでも利用可能)もお勧めします。これは、優れたテスト フレームワークだからです。

モッキング フレームワークを使用する場合、クラスで使用できる「偽の」オブジェクトを作成します。これは、依存性注入 (つまり、クラスに依存性を注入する) を使用する必要があることも意味します。クラスの依存関係は、データベース アクセス クラスとファイル アクセス クラスのようです。それらをクラスに渡すことで、単一責任の原則にも従うことになります。簡単に言えば、1 つのクラスは 1 つのことだけを知っている必要があるということです。(たとえば、データベースへのアクセス方法やファイルへのアクセス方法を知っている必要はありません)

必要なものIDatabaseRepositoryIFileStorageまたはそれらの線に沿ったものに対して 2 つのインターフェイスを作成するだけです。次に、それらのインスタンスをクラスに注入します。単体テストを作成しているとき、これらは簡単にモックされます。たとえば、Rhino モックを使用すると、単体テストはこれに沿って調べることができます。

public interface IDatabaseProvider {
    IEnumerable<Car> GetCars();
}

public interface IFileStorage {
    string ReadText(string filepath);
    void SaveText(string filepath, string content);
}

public class MyClass {
    private readonly IDatabaseProvider dataProvider;
    private readonly IFileStorage storage;

    public MyClass(IDatabaseProvider dataProvider, IFileStorage storage) {
        this.dataProvider = dataProvider;
        this.storage = storage;
    }

    public void GenerateCarDetailsFile(IList<int> carIds, string location) {
        var cars = dataProvider.GetCars().Query<Car>().Where(x => carIds.Contains(x.Id));
        StringBuilder builder = new StringBuilder();
        builder.AppendLine("Make, Model, Year");

        foreach(var car in cars) {
            builder.WriteLine("{0},{1},{2}", car.Make, car.Model, car.Year);
        }

        storage.SaveText(location, builder.ToString());
    }
}

[Test]
public void GenerateCarDetailsSavesFile() {
    // Arrange
    var databaseReturnValue = new List<Car> { new Car() { Make = "ma", Model = "mo", Year = 1900 };
    var location = "testpath.ext";
    var ids = new List<int> { 1, 3, 6 };
    var expectedOutput = "Make, Model, Year\r\nma,mo,1900";

    var database = MockRepository.GenerateMock<IDatabaseProvider>();
    var storage = MockRepository.GenerateMock<IFileStorage>();

    database
       .Stub(m => m.GetCars())
       .Return(databaseReturnValue);
    storage
       .Expect(m => m.SaveText(Arg<string>.Is.Equal(location),
                               Arg<string>.Is.Equal(expectedOutput)));

    MyClass testee = new MyClass(database, storage);

    // Act
    testee.GenerateCarDetailsFile(ids, location);

    // Assert
    storage.VerifyAllExpectations();
}

SaveTextクラスの動作と、IFileStorage依存関係を呼び出す必要があるという事実をテストしています。依存性注入を使用し、すべての補助システムを抽象化することで、データベースにアクセスできない場合やファイル システムがいっぱいになった場合に失敗しないテストを作成できます (これらのイベントは別の単体テストになる可能性があることに注意してください)。

また、より移植性の高いクラスを作成します。これを別の方法でファイル システムにアクセスする別のプラットフォーム (.NET と Windows ストアなど) に移動FileするStorageFile場合は、プラットフォーム固有のIFileStorage実装を作成するだけです。

したがって、他のクラスの動作をテストしないでください。代わりに、依存関係に関してクラスの動作をテストしてください。次に、モックを使用して、テスト間で同じように機能する依存関係の動作をセットアップします。

于 2013-05-23T22:42:56.897 に答える
1

あなたの解決策は依存性注入によるモックです。このようにして、あなたは何にも依存しません。純粋な論理テスト。

于 2013-05-23T21:11:38.400 に答える
1

mockモック フレームワークを使用して、データ アクセス オブジェクトが必要になる場合があります。そうすれば、実際のデータベースの内容に依存したり、データベース接続を必要とせずに単体テストを行うことができます。

さらに、最初のメソッドの「取得部分」と「保存部分」を分割して、これらの部分を個別にテストできるようにします。

public StringWriter GenerateCarDetails(IList<int> carIds)
{

   var cars = Uow.Query<Car>().Where(x => carIds.Contains(x.Id));

   var stringWriter = new StringWriter();

   stringWriter.WriteLine("Make, Model, Year");

   foreach(var car in cars)
   {
     stringWriter.WriteLine("{0},{1},{2}", car.Make, car.Model, car.Year);
   }

   return stringWriter;
}

public void SaveToFile(StringWriter stringWriter, string location)
{
   var bytes = new System.Text.UTF8Encoding().GetBytes(stringWriter.ToString());
   var file = System.IO.File.OpenWrite(location);
   file.Write(bytes, 0, bytes.Length);
   file.Close();
}

またはそのように:

public IEnumerable<Car> LoadCarDetails(IList<int> carIds)
{
   var cars = Uow.Query<Car>().Where(x => carIds.Contains(x.Id));
   return cars;
}

public StringWriter ConvertCarListToStrings(IEnumerable<Car> cars)
{
   var stringWriter = new StringWriter();

   stringWriter.WriteLine("Make, Model, Year");

   foreach(var car in cars)
   {
     stringWriter.WriteLine("{0},{1},{2}", car.Make, car.Model, car.Year);
   }

   return stringWriter;
}

public void SaveToFile(StringWriter stringWriter, string location)
{
   var bytes = new System.Text.UTF8Encoding().GetBytes(stringWriter.ToString());
   var file = System.IO.File.OpenWrite(location);
   file.Write(bytes, 0, bytes.Length);
   file.Close();
}

したがって、少なくともConvertCarListToStrings既知のデータでテストできます。

于 2013-05-23T21:01:23.073 に答える
0

IDataProviderのようなメソッドを使用して特殊なインターフェイスを作成できますGetCarsById(IEnumerable<int> ids)。次に、すべてのクエリをインターフェイスの実装に移動します。必要な場所に注入された実装を取得し、テスト用のモック実装を作成します。

于 2013-05-23T21:06:17.937 に答える
0

このプロセス全体をテストすることは、統合テストに近いものです。私にとってうまくいったのは、テストでテストデータベースを作成し、サンプルデータをロードしてから、クエリを含むクラスにデータベース接続 (または ORM 表現) を渡すことです。

呼び出した後GenerateCarDetailsFile、テストでファイルを (渡された場所から) ロードし、正しく表示されることを確認できます。次に、最後にテストをクリーンアップして、ファイルとテスト データベースを削除します。

別のオプションは、適切なモック/置換フレームワークを使用して、DB やファイル IO のいずれかをモックすることです。私の個人的なお気に入りはNSubstitute( http://nsubstitute.github.io/ )

于 2013-05-23T21:04:56.913 に答える