• TDD 强迫你 Program to Interface


    还是接上次的内容,继续测试Dollar class

    先在有个新的需求--在使用Times方法之前,必须要做用户的身份验证,有权限的人才可以用这个方法,反之则不行。(后面称 需求(1)

    在做完设计后,我们界定有个class 叫LoginChecker中的方法CheckPass将用来做权限的审查,返回值为bool型,如果有权限返回True, 反之为false。

    首先看一下 如果不用TDD 我们脑中第一反应的功能代码实现,应该会是下面的样子--我们去new 了一个LoginChecker的实例,然后调用CheckPass的方法。

            //method to be tested
            public int Times(int multiplier)
            {
                var checker = new LoginChecker();
                if (checker.CheckPass())
                {
                    return this.amount *= multiplier;
                }
                return 0;           
            }

    这样我们Dollar class就紧密依赖LoginChecker class了。 如果我来实现 times方法,我可能会有以下两种处理方式:1.实现功能我自己Times的功能,但不做测试(理由是:CheckPass 还没写好,我怎么测试啊,测了也没用,可能CheckPass会抛异常)2. 等CheckPass写完了,我再写Times方法。你是否有嗅出这两种方式写出来的测试都很像集成测试?!TDD是讲究Isolation(独立,隔离)的。这里你要测的就是Times方法,其它所有的dependency(依赖)都应该用Stub(mock,fake找一个你喜欢的词,不过他们是有区别的)来替代。现在的问题是“这个怎么测呢?”一般有这个问题出现时,你的第一反应应该是 “设计是否有问题?为什么要绑定到一个特定的class?”。我们来看看怎么解决,把开始的问题换个方式问“能否不绑定到特定的class?”,可以的 那就是要把功能抽象出来,抽象成Abstract class或者Interface咯。那我们就抽象出一个IChecker的Interface吧

        public interface IChecker
        {
            bool CheckPass();
        }

    让LoginChecker实现一下,不用写功能哦。

       public class LoginChecker : IChecker
        {
            #region IChecker Members

            public bool CheckPass()
            {
                throw new NotImplementedException();
            }

            #endregion
        }

    下面看看如果光实现 Dollar class的话,实现会是什么样的


        public class Dollar
        {
            int amount;
            IChecker checker = null;

            ////method to be tested
            public int Times(int multiplier)
            {
                if (checker.CheckPass())
                {
                    return this.amount *= multiplier;
                }
                return 0;
            }

            #endregion
        }

    从上面代码中,有人肯定要问了,checker这个Instance 怎么来呢?这里可以用到dependency Injection了,一般depency Injection有两种 分别为contructor和setter, 当然factory 也是可以实现的,这里我们就用contructor injection来实现吧。

    看看现在的 真正实现代码是什么?其实应该是以下这个样子,因为我们还没有 需求(1)。只是一番思考之后这个需求引导我们 应该Program to Interface然后用Depency Injection,这样才好测。


        public class Dollar
        {
            int amount;
            IChecker checker = null;


            public Dollar(int amount, IChecker checker)
            {
                this.amount = amount;
                this.checker = checker;
            } 


            ////method to be tested
            public int Times(int multiplier)
            {

               return this.amount *= multiplier;

            }

            #endregion
        }

    那我们现在开始写需求(1)的测试代码,比方说先测一个简单的:如果Check没过,就总是返回 0

    第一步,我们先要写一个stub,让它来替换掉,LoginChecker的CheckPass方法,有了stub你就可以完全控制你的测试了,即使LoginChecker还根本没实现

           class LoginCheckerStub : IChecker
            {

                #region IChecker Members

                public bool CheckPass()
                {
                    return false;
                }

                #endregion
            }

    真正的test case

            ///// <summary>
            /////A test for times
            /////</summary>
            [TestMethod()]
            public void TesttimesWithChecker()
            {
                int amount = 5;
                IChecker chcker = new LoginCheckerStub(); // new一个stub
                Dollar target = new Dollar(amount, chcker); //用stub来控制你的测试,返回你想要的值
                int actual = target.Times(10);
                Assert.AreEqual(0, actual);
            }
    真正的实现代码

            ////method to be tested
            public int timesWithChekerFalseReturn0(int multiplier)
            {
                if (!checker.CheckPass())
                {
                   return 0;
                }           
            }

    如果checker过了,那5Times10应该返回50

    第一步,我们先要写一个stub,让它来替换掉,LoginChecker的CheckPass方法,有了stub你就可以完全控制你的测试了,即使LoginChecker还根本没实现

           class LoginCheckerStub : IChecker
            {

                #region IChecker Members

                public bool CheckPass()
                {
                    return true;
                }

                #endregion
            }

    真正的test case

            ///// <summary>
            /////A test for times
            /////</summary>
            [TestMethod()]
            public void TesttimesWithCheckerTrueToCheck()
            {
                int amount = 5;
                IChecker chcker = new LoginCheckerStub(); // new一个stub
                Dollar target = new Dollar(amount, chcker); //用stub来控制你的测试,返回你想要的值
                int actual = target.Times(10);
                Assert.AreEqual(50, actual);
            }
    真正的实现代码

            ////method to be tested
            public int timesWithCheker(int multiplier)
            {
                if (!checker.CheckPass())
                {
                   return 0;
                }
               return this.amount *= multiplier;
               
            }

     那可能有些人会说,我就是不想设计什么接口,用什么依赖注入,我还能用TDD 来写我的实现吗?回答是可以的,不过要用到额外的工具,下面用Typemock实现一下,如果实现绑定某一特定的class 测试代码该怎么写。这段测试对应到文章开头的实现。


            /// <summary>
            /// Test with Typemock
            /// </summary>
            [TestMethod]
            public void TesttimesWithCheckerByTypemock()
            {
                MockManager.Init();
                Mock CheckerMock = MockManager.MockAll(typeof(LoginChecker));
                CheckerMock.ExpectAndReturn("checkPass", true);


                int amount = 5; // TODO: Initialize to an appropriate value
                Dollar target = new Dollar(amount); // TODO: Initialize to an appropriate value
                int actual = target.Times(2);
                Assert.AreEqual(10, actual);
                MockManager.ClearAll();
            }

    本文介绍了,TDD会引导你面向接口编程,思考你的设计是否合理,是否高耦合。当然也介绍了 代码一定要高耦合情况下,怎么测试(TypeMock),后面将介绍测试中比较头痛的的问题 -- 处理系统时间和外部资源

  • 相关阅读:
    1026: C语言程序设计教程(第三版)课后习题7.5
    1024: C语言程序设计教程(第三版)课后习题7.3
    1023: C语言程序设计教程(第三版)课后习题7.2
    1022: C语言程序设计教程(第三版)课后习题7.1
    1021: C语言程序设计教程(第三版)课后习题6.11
    1020: C语言程序设计教程(第三版)课后习题6.10
    1019: C语言程序设计教程(第三版)课后习题6.9
    1018: C语言程序设计教程(第三版)课后习题6.8
    1017: C语言程序设计教程(第三版)课后习题6.7
    mac电脑很卡,如何在命令行查看当前电脑中的运行状态
  • 原文地址:https://www.cnblogs.com/michael703/p/2054783.html
Copyright © 2020-2023  润新知