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

Posted Kimisme

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读书笔记-单元测试艺术-使用桩对象解除依赖相关的知识,希望对你有一定的参考价值。

一、几个概念

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

以上是关于读书笔记-单元测试艺术-使用桩对象解除依赖的主要内容,如果未能解决你的问题,请参考以下文章

读书笔记-单元测试艺术-单元测试的基本知识

单元测试-桩对象

《单元测试的艺术》读书笔记----优秀单元测试的特性

读书笔记

JavaScript DOM编程艺术 读书笔记 第3章 DOM-文档对象模型

读书笔记三