• 一起谈.NET技术,WF4.0进行单元测试 狼人:


      1、简单的WF4.0活动测试

      如果是一个简单的WF4.0活动,是那种没有带BookMark,也不是messaging活动,也不是长时间运行的活动。使用WorkflowInvoker进行单元测试将非常的方便。

      下面,我们以一种情况为例子:流程中只包含了两个加数相加的操作,然后就将结果返回。流程如下图所示:

      最简单的方法是通过Workflow Invoker进行测试。

    [TestMethod]
    public void ShouldAddGoodSum()
    {
    var output
    = WorkflowInvoker.Invoke(new GoodSum() {x=1, y=2});

    Assert.AreEqual(
    3, output["sum"]);
    }

      如果这个测试通过,就是说这个流程的输出参数集合中包含一个 Int32的sum参数,值等于3。有下面几种情况可能需要我们进行测试:

      1、没有名为sum这个输出参数。

      2、有一个sum输出参数,但是类型不是Int32。

      3、有一个sum输出参数,类型也是Int32,但是值不是我们期待的(3)。

      这样我们能清楚流程哪出了问题。我们修改一下流程,将输出的参数sum改成Sum。在VB中,没有什么问题,但是在C#中sum!=Sum。通过上面测试代码你会得到下面的错误提示。

      但是异常信息提示的太简单,我们不知道到底是那个参数出现了问题。有更好的办法吗?有,使用WorklowTestHelper。

      使用WorklowTestHelper进行单元测试:

      1、下载WorkflowTestHelper

      2、添加WorkflowTestHelper引用

      3、使用WorkflowTestHelper表达式

      我们将测试代码改成:

    代码
    [TestMethod]
    public void ShouldAddGoodSumAssertOutArgument()
    {
    var output
    = WorkflowInvoker.Invoke(new GoodSum() { x = 1, y = 2 });

    AssertOutArgument.AreEqual(output,
    "sum", 3);
    }

      再看下出现的异常信息。信息更加明确了。

      AssertOutArgument.AreEqual failed. Output does not contain an argument named <sum>.

      如果类型错误,例如我们将流程中的sum修改成string。

      对于Assert.AreEqual我们得到下面错误提示:

      Assert.AreEqual failed. Expected:<3 (System.Int32)>. Actual:<3 (System.String)>.

      使用AssertOutArgument,将会得到更确切的错误信息:

      AssertOutArgument.AreEqual failed. Wrong type for OutArgument <sum>. Expected Type: <System.Int32>. Actual Type: <System.String>.

      2、对带有BookMark的流程进行测试

      什么是BookMark?如果你有使用WF4.0开发过,就应该知道BookMark在WF中的地位。简单的说:如果你的流程需要数据,这些参数可能来自用户输入,可能来自宿主环境,BookMark是用来暂停流程,等待响应的。下面是一个书签活动的代码:

    代码
    public sealed class ReadLine : NativeActivity<string>
    {
    private BookmarkCallback _readCompleteCallback;

    [RequiredArgument]
    public InArgument<string> BookmarkName { get; set; }

    protected override bool CanInduceIdle
    {
    get { return true; }
    }

    public BookmarkCallback ReadCompleteCallback
    {
    get { return _readCompleteCallback ?? (_readCompleteCallback = new BookmarkCallback(OnReadComplete)); }
    }

    protected override void Execute(NativeActivityContext context)
    {
    // Inform the host that this activity needs data and wait for the callback
    context.CreateBookmark(BookmarkName.Get(context), ReadCompleteCallback);
    }

    private void OnReadComplete(NativeActivityContext context, Bookmark bookmark, object state)
    {
    // Store the value returned by the host
    context.SetValue(Result, state as string);
    }
    }

       下面我在流程中使用这个活动,用来等待用户的输入。

      对于这个流程,如果你使用WorkflowInvoker,你的应用程序将会一直被挂起。所以你不得不使用WorkflowApplication,以及在你应用程序中处理这些BookMark。

      如何测试这个流程。

      测试这个流程将非常棘手。使用WorkflowTestHelper类库中的类将会方便很多。这个类就是WorkflowApplicationTest<T>.。这个类可以方便的创建和管理WorkflowApplication。使用非常简单的代码就可以处理好流程的所有的事件和结果。测试代码如下:

    代码
    [TestMethod]
    public void ShouldOutputGreeting()
    {
    // Arrange
    const string expectedFirstName = "Test";
    const string expectedLastName = "User";
    var expectedGreeting
    = string.Format("Hello {0} {1}", expectedFirstName, expectedLastName);
    var sut
    = WorkflowApplicationTest.Create(new TestReadLine());

    // Act

    // Run the workflow
    sut.TestActivity();

    // Wait for the first idle event - prompt for First Name
    // will return false if the activity does not go idle within the
    // timeout (default 1 sec)
    Assert.IsTrue(sut.WaitForIdleEvent());

    // Should have a bookmark named "FirstName"
    Assert.IsTrue(sut.Bookmarks.Contains("FirstName"));

    Assert.AreEqual(BookmarkResumptionResult.Success,
    sut.TestWorkflowApplication.ResumeBookmark(
    "FirstName", expectedFirstName));

    // Wait for the second idle event - prompt for Last Name
    Assert.IsTrue(sut.WaitForIdleEvent());

    // Should have a bookmark named "LastName"
    Assert.IsTrue(sut.Bookmarks.Contains("LastName"));

    Assert.AreEqual(BookmarkResumptionResult.Success,
    sut.TestWorkflowApplication.ResumeBookmark(
    "LastName", expectedLastName));

    // Wait for the workflow to complete
    Assert.IsTrue(sut.WaitForCompletedEvent());

    // Assert
    // WorkflowApplicationTest.TextLines returns an array of strings
    // that contains strings written by the WriteLine activity
    Assert.AreEqual(4, sut.TextLines.Length);
    Assert.AreEqual(expectedGreeting, sut.TextLines[
    2]);
    }

      3、测试WorkflowService

      测试例子:

      定义一个扩展类,用来存储数据和计算平均数:

    1 public class AverageExtension
    2 {
    3 private static readonly List<int> Numbers = new List<int>();
    4
    5 public static void Reset()
    6 {
    7 Numbers.Clear();
    8 }
    9
    10 public void StoreNumber(int num)
    11 {
    12 Numbers.Add(num);
    13 }
    14
    15 public double GetAverage()
    16 {
    17 return Numbers.Average();
    18 }
    19 }

      定义一个活动使用此扩展类,在这个自定义的活动中,重写CacheMetadata方法,实现当服务中没有此扩展就添加此扩展。

    代码
    public sealed class GetAverage : CodeActivity<string>
    {
    public InArgument<int> Number { get; set; }


    protected override void CacheMetadata(CodeActivityMetadata metadata)
    {
    // This activity requires the average extension
    metadata.RequireExtension(typeof (AverageExtension));

    // This lambda will create one if it is not already added
    metadata.AddDefaultExtensionProvider(() => new AverageExtension());

    base.CacheMetadata(metadata);
    }

    protected override string Execute(CodeActivityContext context)
    {
    // Get the average extension
    var average = context.GetExtension<AverageExtension>();

    if (average == null)
    throw new InvalidOperationException("Cannot access AverageExtension");

    var number
    = Number.Get(context);

    // Store this number
    average.StoreNumber(number);

    return string.Format("Stored {0}, Average:{1}", number, average.GetAverage());
    }
    }

      工作流服务如下:

      如何测试:

      1、添加WorkflowTestHelper引用。

      2、配置测试环境。

      3、为WorkflowService添加DeploymentItem属性标签。

      4、使用WorkflowServiceTestHost宿主测试程序,代码如下:

    代码
    private readonly Binding _binding = new NetNamedPipeBinding();

    /// <summary>
    /// The endpoint address to be used by the test host
    /// </summary>
    private readonly EndpointAddress _serviceAddress = new EndpointAddress("net.pipe://localhost/TestService");


    /// <summary>
    /// Tests the GetAverage Activity by invoking it in a WorkflowService multiple times
    /// </summary>
    [TestMethod()]
    [DeploymentItem(
    "Service1.xamlx")]
    public void ShouldInvokeAverageExtension()
    {
    const string expected1 = "Stored 33, Average:33";
    const string expected2 = "Stored 44, Average:38.5";
    const string expected3 = "Stored 55, Average:44";

    string result1;
    string result2;
    string result3;

    // Self-Host Service1.xamlx using Named Pipes
    using (var testHost = WorkflowServiceTestHost.Open("Service1.xamlx", _serviceAddress.Uri))
    {
    // Use the generated proxy with named pipes
    var proxy = new ServiceClient(_binding, _serviceAddress);

    try
    {
    result1
    = proxy.GetData(33);
    result2
    = proxy.GetData(44);
    result3
    = proxy.GetData(55);
    proxy.Close();
    }
    catch (Exception)
    {
    proxy.Abort();
    throw;
    }

    Assert.AreEqual(expected1, result1);
    Assert.AreEqual(expected2, result2);
    Assert.AreEqual(expected3, result3);
    }
    }

       总结:这篇文章是对WF4.0单元测试的一个简单介绍。WorklowTestHelper是一个MS的老外为WF4.0单元测试写的一个类库。测试类库和测试的例子http://code.msdn.microsoft.com/wfth/中都有下载。

       参考http://blogs.msdn.com/b/rjacobs/archive/2010/09/13/how-to-unit-test-a-workflowservice.aspx

  • 相关阅读:
    Python笔记(六)- 模型及Django站点管理
    Python笔记(五)--Django中使用模板
    Java中对象的复制
    Echarts堆积柱状图排序问题
    java基础
    java中的重载与重写
    struts2中配置文件的调用顺序
    struts2的工作原理
    拦截器和过滤器的区别
    Struts2中访问web元素的四种方式
  • 原文地址:https://www.cnblogs.com/waw/p/2162783.html
Copyright © 2020-2023  润新知