2012-04-17 29 views
0

我正在用單元測試(TDD)弄溼我的腳。我有一個基本的存儲庫模式,我正在測試,我不確定我是否正確地做事情。在這個階段,我正在測試我的域名,而不是擔心控制器和視圖。爲了保持簡單,這裏是一個演示項目。使用存儲庫模式時測試域

public class Person 
{ 
    public int PersonID { get; set; } 
    public string Name{ get; set; } 
} 

接口

public interface IPersonRepository 
{ 
    int Add(Person person); 
} 

混凝土

public class PersonnRepository : IPersonRepository 
{ 

    DBContext ctx = new DBContext(); 

    public int Add(Person person) 
    { 
     // New entity 
     ctx.People.Add(person); 
     ctx.SaveChanges(); 
     return person.id; 

    } 
} 

我添加NUnit的和最小起訂量,以我的測試項目,並想知道如何正確地測試功能。

我不確定是否正確,但在閱讀完一些博客後,我創建了一個FakeRepository,但是如果我基於此測試,那麼如何驗證我的實際接口?

public class FakePersonRepository 
{ 

    Dictionary<int, Person> People = new Dictionary<int, Person>(); 

    public int Add(Person person) 
    { 
     int id = People.Count + 1; 
     People.Add(id, person); 
     return id; 
    } 
} 

然後用

[Test] 
    public void Creating_A_Person_Should_Return_The_ID() 
    { 

     FakePersonRepository repository = new FakePersonRepository(); 

     int id = repository.Add(new Person { Name = "Some Name" }); 

     Assert.IsNotNull(id); 

    } 

測試我是不是在任何地方接近測試在正確的莊園?

我想測試一些事情,比如未傳遞名稱導致錯誤等。

回答

0

你需要提取它的接口,使您的DbContext注射:

public interface IDBContext{ 
    IList<Person> People {get;} // I'm guessing at the types 
    void SaveChanges(); 
    //  etc. 
} 

然後注入到這一點你的具體類:

public class PersonRepository : IPersonRepository 
{ 

    IDBContext ctx; 

    public PersonRepository(IDBContext db) { 
     ctx = db; 
    } 

    public int Add(Person person) 
    { 
     // New entity 
     ctx.People.Add(person); 
     ctx.SaveChanges(); 
     return person.id; 

    } 
} 

您的測試將隨後的樣子:

[Test] 
public void Creating_A_Person_Should_Return_The_ID() 
{ 

    Mock<IDBContext> mockDbContext = new Mock<IDBContext>(); 
    // Setup whatever mock values/callbacks you need 

    PersonRepository repository = new PersonRepository(mockDbContext.Object); 

    int id = repository.Add(new Person { Name = "Some Name" }); 

    Assert.IsNotNull(id); 

    // verify that expected calls are made against your mock 
    mockDbContext.Verify(db => db.SaveChanges(), Times.Once()); 
    //... 

}

+0

也謝謝。我可能會錯過理解或者我的例子令人困惑。在這種情況下,dbContext是爲了表示EF DbContext,它允許我使用EF和數據庫。因此它不會顯示屬性。 我的具體PersonRepository將訪問EF寫入數據庫,我明白顯示不是單元測試,但後來集成測試。 – stevejgordon 2012-04-18 09:22:53

+0

我假設我需要一個內存版本的這個實現了相同的接口(從等式中拿出數據庫),然後我想確保我的測試意味着我們的測試,我們確認我的方法。我想我有點失落,因爲我對MVC,EF Code第一和TDD的概念都很陌生。 – stevejgordon 2012-04-18 09:25:15

+0

你想測試你的具體實現,而不是你的接口。爲了做到這一點,你需要將你的實現從DBContext中分離出來,這就是爲什麼我建議你爲它提取一個接口,你可以將它注入到你的PersonRepository具體類中。 單元測試的關鍵是確保您的所有依賴項都是可注入的,以便您可以隔離類中的代碼。在你的單元測試中,你傳遞你的依賴關係的模擬版本。在你的情況下,這意味着你必須能夠注入你的DBContext,如上所述。 – 2012-04-18 14:56:02

4

我在哪裏接近在正確的莊園測試?

恐怕你不是。有一個接口的想法是,它允許你分離其他使用存儲庫的代碼,例如你的控制器,並且能夠單獨測試它。因此,讓我們假設你有你想要的單元測試以下控制器:

public class PersonController : Controller 
{ 
    private readonly IPersonRepository _repo; 
    public PersonController(IPersonRepository repo) 
    { 
     _repo = repo; 
    } 

    [HttpPost] 
    public ActionResult Create(Person p) 
    { 
     if (!ModelState.IsValid) 
     { 
      return View(p); 
     } 

     var id = _repo.Add(p); 
     return Json(new { id = id }); 
    } 
} 

注意如何控制不依賴於特定的資源庫實現。所有的需求是這個倉庫實現給定的合同。現在,我們可以使用模擬框架,如起訂量在單元測試中提供假資料庫,並使其表現爲我們要想在Create動作來測試2分可能的路徑,如:

[TestMethod] 
public void PersonsController_Create_Action_Should_Return_View_And_Not_Call_Repository_If_ModelState_Is_Invalid() 
{ 
    // arrange 
    var fakeRepo = new Mock<IPersonRepository>(); 
    var sut = new PersonController(fakeRepo.Object); 
    var p = new Person(); 
    sut.ModelState.AddModelError("Name", "The name cannot be empty"); 
    fakeRepo.Setup(x => x.Add(p)).Throws(new Exception("Shouldn't be called.")); 

    // act 
    var actual = sut.Create(p); 

    // assert 
    Assert.IsInstanceOfType(actual, typeof(ViewResult)); 
} 


[TestMethod] 
public void PersonsController_Create_Action_Call_Repository() 
{ 
    // arrange 
    var fakeRepo = new Mock<IPersonRepository>(); 
    var sut = new PersonController(fakeRepo.Object); 
    var p = new Person(); 
    fakeRepo.Setup(x => x.Add(p)).Returns(5).Verifiable(); 

    // act 
    var actual = sut.Create(p); 

    // assert 
    Assert.IsInstanceOfType(actual, typeof(JsonResult)); 
    var jsonResult = (JsonResult)actual; 
    var data = new RouteValueDictionary(jsonResult.Data); 
    Assert.AreEqual(5, data["id"]); 
    fakeRepo.Verify(); 
} 
+0

感謝您的回覆。我假設我可以在這個階段測試域(CRUD +更復雜的服務),而不需要控制器和視圖。我想先讓領域模型和接口與它一起工作,以便在擔心輸出之前先驅動商業元素的設計。這不可能嗎? – stevejgordon 2012-04-18 09:21:28

0

我個人看看爲此編寫一個「集成測試」,也就是說,你的數據訪問層不應該包含使隔離測試值得的任何邏輯,從而實現一個真正的(ish)數據庫。

在這種情況下,您需要啓動並運行數據庫。這可能是某個地方已經設置的開發人員數據庫,或者是作爲測試安排的一部分啓動的內存數據庫。

之所以這樣做,是因爲我發現(純粹的)DAL的單元測試通常最終會證明您可以使用模擬框架,並且不會讓您對代碼更有信心。

如果您對單元測試完全陌生,並且沒有大學在手來幫助設置DAL測試所需的環境,那麼我建議您現在就開始測試DAL,並將注意力集中在業務邏輯上這是您將獲得最大收益的地方,並且可以讓您更輕鬆地看到測試如何幫助您。