2016-05-16 109 views
0

我一直在閱讀關於TDD的文章,並且我看到很多關於不做任何數據庫事務的文章,因爲「單獨的,沒有依賴關係的代碼塊」。TDD插入到數據庫

所以現在我有一點兩難 - 我希望能夠測試我的服務層方法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()); 
} 
+1

創建一個可以在單元測試中模擬的DbContext抽象以驗證測試方法 – Nkosi

+0

'DbContext'從哪裏來?它是實體框架嗎?您使用的是什麼單元測試框架(即NUnit)?我們能否看到任何示例代碼,顯示您想要模擬/測試的內容? –

+0

在上面添加了一個示例代碼 –

回答

1

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 
} 

與目標類refactors到...

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

對於生產,你可以創建接口的正確實施,並將其注入到類這取決於他們。此答案完全基於您在帖子中提供的示例。花點時間瞭解使用的概念,並在網上做更多的研究。隨着您在TDD中的進展,您可以將這些概念與您項目的其餘部分一起應用。

+0

快速的問題...在我以前的項目中,我會使用抽象的UoW,但由於這個將使用EDMX文件,UoW的抽象仍然是必要的?這不是重複的工作嗎? –

+0

另一件事是我想使用工廠的原因是因爲每當我創建一個模型的實例(在這種情況下是學生)時,我不必猜測哪些屬性是必需的或不是。隨着行動沒有什麼真正的東西可以阻止我創建一個沒有名字和姓氏的學生實例 - 一個只有DoB的學生。 –

+0

這只是一個基於原始文章中代碼的示例。你仍然可以創建你自己的方法,做到你最初的目的。 – Nkosi