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 的进展,您可以将这些概念应用于项目的其余部分。