2012-10-09 233 views
3

我發現自己陷入這種模式很多,我寫了一個由非常小的方法組成的類,這些方法完全由我的單元測試執行。然後我發現我需要建立調用這些方法的另一種方法,我必須寫一個更復雜的單元測試爲 - 一個簡單的例子是說明:測試內部方法的單元測試公共方法

namespace FooRequest 
{ 
    static public class Verifier 
    { 
     static public bool IsValid(string request) 
     { 
      return (!IsAllCaps(request) && !ContainsTheLetterB(request)); 
     } 

     static internal bool IsAllCaps(string request) 
     { 
      return (request.Equals(request.ToUpper())); 
     } 

     static internal bool ContainsTheLetterB(string request) 
     { 
      return request.ToLower().Contains("b"); 
     } 
    } 
} 

對於代碼,我會寫單元測試覆蓋兩個內部方法是這樣的:

namespace UnitTest 
{ 
    using Microsoft.VisualStudio.TestTools.UnitTesting; 
    using FooRequest; 

    public class VerifierTest 
    { 
     [TestClass] 
     public class ContainsTheLetterB 
     { 
      [TestMethod] 
      public void ShouldReturnTrueForStringContainsB() 
      { 
       Assert.IsTrue(Verifier.ContainsTheLetterB("burns")); 
      } 

      [TestMethod] 
      public void ShouldReturnFakseForStringDoesNotContainB() 
      { 
       Assert.IsFalse(Verifier.ContainsTheLetterB("urns")); 
      } 
     } 

     [TestClass] 
     public class IsAllCaps 
     { 
      [TestMethod] 
      public void ShouldReturnTrueForStringIsAllCaps() 
      { 
       Assert.IsTrue(Verifier.IsAllCaps("IAMALLCAPS")); 
      } 

      [TestMethod] 
      public void ShouldReturnFakseForStringDoesNotContainB() 
      { 
       Assert.IsFalse(Verifier.IsAllCaps("IAMnotALLCAPS")); 
      } 
     } 
    } 
} 

對於公衆方法我真的只是想測試,「如果你調用的方法返回false,則返回false」 - 這很煩人,我必須建立在這樣的輸入一種強制我的內部方法返回true或false的方法 - 我對這個方法的測試不應該關心它調用的內部方法(對吧?)

[TestClass] 
    public class IsValid 
    { 
     [TestMethod] 
     public void ShouldReturnFalseForInvalidStringBecauseContainsB() 
     { 
      Assert.IsFalse(Verifier.IsValid("b")); 
     } 

     [TestMethod] 
     public void ShouldReturnFalseForInvalidStringBecauseIsAllCaps() 
     { 
      Assert.IsFalse(Verifier.IsValid("CAPS")); 
     } 

     [TestMethod] 
     public void ShouldReturnTrueForValidString() 
     { 
      Assert.IsTrue(Verifier.IsValid("Hello")); 
     } 
    } 

顯然,在這個例子中,這是不是太糟糕,但是當有很多的內部方法和輸入是不平凡的配置,測試我的公開「就是這個輸入有效」的方法變得複雜。

我應該爲我的所有內部方法創建一個接口,然後將其存儲到測試中,還是有一個更好的方法?

回答

0

更合適的方法是如下:

  1. 重構您Verifier類分爲三類,一爲在這種情況下,每個方法:VerifierAllCapsCheckerLetterBChecker
  2. 相應地重構你的測試類 - 現在應該有三個測試類。
  3. 使用您最喜歡的DI方法將兩個內部邏輯類注入到Verifier中。
  4. VerifierTests類應具有兩個依賴佈置並注入Verifier,只有測試Verifier邏輯(在這種情況下,只有邏輯運算符)。

在這裏你可以找到VerifierVerifierTests類的適應,只是爲了讓這個想法(我用的起訂量在這裏):

namespace FooRequest 
{ 
    public interface IAllCapsChecker 
    { 
     bool IsAllCaps(string request); 
    } 

    public interface ILetterBChecker 
    { 
     bool IsContainingB(string request); 
    } 

    public class Verifier 
    { 
     private readonly IAllCapsChecker m_AllCapsChecker; 
     private readonly ILetterBChecker m_LetterBChecker; 

     public Verifier(IAllCapsChecker allCapsChecker, ILetterBChecker letterBChecker) 
     { 
      m_AllCapsChecker = allCapsChecker; 
      m_LetterBChecker = letterBChecker; 
     } 

     public bool IsValid(string request) 
     { 
      return (!m_AllCapsChecker.IsAllCaps(request) && !m_LetterBChecker.IsContainingB(request)); 
     } 
    } 

    [TestClass] 
    public class IsValid 
    { 
     [TestMethod] 
     public void ShouldReturnFalseForInvalidStringBecauseContainsB() 
     { 
      var allCapsMock = new Mock<IAllCapsChecker>(); 
      allCapsMock.Setup(checker => checker.IsAllCaps("example")).Returns(true); 

      var letterBChecker = new Mock<ILetterBChecker>(); 
      letterBChecker.Setup(checker => checker.IsContainingB("example")).Returns(true); 

      var verifier = new Verifier(allCapsMock.Object, letterBChecker.Object); 

      Assert.IsFalse(verifier.IsValid("example")); 
     } 
    } 
} 
+0

這似乎表明,應該有一個且只有一個方法在一個類 - 是否正確? –

+0

我想說的是,一般而言,在單一課堂中應該只有一種** public **方法(參見[SRP](http://en.wikipedia.org/wiki/Single_responsibility_principle))。 – seldary

+0

有趣的 - 我認爲我的原始示例類驗證程序不違反SRP,它做了一件事(驗證字符串)。 另外,你所建議的方法似乎會導致一個類的所有內部方法被拉出多個類的單個公共方法 - 我相信這意味着我們放棄了類支持的內聚,也許還有更好的方法? –

4

我打字評論,但它必須是太大。我認爲你處於侵犯SRP的邊緣,但你肯定違反了開放/封閉的原則。如果您需要更改驗證字符串的方式,則需要修改驗證程序類。

我會處理這一點比@seldary會不同,但不是很多......

public interface IStringRule 
    { 
     bool Matches(string request); 
    } 

    public class AllCapsRule : IStringRule 
    { 
     public bool Matches(string request) 
     { 
      //implement 
     } 
    } 

    public class IsContainingBRule : IStringRule 
    { 
     public bool Matches(string request) 
     { 
      //implement 
     } 
    } 

    public class Verifier 
    { 
     private List<IStringRule> Rules; 

     public Verifier(List<IStringRule> rules) 
     { 
      Rules = rules; 
     } 

     public bool IsValid(string request) 
     { 
      return (!Rules.Any(x=>x.Matches(request) == false)); 
     } 
    } 

現在你verifer是開放的擴展,但封閉的修改。您可以根據需要添加儘可能多的新規則,並且實施不會更改。測試驗證程序非常簡單,只需傳入一些模擬字符串規則即可返回任意的真值和假值,並確保驗證程序返回適當的結果。

每個IStringRule都會像您一樣單獨進行測試。

+0

+1,我喜歡這種方法。確實遵守開放/封閉的原則,增加規則將是一項低摩擦的任務。 –