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

摘要:
最常见的例子是文件系统、线程、内存和时间等,我们使用桩对象来处理外部依赖问题。通过使用桩对象,无需涉及依赖项,即可直接对代码进行测试。
一、几个概念

1.什么是外部依赖

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

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

2.什么是桩对象

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

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

3.什么是重构

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

4.什么是接缝

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

二、解除依赖

抽象一个接口

namespaceLogAn.Interface
{
    public interfaceIExtensionManager
    {
        bool IsValid(stringfileName);
    }
}

实现接口的具体类

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

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

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

public classStubExtensionManager:IExtensionManager
{
    public bool IsValid(stringfileName)
    {
        return true;
    }
}

编写被测方法

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

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

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

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

namespaceLogAn
{
    public classLogAnalyzer
    {
        public bool IsValidLogFileName(stringfileName)
        {
            IExtensionManager mgr =newFileExtensionManager();
            returnmgr.IsValid(fileName);
        }
    }
}
三、在被测类中注入桩对象-构造函数

1.重写LogAnalyzer.cs

namespaceLogAn
{
    public classLogAnalyzer
    {
        privateIExtensionManager manager;
        /// <summary>
        ///在生产代码中新建对象
        /// </summary>
        publicLogAnalyzer()
        {
            manager = newFileExtensionManager();
        }
        /// <summary>
        ///定义可供测试调用的构造函数
        /// </summary>
        /// <param name="mgr"></param>
        publicLogAnalyzer(IExtensionManager mgr)
        {
            manager =mgr;
        }
        public bool IsValidLogFileName(stringfileName)
        {
            returnmanager.IsValid(fileName);
        }
    }
}
2.编写桩对象
public classStubExtensionManager : IExtensionManager
 {
     public boolShouldExtensionBeValid;
     public bool IsValid(stringfileName)
     {
         returnShouldExtensionBeValid;
     }
 }
3.编写测试方法
[TestFixture]
public classLogAnalyzerTest
{
    [Test]
    public voidIsValidFileName_validFileLowerCased_ReturnTrue()
    {
        StubExtensionManager myFakeManager = newStubExtensionManager();
        myFakeManager.ShouldExtensionBeValid = true;
        LogAnalyzer analyzer = newLogAnalyzer(myFakeManager);
        bool result = analyzer.IsValidLogFileName("haha.slf");
        Assert.IsTrue(result, "filename shoud be valid!");
    }
}

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

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

image

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

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

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

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

1.重写LogAnalyzer.cs

namespaceLogAn
{
    public classLogAnalyzer
    {
        privateIExtensionManager manager;
        /// <summary>
        ///在生产代码中新建对象
        /// </summary>
        publicLogAnalyzer()
        {
            manager = newFileExtensionManager();
        }

        /// <summary>
        ///允许通过属性设置依赖
        /// </summary>
        /// <param name="mgr"></param>
        publicIExtensionManager ExtensionManager
        {
            get { returnmanager; }
            set { manager =value; }
        }
        public bool IsValidLogFileName(stringfileName)
        {
            returnmanager.IsValid(fileName);
        }
    }
}
2.编写桩对象类
public classStubExtensionManager : IExtensionManager
{
    public boolShouldExtensionBeValid;
    public bool IsValid(stringfileName)
    {
        returnShouldExtensionBeValid;
    }
}
3.编写测试方法
[TestFixture]
public classLogAnalyzerTest
{
    [Test]
    public voidIsValidFileName_validFileLowerCased_ReturnTrue()
    {
        StubExtensionManager myFakeManager = newStubExtensionManager();
        myFakeManager.ShouldExtensionBeValid = true;
        LogAnalyzer analyzer = newLogAnalyzer();
        analyzer.ExtensionManager =myFakeManager;
        bool result = analyzer.IsValidLogFileName("haha.slf");
        Assert.IsTrue(result, "filename shoud be valid!");
    }
}
五、在被测类中注入桩对象-工厂方法

1.编写LogAnalyzer.cs

namespaceLogAn
{
    public classLogAnalyzer
    {
        privateIExtensionManager manager;
        /// <summary>
        ///在生产代码中使用工厂
        /// </summary>
        /// <param name="mgr"></param>
        publicLogAnalyzer()
        {
            manager =ExtensionManagerFactory.Create();
        }
        public bool IsValidLogFileName(stringfileName)
        {
            returnmanager.IsValid(fileName);
        }
    }
}

2.编写测试方法

[TestFixture]
public classLogAnalyzerTest
{
    [Test]
    public voidIsValidFileName_validFileLowerCased_ReturnTrue()
    {
        StubExtensionManager myFakeManager = newStubExtensionManager();
        myFakeManager.ShouldExtensionBeValid = true;
        //把桩对象赋给工厂类
ExtensionManagerFactory.SetManager(myFakeManager);
        LogAnalyzer analyzer = newLogAnalyzer();
        bool result = analyzer.IsValidLogFileName("haha.slf");
        Assert.IsTrue(result, "filename shoud be valid!");
    }
}

免责声明:文章转载自《读书笔记-单元测试艺术(三)-使用桩对象解除依赖》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇windows linux子系统(Ubuntu)ip地址一文教您如何通过 Docker 快速搭建各种测试环境(Mysql, Redis, Elasticsearch, MongoDB下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

JavaScript 的继承与多态

本文先对es6发布之前javascript各种继承实现方式进行深入的分析比较,然后再介绍es6中对类继承的支持以及优缺点讨论。最后介绍了javascript面向对象编程中很少被涉及的“多态”,并提供了“运算符重载”的思路。本文假设你已经知道或了解了js中原型、原型链的概念。 es6之前,javascript本质上不能算是一门面向对象的编程语言,因为它对...

使用Intent实现Activity的显式跳转

【正文】 这里以按钮实现活动跳转为例,为实现这个功能,我们需要三个步骤: 1.点击按钮才发生页面跳转,因此,第一步我们先要找到要点击的按钮 如何拿到按钮对象呢?通过资源id,前面我们提到过,在R.id.xxx 中会有我们的资源id,但button按钮是在layout 中创建的,系统不会为其创建资源id,所以我们需要在layout 设置 button 时自己...

ES6-10笔记(class类)

class类 Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。摘自阮一峰老师语录 class声明 ES5的JavaScript中只有对象,想要模拟类去生成一个对象实例,只能通过定义一个构造函数,然后通过new操作符来完...

.NET 基础知识

.net程序基本编写、执行流程(c#)       1>编写c#代码,保存为.cs文件。       2>通过csc.exe程序来将.cs文件编译为.net程序集(.exe或.dll)。此时的exe或dll并不是机器码(cpu不可理解)。【>csc /out:c:a.exe c:program.cs】   C:WindowsMicroso...

软件集成、确认和系统测试方法

引言 软件测试按测试用例设计(TEST CASE DESIGN)方法分为白盒测试(WHITE-BOX TESTING)和黑盒测试(BLACK-BOX TESTING)。 按测试过程或测试策略,软件测试分为单元测试(UNIT TESTING),集成测试(INTEGRATION TESTING〕,确认测试(VALIDATION TESTING〕和系统测试(SY...

用C#实现Web代理服务器2

三、C#实现Web代理服务程序   经过了上面的介绍,我想大家对代理服务应该有了一个基本的认识,下面就让我们通过一个实例来深入体会一下如何用C#实现Web代理服务。Web代理服务的功能顺序是这样的:   (1)侦听端口,等待客户端浏览器发送来的Web请求信息。   (2)接收到客户端Web请求信息后,解析出目标Web服务器的地址,并创建一个Socket实例...