2012-10-09 189 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,我喜欢这种方法。确实遵守开放/封闭的原则,增加规则将是一项低摩擦的任务。 –