单元测试——Mock

来源:互联网 发布:Linux下重启apache 编辑:程序博客网 时间:2024/05/22 13:37

【背景】

        单元测试的目标是一次只验证一个方法,但是倘若遇到这样的情况:某个方法依赖于其他一些难以操控的东西,诸如网络、数据库,甚至是微软最新的股票价格,那我们该怎么办?

        要是你的测试依赖于系统的其他部分,甚至是系统的多个其他部分呢?在这种情况下,倘若不小心,你最终可能会发现自己几乎初始化了系统的每个组件,而这只是为了给一个测试创造足够的运行环境让它们可以运行起来。忙乎了大半天,看上去我们好像有点违背了测试的初衷了。这样不仅仅消耗时间,还给测试过程引入了大量的耦合因素,比如说,可能有人兴致冲冲地改变了一个接口或者数据库的一张表,突然,你那卑微的单元测试的神秘的挂掉了。在这种情况发生几次之后,即使是最有耐心的开发者也会泄气,甚至最终放弃所有的测试,那样的话后果就不能想像了。


【简介】

         mock就是在测试过程中,对于某些不容易构造或者 不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

        在实际的面向对象软件设计中,我们经常会碰到这样的情况,我们在对现实对象进行构建之后,对象之间是通过一系列的接口来实现。这在面向对象设计里是最自然不过的事情了,但是随着软件测试需求的发展,这会产生一些小问题。举个例子,用户A现在拿到一个用户B提供的接口,他根据这个接口实现了自己的需求,但是用户A编译自己的代码后,想简单模拟测试一下,怎么办呢?这点也是很现实的一个问题。我们是否可以针对这个接口来简单实现一个代理类,来测试模拟,期望代码生成自己的结果呢?

         幸运的是,有一种测试模式可以帮助我们:mock对象。Mock对象也就是真实对象在调试期的替代品。

何时使用Mock对象

1)    真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)

2)    真实对象很难被创建(比如具体的web容器)

3)    真实对象的某些行为很难触发(比如网络错误)

4)    真实情况令程序的运行速度很慢

5)    真实对象有用户界面

6)    测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)

7)    真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)

如何使用Mock对象

1、使用一个接口来描述这个对象
2、为产品代码实现这个接口

3、以测试为目的,在mock对象中实现这个接口


【实例】

下边我们以一个Demo:其中有一个Reminder()的方法,如果在下午5点之后调用该方法,就会播放对应的音频,我们需要测试其中的Reminder()方法。

这是整个Demo的总体架构:



1、创建产品代码类

首先,创建一个接口:Environmental

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Clock{    //接口    public interface Environmental    {        //接口中的定义的方法:Now,返回类型为DataTime         DateTime Now { get; }       //播放音频         void PlayWavFile(string fileName);    }}

在产品SystemEnvironment类中,继承接口,实现接口中的方法

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Clock{    //SystemEnvironment类继承接口    public class SystemEnvironment:Environmental     {        // //定义一个私有的变量:when,返回类型为DateTime        private DateTime when;        //定义了一个构造函数SystemEnvironment,并传入了一个when对象,when的类型为DateTime类型        public SystemEnvironment(DateTime when)        {            this.when = when;                }        //具体实现接口中的方法        public DateTime Now {            get {                return DateTime.Now;            }               }        //重写playWavFile方法        public void PlayWavFile(string fileName)        {            throw new NotImplementedException();        }    }}

定义一个Checker类,来调用接口方法

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Clock{    //定义一个Checker类,来调用接口中的方法    public class Checker    {        Environmental env;        public Checker(Environmental env)         {            this.env = env;        }                public void Reminder()         {            DateTime Now = env.Now;            if (Now.Hour >= 17)            {                env.PlayWavFile("***.wav");            }        }    }}

下边是一个控制台类

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace Clock{    public class Program    {        //控制台应用程序        static void Main(string[] args)        {         }        //定义一个QuittingTime方法        public void QuittingTime()         {            DateTime when = new DateTime(2015, 3, 6, 15, 00, 0);            SystemEnvironment se = new SystemEnvironment(when);            Checker checker = new Checker(se);            checker.Reminder();        }    }}

2、然后编写测试类TestClock(需要添加对nunit.framework的引用)

首先,通过创建Mock测试类MockSystemEnvironment类,来继承接口Environmental 。MockSystemEnvironment类就是用来代替产品代码中的SystemEnvironment类。

using System;using System.Collections.Generic;using System.Linq;using System.Text;using Clock;namespace TestClock{    public class MockSystemEnvironment:Environmental     {        //定义一个私有的变量:currentTime,返回类型为DateTime        private DateTime currentTime;        //定义了一个构造函数MockSystemEnvironment,并传入了一个when对象,when的类型为DateTime类型        public MockSystemEnvironment(DateTime when) {            //将对象when的值赋给上边定义的私有变量currentTime            currentTime = when;        }        //实现方法Now        public DateTime Now {            get {                return currentTime;            }        }        //定义一个IncrementMinutes方法,作用是:给currentTime这个时间,加上指定的分钟数,就可以控制Mock对象所返回的日期和时间        public void IncrementMinutes(int minutes) {            currentTime = currentTime.AddMinutes(minutes);        }        private bool soundWasPlayed = false;        public void PlayWavFile(string fileName) {            soundWasPlayed = true;        }        public bool CheckAndResetSound() {            bool value = soundWasPlayed;            soundWasPlayed = false;            return value;        }    }}

然后,编写具体的测试代码

using System;using System.Collections.Generic;using System.Linq;using System.Text;using NUnit.Framework;using Clock;namespace TestClock{    [TestFixture ]    public  class TestChecker    {        [Test]        public void QuittingTime()         {            //创建了一个DateTime对象:When,并给其赋值为2015, 3, 6, 15, 00, 0            DateTime when = new DateTime(2015, 3, 6, 15, 00, 0);            MockSystemEnvironment env = new MockSystemEnvironment(when);                 Checker checker = new Checker(env);            //在16:45时,闹钟不响            checker.Reminder();            Assert.IsFalse(env.CheckAndResetSound(), "16:45");            //现在,在16:45的基础上加上15分钟            env.IncrementMinutes(15);            checker.Reminder();            Assert.IsTrue(env .CheckAndResetSound (),"17:00");        }    }}

PS:在具体的测试中,需要根据实际设定时间。

【结语】

    
    上边我们通过一个简单的Demo来介绍了一下Mock,Demo很简单,但是Mock的复杂之处在于如何在实际项目中去使用。文章中只是介绍了Mock的冰山一角,更多的东西还需要我们去研究、探索。
    
    在项目中如何使用Mock,还需要我们多实践,多动手去做,实践出真知!
        


0 0