Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/337.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何从数据库中获取数据并保存到文件的单元测试方法是保存正确的数据_C#_Unit Testing - Fatal编程技术网

C# 如何从数据库中获取数据并保存到文件的单元测试方法是保存正确的数据

C# 如何从数据库中获取数据并保存到文件的单元测试方法是保存正确的数据,c#,unit-testing,C#,Unit Testing,这是我的密码: 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, Yea

这是我的密码:

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。

您可能希望使用模拟框架模拟数据访问对象。这样,您就可以对其进行单元测试,而不依赖于实际的数据库内容,甚至不需要数据库连接

此外,我将分割第一个方法的检索部分和保存部分,以便您可以单独测试这些部分:

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();
}
因此,您至少可以使用已知数据测试ConvertCarListToString。

您可能希望使用模拟框架模拟数据访问对象。这样,您就可以对其进行单元测试,而不依赖于实际的数据库内容,甚至不需要数据库连接

此外,我将分割第一个方法的检索部分和保存部分,以便您可以单独测试这些部分:

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();
}

因此,您至少可以使用已知数据测试ConvertCarListToString。

测试整个过程更像是一个集成测试。对我来说,有效的方法是让测试创建一个测试数据库,向其中加载一些示例数据,然后将数据库连接或ORM表示传递给包含查询的类

调用GenerateCarDetailsFile后,您可以让测试从传入位置加载该文件,并确保其外观正确。然后最后让测试本身清理后删除文件和测试数据库


另一种选择是使用良好的模拟/替换框架来模拟DB和/或文件IO。我个人最喜欢的是NSubstitute

测试。整个过程更像是一个集成测试。对我来说,有效的方法是让测试创建一个测试数据库,向其中加载一些示例数据,然后将数据库连接或ORM表示传递给包含查询的类

调用GenerateCarDetailsFile后,您可以让测试从传入位置加载该文件,并确保其外观正确。然后最后让测试本身清理后删除文件和测试数据库


另一种选择是使用良好的模拟/替换框架来模拟DB和/或文件IO。我个人最喜欢的是NSubstitute

您可以使用getCarsByDieNumerable ID等方法创建一个专门的接口IDataProvider。然后将所有查询移动到接口的实现。在需要的地方注入实现,并创建模拟实现进行测试。

您可以使用getCarsByDieNumerable ID等方法创建专用接口IDataProvider。然后将所有查询移动到接口的实现。在需要的地方注入实现,并创建模拟实现进行测试。

您的解决方案是。 这样你就不依赖任何东西了。 纯逻辑测试。

您的解决方案是。 这样你就不依赖任何东西了。
纯逻辑测试。

您可以通过读回保存的文件并对其进行解析来测试它是否保存了正确的数据。如果可以正确解析数据,则应该正确保存数据。如果您询问代码是否确实将文件保存到磁盘,那么这是对FileStream类的单元测试,而不是您的类

不过,使用模拟库测试类的行为会更容易。这里比较了C社区中流行的三种框架。我还建议使用nuget,因为它是一个很好的测试框架

当使用模拟框架时,您创建了一个类可以使用的伪对象。这也意味着您应该使用依赖项注入,即向类注入依赖项。类的依赖项似乎是数据库访问类和文件访问类。通过将这些信息传递给您的班级,您也遵循了规则,简单地说,一个班级应该只知道一件事。例如,它不应该知道如何访问数据库以及如何访问文件

只需为您需要的内容创建两个接口,IDatabaseRepository和IFileStore或类似的东西。然后将这些实例注入到类中。当您创建单元测试时,这些测试很容易被模仿。例如,使用Rhino Mock,单元测试可以沿着这条线进行

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();
}
您正在测试类的行为,以及它应该在IFileStorage依赖项上调用SaveText这一事实。通过使用依赖注入和抽象所有辅助系统,您可以创建不会因为数据库不可访问或文件系统已满而失败的测试。请注意,这些事件可能是另一个单元测试 .

您还将创建更具可移植性的类。例如,当将其移动到另一个平台时,您只需创建一个特定于平台的iFileStore实现,该平台具有另一种访问文件系统文件与.NET vs Windows Store中的StorageFile的方式


因此,不要测试其他类的行为。改为测试类与其依赖项相关的行为。然后使用mock设置那些在测试之间工作相同的依赖项的行为。

您可以通过读回保存的文件并对其进行解析来测试它是否保存了正确的数据。如果可以正确解析数据,则应该正确保存数据。如果您询问代码是否确实将文件保存到磁盘,那么这是对FileStream类的单元测试,而不是您的类

不过,使用模拟库测试类的行为会更容易。这里比较了C社区中流行的三种框架。我还建议使用nuget,因为它是一个很好的测试框架

当使用模拟框架时,您创建了一个类可以使用的伪对象。这也意味着您应该使用依赖项注入,即向类注入依赖项。类的依赖项似乎是数据库访问类和文件访问类。通过将这些信息传递给您的班级,您也遵循了规则,简单地说,一个班级应该只知道一件事。例如,它不应该知道如何访问数据库以及如何访问文件

只需为您需要的内容创建两个接口,IDatabaseRepository和IFileStore或类似的东西。然后将这些实例注入到类中。当您创建单元测试时,这些测试很容易被模仿。例如,使用Rhino Mock,单元测试可以沿着这条线进行

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();
}
您正在测试类的行为,以及它应该在IFileStorage依赖项上调用SaveText这一事实。通过使用依赖注入和抽象所有辅助系统,您可以创建不会因为数据库不可访问或文件系统已满而失败的测试。请注意,这些事件可能是另一个单元测试

您还将创建更具可移植性的类。例如,当将其移动到另一个平台时,您只需创建一个特定于平台的iFileStore实现,该平台具有另一种访问文件系统文件与.NET vs Windows Store中的StorageFile的方式

因此,不要测试其他类的行为。改为测试类与其依赖项相关的行为。然后使用mock设置那些在中间测试中工作相同的依赖项的行为