2011-10-12 45 views
1

我有一個單元測試,測試是否沒有提供寶寶的名字,然後不保存寶寶,另一個,如果第一個名字提供,然後保存應該被稱爲。我做了紅/綠/重構,並通過了。我添加了對姓氏做相同的新測試。現在名字的測試失敗了,因爲沒有提供姓氏。我已經提供了下面的測試代碼。我在想,如果我正在以這種錯誤的方式進行,或者我只是希望糾正破損的測試?新的測試導致舊的測試中斷,我做錯了嗎?

此外驗證器接口是必要的,因爲什麼是有效的更改取決於客戶端使用該軟件,否則我會將這些檢查編碼到Baby類本身。

更新:根據我已經收到的幾個答覆,似乎我正在對此做錯誤的方式。我應該怎麼做才能避免這種情況發生?

[TestMethod] 
    public void baby_is_not_saved_if_validation_fails() { 
     // arange 
     var validator = new Mock<IValidator<Baby>>(); 
     var output = new ValidationCollection(); 
     validator.Setup(v => v.IsValid(It.IsAny<Baby>(), out output)).Returns(false); 
     var unitOfWork = GetMock(); 
     // act 
     var b = new Baby(); 
     var svc = new BabyService(validator.Object, unitOfWork.Object); 
     svc.AddNewBaby(b); 
     // assert 
     unitOfWork.Verify(u => u.SaveChanges(), Times.Never()); 
    } 

    [TestMethod] 
    public void baby_is_saved_if_validation_passes() { 
     // arange 
     var validator = new Mock<IValidator<Baby>>(); 
     var output = new ValidationCollection(); 
     validator.Setup(v => v.IsValid(It.IsAny<Baby>(), out output)).Returns(true); 
     var unitOfWork = GetMock(); 
     // act 
     var b = new Baby(); 
     var svc = new BabyService(validator.Object, unitOfWork.Object); 
     svc.AddNewBaby(b); 
     // assert 
     unitOfWork.Verify(u => u.SaveChanges(), Times.Once()); 
    } 

    [TestMethod] 
    public void if_first_name_is_not_supplied_baby_is_not_added() { 
     // arrange 
     var validator = new DefaultBabyValidator(); 
     var unitOfWork = GetMock(); 
     // act 
     var b = new Baby(); 
     var svc = new BabyService(validator, unitOfWork.Object); 
     svc.AddNewBaby(b); 
     // assert 
     unitOfWork.Verify(u => u.SaveChanges(), Times.Never()); 
    } 

    Mock<IHealthUnitOfWork> GetMock() { 
     var uow = new Mock<IHealthUnitOfWork>(); 
     var dbSet = new Mock<IDbSet<Baby>>(); 
     dbSet.Setup(db => db.Add(It.IsAny<Baby>())).Returns(new Baby()); 
     uow.Setup(u => u.SaveChanges()).Verifiable(); 
     uow.SetupGet(u => u.Babies).Returns(dbSet.Object); 

     return uow; 
    } 

    [TestMethod] 
    public void if_first_name_is_supplied_baby_is_added() { 
     // arrange 
     var validator = new DefaultBabyValidator(); 
     var unitOfWork = GetMock(); 
     // act 
     var b = new Baby { FirstName = "Charles" }; 
     var svc = new BabyService(validator, unitOfWork.Object); 
     svc.AddNewBaby(b); 
     // assert 
     unitOfWork.Verify(u => u.SaveChanges(), Times.Once()); 
    } 

    [TestMethod] 
    public void if_last_name_is_not_supplied_baby_is_not_added() { 
     // arrange 
     var validator = new DefaultBabyValidator(); 
     var unitOfWork = GetMock(); 
     // act 
     var b = new Baby { FirstName = "Charles" }; 
     var svc = new BabyService(validator, unitOfWork.Object); 
     svc.AddNewBaby(b); 
     // assert 
     unitOfWork.Verify(u => u.SaveChanges(), Times.Never()); 
    } 
} 
+0

添加測試不應該打破其他測試。永遠。如果你改變你的代碼來通過一個新的測試,並打破舊的測試,那是不同的。 – Oded

+0

我首先想到了。但他正在談論TDD。當他寫下現在失敗的測試時,並沒有要求姓氏。現在,姓氏的新要求打破了他的舊測試。我對自己對這個問題的回答很感興趣。 :) – vhallac

+0

@vhallac - 即使使用TDD,當您在任何實現之前_write_一個_test_,新的_test_應該不會干擾現有測試(因爲尚未編寫新代碼)。如果是這樣,你做錯了什麼。 – Oded

回答

2

你的問題是你的測試代表了矛盾的要求。如果提供名字並且姓氏不是,那麼if_first_name_is_supplied_baby_is_added()表示應該保存嬰兒,但是if_last_name_is_not_supplied_baby_is_not_added()表示不應該保存嬰兒。

+0

那麼你有什麼建議來解決這個問題? –

+0

@Charles你需要解決矛盾。找出你真正的需求是什麼(可能保存寶貝當且僅當提供名字和姓氏時),使測試反映這些要求,然後修復程序以通過這些測試。 – JGWeissman

+1

所以我的測試確實需要改變。這些要求並沒有完全充實。我正在編寫我們現有的代碼。這意味着我應該期望我的測試隨着需求的變化而改變。正確? –

0

問題是,當時名字的原始測試沒有名字的規格。當你的規格改變時,你的測試可能會改變。我重構了我的測試,以便在Baby類中發現新字段時無需更改以前的測試。我現在有一種測試方法可以確定一個有效的嬰兒的真實條件。所需的實地測試,現在檢查以下內容:

  • 如果需要現場無效
    • 的IsValid必須返回false
    • 預期的異常必須存在返回的集合(AggregateException.InnerExceptions)
  • 如果需要的字段有效
    • 只檢查預期的異常不存在

現在我只需要在發現新字段時更新一個測試(所有字段都是有效測試)。

[TestMethod] // only update this one as new fields are discovered 
public void when_baby_is_valid_validation_returns_true() { 
    var validator = new DefaultBabyValidator(); 

    // valid baby goes here 
    var baby = new Baby(); 

    AggregateException fail; 
    Assert.IsTrue(validator.IsValid(baby, out fail)); 
} 

[TestMethod] 
public void validation_returns_exception_if_first_name_is_null() { 
    var validator = new DefaultBabyValidator(); 
    AggregateException results; 
    var isValid = validator.IsValid(new Baby(), out results); 
    var expected = results.InnerExceptions 
        .OfType<ArgumentException>() 
        .SingleOrDefault(ex => ex.ParamName == "FirstName"); 
    Assert.IsFalse(isValid); // should always return false if this condition is met 
    Assert.IsNotNull(expected); // should also contain the expected exception 
} 

[TestMethod] 
public void validation_does_not_return_exception_if_first_name_is_valid() { 
    var validator = new DefaultBabyValidator(); 
    AggregateException results; 
    validator.IsValid(new Baby { FirstName = "Charles" }, out results); 
    var expected = results.InnerExceptions 
     .OfType<ArgumentException>() 
     .SingleOrDefault(ex => ex.ParamName == "FirstName"); 
    Assert.IsNull(expected); // exception should not exsist. 
          // We don't care if it returns true. 
          // There is only one case where IsValid should 
          // return true. 
} 
相關問題