前面几篇介绍了如何对Activity进行状态测试,行为测试,及Mock Object Framework的使用。当然,仅仅测试Activity是不够的,我们最终还要对整个Workflow进行测试。这一篇就为大家讲一下如何对Workflow进行测试。
     在上一篇的NewEmployeeWFLibrary工程里,添加一个名为StoreNewAcmeEmployee的顺序工作流。为工作流添加三个属性和一个事件:

 Property
Property

 public string FirstName
        public string FirstName  { get; set; }
{ get; set; }

 public string LastName
        public string LastName  { get; set; }
{ get; set; }


 public AcmeEmployee NewEmployee
        public AcmeEmployee NewEmployee  { get; set; }
{ get; set; }

     我们设定这个工作流的功能是根据给定的FirstName和LastName生成一个AcmeEmployee对象。下面我们开始写测试代码。在测试工程中添加一个测试类ProvisionNewEmployee_WorkflowShould,代码如下:
 

 ProvisionNewEmployee_WorkflowShould
ProvisionNewEmployee_WorkflowShould
 [TestClass]
[TestClass]
 public class ProvisionNewEmployee_WorkflowShould
    public class ProvisionNewEmployee_WorkflowShould

 
     {
{
 private AcmeEmployee _createAcmeEmployee;
        private AcmeEmployee _createAcmeEmployee;
 private ManualWorkflowSchedulerService _manualScheduler;
        private ManualWorkflowSchedulerService _manualScheduler;
 private WorkflowRuntime _runtime;
        private WorkflowRuntime _runtime;

 [TestInitialize]
        [TestInitialize]
 public void TestInitializer()
        public void TestInitializer()

 
         {
{
 _manualScheduler = new ManualWorkflowSchedulerService();
            _manualScheduler = new ManualWorkflowSchedulerService();
 _runtime = new WorkflowRuntime();
            _runtime = new WorkflowRuntime();

 // Add the scheduler to the _runtime before it is started
            // Add the scheduler to the _runtime before it is started
 _runtime.AddService(_manualScheduler);
            _runtime.AddService(_manualScheduler);

 // when the workflow completes, assign the output value to a test
            // when the workflow completes, assign the output value to a test 
 // class member so it can be evaluated.
            // class member so it can be evaluated.
 _runtime.WorkflowCompleted += ((o, e) =>
            _runtime.WorkflowCompleted += ((o, e) =>

 
                                                {
{
 if (o == null) throw new
                                                   if (o == null) throw new 

 ArgumentNullException("o");
ArgumentNullException("o");
 _createAcmeEmployee =
                                                   _createAcmeEmployee =
 (AcmeEmployee)
                                                       (AcmeEmployee) 

 e.OutputParameters["NewEmployee"];
e.OutputParameters["NewEmployee"];
 });
                                               });

 // this event will fire if an exception occurs in the runtime.
            // this event will fire if an exception occurs in the runtime.
 _runtime.WorkflowTerminated += ((o, e) =>
            _runtime.WorkflowTerminated += ((o, e) =>

 
             {
{
 // but throwing it again means the runtime will handle it
                // but throwing it again means the runtime will handle it
 // in ServicesExceptionNotHandled
                // in ServicesExceptionNotHandled 
 throw e.Exception;
                throw e.Exception;
 });
            });

 // The WF runtime wants to manage exceptions here all by itself
            // The WF runtime wants to manage exceptions here all by itself
 // so we throw the expection again to ensure it gets into the
            // so we throw the expection again to ensure it gets into the 
 // test class itself
            // test class itself

 _runtime.ServicesExceptionNotHandled += ((o, e) =>
            _runtime.ServicesExceptionNotHandled += ((o, e) =>  { throw e.Exception;
{ throw e.Exception; 

 });
});
 }
        }

 [TestMethod]
        [TestMethod]
 public void CreateAValidAcmeEmployee()
        public void CreateAValidAcmeEmployee()

 
         {
{
 RunTheWorkflow(Mother.FIRST_NAME, Mother.LAST_NAME);
            RunTheWorkflow(Mother.FIRST_NAME, Mother.LAST_NAME);
 AcmeEmployeeAssert.AreEqualExceptForId(_createAcmeEmployee,
            AcmeEmployeeAssert.AreEqualExceptForId(_createAcmeEmployee, 

 Mother.CreateNewAcmeEmployee());
Mother.CreateNewAcmeEmployee());
 }
        }

 [TestMethod]
        [TestMethod]
 [ExpectedException(typeof(ArgumentNullException))]
        [ExpectedException(typeof(ArgumentNullException))]
 public void ThrowExceptionOnNullFirstName()
        public void ThrowExceptionOnNullFirstName()

 
         {
{
 RunTheWorkflow(null, Mother.LAST_NAME);
            RunTheWorkflow(null, Mother.LAST_NAME);
 }
        }

 [TestMethod]
        [TestMethod]
 [ExpectedException(typeof(ArgumentNullException))]
        [ExpectedException(typeof(ArgumentNullException))]
 public void ThrowExceptionOnNullLastName()
        public void ThrowExceptionOnNullLastName()

 
         {
{
 RunTheWorkflow(Mother.FIRST_NAME, null);
            RunTheWorkflow(Mother.FIRST_NAME, null);
 }
        }

 private void RunTheWorkflow(string firstName, string lastName)
        private void RunTheWorkflow(string firstName, string lastName)

 
         {
{
 // Setup the input parameters
            // Setup the input parameters
 // The name of the argument here must match the
            // The name of the argument here must match the 
 // name of the property on the workflow class
            // name of the property on the workflow class
 var args = new Dictionary<string, object>
            var args = new Dictionary<string, object>

 
                            {
{

 
                                {"FirstName", firstName},
{"FirstName", firstName},

 
                                {"LastName", lastName}
{"LastName", lastName}
 };
                           };

 WorkflowInstance targetWorkflow =
            WorkflowInstance targetWorkflow = 

 _runtime.CreateWorkflow(typeof(ProvisionNewEmployee), args);
_runtime.CreateWorkflow(typeof(ProvisionNewEmployee), args);
 targetWorkflow.Start();
            targetWorkflow.Start();

 _manualScheduler.RunWorkflow(targetWorkflow.InstanceId);
            _manualScheduler.RunWorkflow(targetWorkflow.InstanceId);
 }
        }
 }
    }


     简单解释一下代码:
     _createAcmeEmployee对象负责获取工作流的返回值,用来验证测试结果。
     _manualScheduler和_runtime负责工作流的运行。
     TestInitializer()方法负责初始化测试环境,实际上就是运行工作流,并在工作流结束之后获取输出参数给_createAcmeEmployee对象。
     RunTheWorkflow()方法传递FirstName和LastName两个参数给被运行的ProvisionNewEmployee工作流。
     CreateAValidAcmeEmployee()方法用指定的参数运行工作流,然后验证工作流生成的AcmeEmployee对象的FirstName和LastName是否和传入的参数一致。
     运行测试,失败!因为我们还没有为工作流添加逻辑呢。
     这时我们之前创建的两个Activity派上用场了。编译工程,然后从Toolbar中依次把GetEmailAddress和StoreNewAcmeEmoloyee拖到工作流当中。设置他们的属性:
     1.把GetEmailAddress的FirstName,LastName分别绑定到工作流的对应属性上,从而获得传入的参数值。GetEmailAddress负责根据这两个参数生成一个Email,保存到它的Email属性上。
     2.把StoreNewAcmeEmoloyee的Email属性绑定到GetEmailAddress的Email上,用来获得它生成的Email。
     3.绑定StoreNewAcmeEmoloyee的FirstName,LastName到工作流的对应属性上,同样用来获得参数值。
     4.绑定StoreNewAcmeEmoloyee的NewEmployee属性到工作流的对应属性上,用来输出它生成的对象。
     看到这里大家应该明白了,我的工作流根据传入的两个参数生成Email,再根据这三个参数生成一个AcmeEmployee对象,然后输出给宿主。
最后还有一个细节,大家还记得吧,StoreNewAcmeEmployee把生成的AcmeEmployee对象添加到一个IEmployeeRepository中保存(调用它的Add方法)。所以在工作流中添加如下代码:

 ProvisionNewEmployeeWF_Initialized
ProvisionNewEmployeeWF_Initialized
        private void ProvisionNewEmployeeWF_Initialized(object sender, EventArgs e)
        {
            _storeNewAcmeEmployeeActivity.EmployeeDataStore = 
AcmeEmployeeRepository.GetInstance();
        }
     用来在工作流初始化时实例化此接口。
     OK!我们的逻辑添加完毕!运行测试,通过!
     至于另外两个测试方法,则是测试错误路径的,前面讲过,不再赘述。
     我们的WF测试系列就此结束了。当然关于此的话题不会就此终结,大家有什么关于WF测试的心得,体会,问题,欢迎一起讨论!
     附源码:NewEmployeeWF4.rar
     注:以上示例来自WF3.5 Hands On Lab,英文好的朋友可以去这里看:https://www.microsoft.com/resources/virtuallabs/step3-msdn.aspx?LabId=c4a993a5-d498-4d5c-9f98-476c1f496d15&BToken=reg