一、伪对象

  1、简单的业务场景

  有一个文件监控程序,有一个方法用来检查文件名的合法性,检查过程中,如文件名不合法,需调用远程web服务记录日志,如远程web服务调用发生异常,发送邮件到指定收件人(类似这句话描述的业务场景在实际开发中数不甚数)。

  2、什么是伪对象

  上面所举的业务场景,文件监控主体程序是需要自己实现的,而远程web服务以及邮件服务都是监控程序所依赖的外部服务,在我们开发测试的时候可能还不能直接调用,或者调用服务代价太大(想想为什么代价比较大?),这个时候我们如何进行测试呢?

  答案是构造伪对象(fake object)来代替外部依赖的服务,伪对象是桩对象和模拟对象的统称。

  在我们这个业务场景中,显然需要构造两个伪对象,即常用的两个基础服务:日志和邮件服务。

  (1)桩对象(stub)

  定义:桩对象是对系统中现有依赖项的一个替代品,可人为控制,通过使用桩对象,无需涉及依赖项,即可直接对代码进行测试。

  和被测试对象的关系:

  从上图可知,桩对象在单元测试中是不会被下断言的。

  (2)模拟对象(mock)

  定义:模拟对象用来决定一个单元测试是通过还是失败。它通过验证被测试对象和伪对象之间是否进行预期的交互来判断。

  和被测试对象的关系:

  从上图可知,模拟对象在单元测试中必然要被下断言。

  综上所述,我们可以分析在当前所举的场景中,如果不对web服务伪对象验证测试结果(即不对它进行断言),而只是用来确保测试正确运行,那么日志服务是一个桩对象;如果我们需要针对邮件服务来做断言,验证它是否被正确调用,那么邮件服务是一个模拟对象。

  下面手动创建两个伪对象,其中日志服务为桩对象,邮件服务为模拟对象,代码如下:

  a、LogService

using System;

namespace MonitorService
{
    public interface ILogService
    {
        Exception ExToThrow { get; set; }

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="msg"></param>
        void AppendLog(string msg);
    }

    public class StubLogService : ILogService
    {
        public Exception ExToThrow { get; set; }

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="msg"></param>
        public void AppendLog(string msg)
        {
            if (ExToThrow != null)
            {
                throw ExToThrow;
            }
            //throw new NotImplementedException("fake exception");
        }
    }
}