单元测试——Mock

【背景】

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

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


【简介】

         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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值