上一篇中介绍了如何简单地测试Activity的行为,我们只是使用VSTS的单元测试工具,捕获了一个期待的异常,这样简单的行为测试当然是不能满足我们的需求的,我们要进一步测试Activity的更多,更复杂的行为。
     比如我们有一个LogActivity 负责写日志,任何调用它的结果等同于调用一个ILog接口的一个Write()方法。我们就要写一个单元测试,测试是否LogActivity的调用等同于对ILog接口的Write()方法的调用。
     有的人不禁要问了,这样的测试如何做呢?我们的测试工具只支持Assert操作,难道我要去Check日志文件,看看两个日志是否相等吗?答案是否定的,我们不会这么做,因为这样还属于“状态测试”的范畴,而不是“行为测试”。
     还好有人想到了这一点,提供给我们现成的工具,这就是Mock Object Framework。Mock Object Framework基于单元测试中的这样几个问题:
     1.在一个庞大的系统中,成百上千个类共同协调完成一个功能。往往一个调用的背后,是几十个类在共同工作,而且很多类的创建很耗费资源和时间。在这样的情况下,运行一个测试的代价可想而知,何况我们每次对代码做一次改动,就要运行单元测试呢。

2.很多代码都需要调用外部资源,比如,读取配置文件,读取数据库,调用远程的Web Service等等。这样的操作很费时间,最主要的是依赖外部的资源。如果外部资源处了问题,测试将无法进行下去。

3.对系统的行为无法准确捕获。一个简单的调用,系统究竟调用了那些类的那个方法,调用了几次,做了几次查询,修改,等等,一系列的操作都无法准确测试,难以保证单元测试的严谨性。

基于以上几点考虑(当然不是全部的),Mock Object Framework出现了。它解决了这些问题,

所以成为了TDD的必备武器!

Mock Object Framework的主要功能:

1.Mock Object---顾名思义,就是模仿对象。正是由于单元测试中庞大的对象群的创建太耗费资源和时间,所以我们使用Mock Object来代替那些对象。Mock Object是轻量级的,代价很低,所以大大减少单元测试的开销。

2.对于一些需要访问外部资源的对象,Mock可以用硬编码代替对外部资源的访问。从而提高效率。

3.Mock Object Framework可以准确地记录所模仿的对象的一切操作,使得我们可以准确地测试对象的行为是否符合要求。

4.Mock Object Framework可以模仿接口,抽象类,或者尚未实现的类,从而加快开发速度。

关于Mock Object Framework,大家可以去找一些资料。
     这里我们使用的是Rhino Mock,大家可以去这里看Rhino Mock的简单介绍:http://stephenwalther.com/blog/archive/2008/03/23/tdd-introduction-to-rhino-mocks.aspx
     下面开始我们的单元测试:
     在前文的工程中,加入一个名叫的Activity,继承自BaseEmployeeActivity。定义三个属性:

StoreNewAcmeEmployee
        public static readonly DependencyProperty
                NewEmployeeProperty = DependencyProperty.Register("NewEmployee",
                    typeof(AcmeEmployee), typeof(StoreNewAcmeEmployee));

        public AcmeEmployee NewEmployee
        {
            get { return (AcmeEmployee)GetValue(NewEmployeeProperty); }
            set { SetValue(NewEmployeeProperty, value); }
        }

        public IEmployeeRepository EmployeeDataStore { get; set; }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext 

executionContext)
        {

            return ActivityExecutionStatus.Closed;
        }

我们设定这个Activity的功能是:根据给定的参数,创建一个NewEmployee对象,把它存储在EmployeeDataStore对象中。我们先不实现Execute()方法。注意:EmployeeDataStore会在后面使用依赖注入赋值。
     为了测试这个Activity,我们如法炮制,在测试工程中加一个新的Activity:StoreNewAcmeEmployee_Accessor,继承自StoreNewAcmeEmployee。并Wrap它的Execute方法。
     接下来添加一个测试类:StoreNewAcmeEmployee_ActivityShould。记得引用Rhino.Mocks库。
     先把代码贴出来:

StoreNewAcmeEmployee_ActivityShould
using System;
using AcmeCorp.DomainLibrary.AcmeEmployeeDomain;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NewEmployeeWFLibrary.Activities;
using NewEmployeeWFLibrary_Test.Helpers;
using Rhino.Mocks;//引用Rhino Mock库

namespace NewEmployeeWFLibrary_Test.Activities
{
    [TestClass]
    public class StoreNewAcmeEmployee_ActivityShould
    {
        //被Mock的对象,将被注入到StoreNewAcmeEmployee的EmployeeDataStore属性。
        private IEmployeeRepository _mockEmployeeRepository;
        
        //Mock功能类
        private MockRepository _mocker;

        [TestInitialize]
        public void TestInitializer()
        {
            //初始化Mock功能类           
            _mocker = new MockRepository();

            //创建Mock对象
            _mockEmployeeRepository = _mocker.CreateMock<IEmployeeRepository>();
        }

