TDD 插入数据库
TDD Inserting to Database
我一直在阅读有关 TDD 的文章,并且看到很多关于不要进行任何数据库事务的帖子,因为 "single, isolated block of code with no dependencies"。
所以现在我有点进退两难 - 我希望能够测试名为 AddNewStudent
的服务层方法是否真的有效。这个方法进入我的 DbContext
,然后将一条新记录添加到数据库中。如果 TDD 不推荐数据库操作,那么除了在浏览器上测试我的应用程序之外,我还能如何测试 AddNewStudent
方法?
public class StudentManager : ManagerBase
{
internal StudentManager() { }
public Student AddNewStudent(string fName, string lName, DateTime dob)
{
// Create a student model instance using factory
var record = Factories.StudentFac.CreateOne(fName, lName, dob);
DbContext.Students.Add(record);
DbContext.SaveChanges();
return record;
}
}
我的测试是这样的
[TestMethod]
public void StudentManager_AddNewStudent_Test()
{
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3/1/2006");
var student = Managers.StudentManager.AddNewStudent(fName, lName, dob);
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
您的 StudentManager
具有隐藏在内部的依赖项,这使得测试变得困难。考虑重构您的设计以实现更好的可测试性。
查看 StudentManager
得出以下假设...
//An assumed abstraction of the ManagerBase
public abstract class ManagerBase {
public ManagerBase(IDbContext dbContext, IFactory factories) {
DbContext = dbContext;
Factories = factories;
}
public IDbContext DbContext { get; private set; }
public IFactory Factories { get; private set; }
}
//An abstraction of what the unit of work would look like
public interface IDbContext {
//student repository
DbSet<Student> Students { get; }
//...other repositories
int SaveChanges();
}
//Just an example of the Student Factory.
public interface IModelFactory<T> where T : class, new() {
T Create(Action<T> configuration);
}
public interface IFactory {
IModelFactory<Student> StudentFac { get; }
//...other factories. Should try to make your factories Generic
}
目标 class 重构为...
public class StudentManager : ManagerBase {
public StudentManager(IDbContext dbContext, IFactory factories) : base(dbContext, factories) { }
public Student AddNewStudent(string fName, string lName, DateTime dob) {
// Create a student model instance using factory
var record = Factories.StudentFac.Create(r => {
r.FirstName = fName;
r.LastName = lName;
r.DoB = dob;
});
base.DbContext.Students.Add(record);
base.DbContext.SaveChanges();
return record;
}
}
虽然它看起来可能很多,但它将极大地帮助您的代码的可测试性。
现在可以使用像 Moq
这样的模拟框架来创建数据库访问和工厂的伪造版本...
[TestMethod]
public void StudentManager_Should_AddNewStudent() {
//Arrange: setup/initialize the dependencies of the test
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3006-01-03");
//using Moq to create mocks/fake of dependencies
var dbContextMock = new Mock<IDbContext>();
//Extension method used to create a mock of DbSet<T>
var dbSetMock = new List<Student>().AsDbSetMock();
dbContextMock.Setup(x => x.Students).Returns(dbSetMock.Object);
var factoryMock = new Mock<IFactory>();
factoryMock
.Setup(x => x.StudentFac.Create(It.IsAny<Action<Student>>()))
.Returns<Action<Student>>(a => {
var s = new Student();
a(s);
return s;
});
//this is the system/class under test.
//while this is being created manually, you should look into
//using DI/IoC container to manage Dependency Injection
var studentManager = new StudentManager(dbContextMock.Object, factoryMock.Object);
//Act: here we actually test the method
var student = studentManager.AddNewStudent(fName, lName, dob);
//Assert: and check that it executed as expected
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
对于生产,您可以创建接口的适当实现并将它们注入到依赖于它们的 classes 中。该答案完全基于您在 post 中提供的示例。花一些时间来理解所使用的概念,并在网上做更多的研究。然后,随着 TDD 的进展,您可以将这些概念应用于项目的其余部分。
我一直在阅读有关 TDD 的文章,并且看到很多关于不要进行任何数据库事务的帖子,因为 "single, isolated block of code with no dependencies"。
所以现在我有点进退两难 - 我希望能够测试名为 AddNewStudent
的服务层方法是否真的有效。这个方法进入我的 DbContext
,然后将一条新记录添加到数据库中。如果 TDD 不推荐数据库操作,那么除了在浏览器上测试我的应用程序之外,我还能如何测试 AddNewStudent
方法?
public class StudentManager : ManagerBase
{
internal StudentManager() { }
public Student AddNewStudent(string fName, string lName, DateTime dob)
{
// Create a student model instance using factory
var record = Factories.StudentFac.CreateOne(fName, lName, dob);
DbContext.Students.Add(record);
DbContext.SaveChanges();
return record;
}
}
我的测试是这样的
[TestMethod]
public void StudentManager_AddNewStudent_Test()
{
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3/1/2006");
var student = Managers.StudentManager.AddNewStudent(fName, lName, dob);
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
您的 StudentManager
具有隐藏在内部的依赖项,这使得测试变得困难。考虑重构您的设计以实现更好的可测试性。
查看 StudentManager
得出以下假设...
//An assumed abstraction of the ManagerBase
public abstract class ManagerBase {
public ManagerBase(IDbContext dbContext, IFactory factories) {
DbContext = dbContext;
Factories = factories;
}
public IDbContext DbContext { get; private set; }
public IFactory Factories { get; private set; }
}
//An abstraction of what the unit of work would look like
public interface IDbContext {
//student repository
DbSet<Student> Students { get; }
//...other repositories
int SaveChanges();
}
//Just an example of the Student Factory.
public interface IModelFactory<T> where T : class, new() {
T Create(Action<T> configuration);
}
public interface IFactory {
IModelFactory<Student> StudentFac { get; }
//...other factories. Should try to make your factories Generic
}
目标 class 重构为...
public class StudentManager : ManagerBase {
public StudentManager(IDbContext dbContext, IFactory factories) : base(dbContext, factories) { }
public Student AddNewStudent(string fName, string lName, DateTime dob) {
// Create a student model instance using factory
var record = Factories.StudentFac.Create(r => {
r.FirstName = fName;
r.LastName = lName;
r.DoB = dob;
});
base.DbContext.Students.Add(record);
base.DbContext.SaveChanges();
return record;
}
}
虽然它看起来可能很多,但它将极大地帮助您的代码的可测试性。
现在可以使用像 Moq
这样的模拟框架来创建数据库访问和工厂的伪造版本...
[TestMethod]
public void StudentManager_Should_AddNewStudent() {
//Arrange: setup/initialize the dependencies of the test
var fName = "Ryan";
var lName = "Rigil";
var dob = DateTime.Parse("3006-01-03");
//using Moq to create mocks/fake of dependencies
var dbContextMock = new Mock<IDbContext>();
//Extension method used to create a mock of DbSet<T>
var dbSetMock = new List<Student>().AsDbSetMock();
dbContextMock.Setup(x => x.Students).Returns(dbSetMock.Object);
var factoryMock = new Mock<IFactory>();
factoryMock
.Setup(x => x.StudentFac.Create(It.IsAny<Action<Student>>()))
.Returns<Action<Student>>(a => {
var s = new Student();
a(s);
return s;
});
//this is the system/class under test.
//while this is being created manually, you should look into
//using DI/IoC container to manage Dependency Injection
var studentManager = new StudentManager(dbContextMock.Object, factoryMock.Object);
//Act: here we actually test the method
var student = studentManager.AddNewStudent(fName, lName, dob);
//Assert: and check that it executed as expected
Assert.AreEqual(fName, student.FirstName);
Assert.AreEqual(lName, student.LastName);
Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString());
}
对于生产,您可以创建接口的适当实现并将它们注入到依赖于它们的 classes 中。该答案完全基于您在 post 中提供的示例。花一些时间来理解所使用的概念,并在网上做更多的研究。然后,随着 TDD 的进展,您可以将这些概念应用于项目的其余部分。