您可以通过读取保存的文件并对其进行解析来测试它是否保存了正确的数据。如果可以正确解析数据,则应“正确保存”。如果您询问代码是否实际上将文件保存到磁盘,那是 FileStream 类的单元测试,而不是您的类。
不过,使用模拟库来测试类的行为会更容易。下面是在 C# 社区中似乎很流行的三个框架的比较;Rhino Mocks vs Moq vs NSubstitute。我还会推荐NUnit(也可以与 nuget 一起使用),因为它是一个很好的测试框架。
使用模拟框架时,您创建了一个您的类可以使用的“假”对象。这也意味着您应该使用依赖注入(即向您的类注入依赖)。您的类的依赖项似乎是一个数据库访问类和一个文件访问类。通过将这些传递给您的班级,您也遵循了单一职责原则,简单来说就是一个班级应该只知道一件事。(例如,它不应该知道如何访问数据库以及如何访问文件)
只需根据您的需要创建两个接口,IDatabaseRepository
或者IFileStorage
类似的东西。然后将这些实例注入你的类。当您创建单元测试时,这些很容易被模拟。例如,使用 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
。通过使用依赖注入和抽象所有辅助系统,您可以创建不会因为数据库不可访问或文件系统已满而失败的测试(请注意,这些事件可能是另一个单元测试)。
您还将创建更便携的类。将其移至具有另一种访问文件系统的方式的另一个平台时(例如,在 .NETFile
与StorageFile
Windows Store 中),您只需创建一个特定于平台的IFileStorage
实现。
所以,不要测试其他类的行为。测试你的类的行为,而不是它的依赖关系。然后使用模拟来设置那些在测试之间工作相同的依赖项的行为。