        [TestMethod]
        public void CallAddOnTheRepo()
        {
            //记录Mock对象的操作
            using (_mocker.Record())
            {
                _mockEmployeeRepository.Add(null);
                LastCall.IgnoreArguments();//忽略上面的null参数,即传什么参数都一样


            }

            //回放Mock对象的操作,Rhino Mock会自动检验对被Mock的对

象_mockEmployeeRepository的操作与上面记录的是否一致
            //如果不一致,就会自动抛出异常
            using (_mocker.Playback())
            {
                using (StoreNewAcmeEmployee_Accessor activity = new 

StoreNewAcmeEmployee_Accessor())
                {
                    SetActivityProperties(activity, Mother.FIRST_NAME, 

Mother.LAST_NAME, Mother.EMAIL,
                                          _mockEmployeeRepository);
                    activity.Execute_Accessor(null);
                }
            }
        }
        

        private void SetActivityProperties(StoreNewAcmeEmployee activity, string 

firstName, string lastName,
                                           string email, IEmployeeRepository 

employeeRepo)
        {
            activity.FirstName = firstName;
            activity.LastName = lastName;
            activity.Email = email;
            activity.EmployeeDataStore = employeeRepo;//注入被Mock对象
        }
    }
}

在上面的代码中,下面一句比较重要
     _mockEmployeeRepository = _mocker.CreateMock<IEmployeeRepository>();
     这一句创建了针对接口IEmployeeRepository的Mock对象。注意:在我的工程里,名没有实现这个接口。这就是Mock的特点,可以不用实现功能就能测试。可谓针对接口测试吧!:)
     在接下来的测试方法中,首先使用_mocker.Record()记录对Mock对象的操作。在_mocker.Record()的Unsing块中的所有操作,都是我们期待的Mock对象的“行为”。而_mocker.Playback()块则是对Activity的实际测试过程,在这个过程中,必须和Record块对_mockEmployeeRepository的操作一致------也就是说,也调用了它的Add()方法。如果一致,测试通过,否则Rhino Mock会抛出异常,测试失败!
     我们运行一下测试,失败!因为还没有为StoreNewAcmeEmployee的Execute()方法添加逻辑。

回过头来实现StoreNewAcmeEmployee的Execute()方法:

Execute
protected override ActivityExecutionStatus Execute(ActivityExecutionContext

executionContext)
        {
            NewEmployee = new AcmeEmployee(Guid.NewGuid().ToString(), FirstName,

LastName, Email);
            EmployeeDataStore.Add(NewEmployee);
            return ActivityExecutionStatus.Closed;
        }

最关键的一个操作就是调用了注入进来的EmployeeDataStore的Add()方法!
     再次运行测试!通过!
     上面讨论了如何对Activity进行“行为测试”,当然这还只是一个开始。
     类似前一篇中提到的,我们为测试加入“异常处理”。
     添加如下测试方法和辅助方法:

