• 读书笔记-单元测试艺术(三)-使用桩对象解除依赖


    一、几个概念

    1.什么是外部依赖

    外部依赖是指在系统中代码与其交互的对象,而且无法对其做人为控制。

    最常见的例子是文件系统、线程、内存和时间等,我们使用桩对象来处理外部依赖问题。

    2.什么是桩对象

    桩对象是对系统中现有依赖的一个替代品,可人为控制。

    通过使用桩对象,无需涉及依赖项,即可直接对代码进行测试。

    3.什么是重构

    重构是指不影响已有功能而改变代码设计的一种行为

    4.什么是接缝

    接缝是指代码中可以插入不同功能(如桩对象类)的地方。

    二、解除依赖

    抽象一个接口

    namespace LogAn.Interface
    {
        public interface IExtensionManager
        {
            bool IsValid(string fileName);
        }
    }

    实现接口的具体类

    namespace LogAn.Implement
    {
        public class FileExtensionManager:IExtensionManager
        {
            public bool IsValid(string fileName)
            {
                if (string.IsNullOrEmpty(fileName))
                {
                    throw new ArgumentException("No filename provided!");
                }
                if (!fileName.EndsWith(".SLF"))
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
        }
    }

    编写一个实现该接口的桩对象类

    无论文件的扩展类是什么,这个桩对象类永远返回true

    public class StubExtensionManager:IExtensionManager
    {
        public bool IsValid(string fileName)
        {
            return true;
        }
    }

    编写被测方法

    现有一个接口和两个实现该接口的类,但被测类还是直接调用“真对象”;

    这个时候我们需要在代码中引入接缝,以便可以使用桩对象

    在被测试类中注入桩对象的实现;

    在构造函数级别上接收一个接口;

    namespace LogAn
    {
        public class LogAnalyzer
        {
            public bool IsValidLogFileName(string fileName)
            {
                IExtensionManager mgr =new FileExtensionManager();
                return mgr.IsValid(fileName);
            }
        }
    }

    三、在被测类中注入桩对象-构造函数

    1.重写LogAnalyzer.cs

    namespace LogAn
    {
        public class LogAnalyzer
        {
            private IExtensionManager manager;
            /// <summary>
            /// 在生产代码中新建对象
            /// </summary>
            public LogAnalyzer()
            {
                manager = new FileExtensionManager();
            }
            /// <summary>
            /// 定义可供测试调用的构造函数
            /// </summary>
            /// <param name="mgr"></param>
            public LogAnalyzer(IExtensionManager mgr)
            {
                manager = mgr;
            }
            public bool IsValidLogFileName(string fileName)
            {
                return manager.IsValid(fileName);
            }
        }
    }
    2.编写桩对象
    public class StubExtensionManager : IExtensionManager
     {
         public bool ShouldExtensionBeValid;
         public bool IsValid(string fileName)
         {
             return ShouldExtensionBeValid;
         }
     }
    3.编写测试方法
    [TestFixture]
    public class LogAnalyzerTest
    {
        [Test]
        public void IsValidFileName_validFileLowerCased_ReturnTrue()
        {
            StubExtensionManager myFakeManager = new StubExtensionManager();
            myFakeManager.ShouldExtensionBeValid = true;
            LogAnalyzer analyzer = new LogAnalyzer(myFakeManager);
            bool result = analyzer.IsValidLogFileName("haha.slf");
            Assert.IsTrue(result, "filename shoud be valid!");
        }
    }

    4.构造函数注入方式存在的问题

    如果被测代码需要多个桩对象才能正常工作,就需要增加更多的构造函数,而造成很大的困扰,甚至降低代码的可读性和可维护性

    image

    5.何时使用构造函数注入方式

    使用构造函数的方式,可以很好的告知API使用者:“这些参数是必须的,新建这个对象时必须传入所有参数”

    如果想要这些依赖变成可选的,可以使用属性注入

    四、在被测类中注入桩对象-属性注入

    1.重写LogAnalyzer.cs

    namespace LogAn
    {
        public class LogAnalyzer
        {
            private IExtensionManager manager;
            /// <summary>
            /// 在生产代码中新建对象
            /// </summary>
            public LogAnalyzer()
            {
                manager = new FileExtensionManager();
            }
    
            /// <summary>
            /// 允许通过属性设置依赖
            /// </summary>
            /// <param name="mgr"></param>
            public IExtensionManager ExtensionManager
            {
                get { return manager; }
                set { manager = value; }
            }
            public bool IsValidLogFileName(string fileName)
            {
                return manager.IsValid(fileName);
            }
        }
    }
    2.编写桩对象类
    public class StubExtensionManager : IExtensionManager
    {
        public bool ShouldExtensionBeValid;
        public bool IsValid(string fileName)
        {
            return ShouldExtensionBeValid;
        }
    }
    3.编写测试方法
    [TestFixture]
    public class LogAnalyzerTest
    {
        [Test]
        public void IsValidFileName_validFileLowerCased_ReturnTrue()
        {
            StubExtensionManager myFakeManager = new StubExtensionManager();
            myFakeManager.ShouldExtensionBeValid = true;
            LogAnalyzer analyzer = new LogAnalyzer();
            analyzer.ExtensionManager = myFakeManager;
            bool result = analyzer.IsValidLogFileName("haha.slf");
            Assert.IsTrue(result, "filename shoud be valid!");
        }
    }

    五、在被测类中注入桩对象-工厂方法

    1.编写LogAnalyzer.cs

    namespace LogAn
    {
        public class LogAnalyzer
        {
            private IExtensionManager manager;
            /// <summary>
            /// 在生产代码中使用工厂
            /// </summary>
            /// <param name="mgr"></param>
            public LogAnalyzer()
            {
                manager = ExtensionManagerFactory.Create();
            }
            public bool IsValidLogFileName(string fileName)
            {
                return manager.IsValid(fileName);
            }
        }
    }

    2.编写测试方法

    [TestFixture]
    public class LogAnalyzerTest
    {
        [Test]
        public void IsValidFileName_validFileLowerCased_ReturnTrue()
        {
            StubExtensionManager myFakeManager = new StubExtensionManager();
            myFakeManager.ShouldExtensionBeValid = true;
            //把桩对象赋给工厂类
            ExtensionManagerFactory.SetManager(myFakeManager);
            LogAnalyzer analyzer = new LogAnalyzer();
            bool result = analyzer.IsValidLogFileName("haha.slf");
            Assert.IsTrue(result, "filename shoud be valid!");
        }
    }
  • 相关阅读:
    没提供编码格式,读文件时要怎么推测文件具体的编码
    Spring系列.@EnableRedisHttpSession原理简析
    程序员必备画图技能之——流程图
    痞子衡嵌入式:第一本Git命令教程(7.1)- 清理之缓存(stash)
    痞子衡嵌入式:第一本Git命令教程(6)- 日志(log/reflog/gitk)
    痞子衡嵌入式:第一本Git命令教程(5)- 提交(commit/format-patch/am)
    痞子衡嵌入式:第一本Git命令教程(4)- 转移(add/rm/mv)
    痞子衡嵌入式:第一本Git命令教程(3)- 变动(status/diff)
    痞子衡嵌入式:第一本Git命令教程(2)- 连接(remote/clone)
    痞子衡嵌入式:第一本Git命令教程(1)- 准备(init/config/.gitignore)
  • 原文地址:https://www.cnblogs.com/kimisme/p/5565293.html
Copyright © 2020-2023  润新知