本文通过举例说明在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
原文链接: http://www.cnblogs.com/zhuqil/archive/2010/10/09/how-to-unit-test-in-wf4.html