ThrowExceptionOnNullFirstName
[TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ThrowExceptionOnNullFirstName()
        {
            ExerciseActivityWithNoExpectations(null, Mother.LAST_NAME, 

Mother.EMAIL, _mockEmployeeRepository);
        }

        private void ExerciseActivityWithNoExpectations(string firstName, string 

lastName, string email,
                                                        IEmployeeRepository 

employeeRepository)
        {
            _mocker.Record().Dispose();//不记录任何操作

            using (_mocker.Playback())
            {
                using (StoreNewAcmeEmployee_Accessor activity = new 

StoreNewAcmeEmployee_Accessor())
                {
                    SetActivityProperties(activity, firstName, lastName, email, 

employeeRepository);

                    activity.Execute_Accessor(null);
                }
            }
        }

在这里,没有对Mock对象进行任何记录。就是说不期待对Mock对象进行任何操作。但是期待一个异常:ArgumentNullException,因为传入和一个Null参数。

回到StoreNewAcmeEmployee中,改造Execute()方法为:

Execute
   protected override ActivityExecutionStatus
                Execute(ActivityExecutionContext executionContext)
        {
            EnsurePropertiesAreValid();
            
            NewEmployee = new AcmeEmployee(Guid.NewGuid().ToString(),
                              FirstName,
                              LastName,
                              Email);

            EmployeeDataStore.Add(NewEmployee);

            return ActivityExecutionStatus.Closed;
        }

        private void EnsurePropertiesAreValid()
        {
            if (Email == null)
                throw new ArgumentNullException("Email");

            if (FirstName == null)
                throw new ArgumentNullException("FirstName");

            if (LastName == null)
                throw new ArgumentNullException("LastName");

            if (EmployeeDataStore == null)
                throw new ArgumentNullException("Email");
        }

在调用EmployeeDataStore的Add()方法之前,检查参数是否合法,不合法就抛出异常,不调用EmployeeDataStore的Add()方法。
     运行测试,通过!
     对Activity的行为测试就到这里,下次为大家介绍对Workflow的测试。
     附源码:NewEmployeeWF3.rar

注:以上示例来自WF3.5 Hands On Lab,英文好的朋友可以去这里看:https://www.microsoft.com/resources/virtuallabs/step3-msdn.aspx?LabId=c4a993a5-d498-4d5c-9f98-476c1f496d15&BToken=reg

转载于:https://www.cnblogs.com/zhaojunqi/archive/2009/03/10/1407951.html

WF单元测试系列3:测试Activity的行为相关推荐

  1. 单元测试系列之八:Sonar 数据库表关系整理一(续)

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 简介:Sonar平台是目前较为流行的静态代码扫描平台,为了便于使用以及自己二次开发,有必要对它的数据库结构进行学习 ...

  2. 单元测试系列之九:Sonar 常用代码规则整理(一)

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 摘要:公司部署了一套sonar,经过一段时间运行,发现有一些问题出现频率很高,因此有必要将这些问题进行整理总结和分 ...

  3. Android Studio中进行单元测试和UI测试

    目录 在Android Studio中进行单元测试和UI测试 - 1.概述 在Android Studio中进行单元测试和UI测试 - 2.创建新的Android Studio工程 在Android ...

  4. 单元测试系列之五:Mock工具之Mockito实战

    更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注个人的微信号,感谢! 原文链接:http://www.cnblogs.com/zishi/p/6780719.html 在实际项目中写单 ...

  5. Android单元测试系列(2)-Junit

    书接上文:Android单元测试系列(1)-开篇_Chris_166的博客-CSDN博客 本篇来介绍下Junit的简单使用,当前已经更新到Junit5了,我这里还是以Junit4来说明吧. 一.Jun ...

  6. Android单元测试系列(3)-Mock之Mockito

    目录 一.官网 二.Demo示例 1. 目录结构 2. 被测试的类 3. 测试类 三.Mockito方法说明 1. mock对象创建 2. Mockito框架中的常见方法说明 2.1 常见的打桩方法 ...

  7. Android单元测试系列(3)-Mock之PowerMock

    目录 一.官网 二.Demo示例 三.PowerMock常用的测试方法 1. Private 1.1 私有变量 1.2 私有方法 2. Final 3. Static Android单元测试系列(3) ...

  8. Android单元测试系列(5)-Robolectric

    在Android单元测试系列(1)-开篇_Chris_166的博客-CSDN博客中就提过"Robolectric用于模拟Android接口,这套框架可以直接让测试case运行于JVM,不需要 ...

  9. Webpack单元测试,e2e测试

    此篇文章是续 webpack多入口文件.热更新等体验,主要说明单元测试与e2e测试的基本配置以及相关应用. 一.单元测试 实现单元测试框架的搭建.es6语法的应用.以及测试覆盖率的引入. 1. 需要安 ...

最新文章

  1. python中关于sqlite3数据库删除数据的使用
  2. linux-命令替换-通配符-重定向-管道
  3. python3 矩阵运算_3.10 矩阵与线性代数运算
  4. [微信开发] - 用户获取推广二维码
  5. TI 无桥PFC方案【转子tidu312.pdf】-----图片专帖
  6. Git之Stash(储藏)备份当前的工作区的内容
  7. ThinkPHP 3.2版本 , 无法读取$_SESSION['verify_code']
  8. 下载丨6月数据库技术通讯:不规范SQL引发大量TX锁
  9. 模板与泛型编程(二)
  10. 浅谈操作系统是如何工作的及简单的进程调度的linux实现
  11. EMACS 使用入门
  12. JDBC:随机生成车牌号,批量插入数据库
  13. Linux多线程编程实验
  14. 网站 smtp服务器,网站smtp服务器
  15. Hyperledger Fabric 开发环境安装
  16. pc端和移动端抓包工具
  17. 菜鸟入门Docker
  18. 【CS学习笔记】14、powerup提权的方法
  19. 1.3_VMare 16 虚拟机安装配置 CentOS-7-x86_64-DVD-2009+GUI+原生 Java 1.8+ibus+VMware Tools
  20. 落笔成殇,一路颠簸红尘

热门文章

  1. vue 中eslint 格式报错
  2. 量产车发布前夕,乐视在美汽车工厂被传欠薪停工
  3. 替代jquery1.9版本以前的toggle事件函数(开关)
  4. 美参议员敦促SEC就雅虎黑客案信息披露义务展开调查
  5. UIScrollView 的代理方法简单注解
  6. Spring学习(8)--- @Autowired注解(一)
  7. JDBC访问数据库的基本程序
  8. 电子书推荐--《Python灰帽子》,python黑客编程
  9. 写给程序员的最好的13条建议
  10. putty利用密钥ssh服务登录ubuntu server 10.4