簡體   English   中英

用經過測試的內部方法測試公共方法

[英]unit testing public methods with tested internal methods

我發現自己陷入了很多這種模式,我編寫了一個由非常小的方法組成的類,這些方法完全由我的單元測試來完成。 然后我發現我需要構建另一個調用這些方法的方法,我必須為此編寫一個更復雜的單元測試 - 一個簡單的例子就是說明性的:

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

顯然對於這個例子來說,這並不算太糟糕,但是當有很多內部方法並且輸入配置非常簡單時,測試我的公共“Is This Input Valid”方法會變得復雜。

我應該為我的所有內部方法創建一個接口,然后將其存根以進行測試,還是有更簡潔的方法?

我正在輸入評論,但它太大了。 我認為你處於違反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可以擴展,但是接近修改。 您可以根據需要添加任意數量的新規則,但實現不會更改。 測試驗證程序就像傳入一些返回任意true和false值的模擬字符串規則一樣簡單,並確保驗證程序返回適當的結果。

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

更簡潔的方式如下:

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

在這里你可以找到VerifierVerifierTests類的改編,只是為了得到這個想法(我在這里使用了Moq):

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM