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

コードについては、次のような 2 つの内部メソッドをカバーする単体テストを作成します。

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」メソッドのテストが複雑になります。

すべての内部メソッドのインターフェイスを作成してから、テスト用にスタブ化する必要がありますか、それともより適切な方法がありますか?

4

2 に答える 2

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

これで、ベリファーは拡張に対してオープンですが、変更に対してクローズされます。好きなだけ新しいルールを追加でき、実装は変更されません。ベリファイアのテストは、任意の true 値と false 値を返すいくつかのモック文字列ルールを渡し、ベリファイアが適切な結果を返すことを確認するのと同じくらい簡単です。

各 IStringRule は、行っていたように個別にテストされます。

于 2012-10-12T20:25:46.927 に答える
0

きちんとした方法は次のようになります:

  1. クラスを3つのクラスにリファクタリングします。この場合はVerifierメソッドごとに1つです:Verifier、、。AllCapsCheckerLetterBChecker
  2. それに応じてテストクラスをリファクタリングします-これで3つのテストクラスがあるはずです。
  3. Verifier2つの内部ロジッククラスをお気に入りのDIメソッドを使用して注入します。
  4. VerifierTestsクラスには、2つの依存関係を配置して注入しVerifierロジックのみ(この場合は論理演算子のみ)をテストVerifierする必要があります。

Verifierここでは、アイデアを得るために、クラスとクラスの適応を見つけることができますVerifierTests(私はここで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"));
        }
    }
}
于 2012-10-09T07:36:26.300 に答える