我已经实施了WF4活动。它有一些必需的InArguments,一个需要InOutArgument和一个可选的OutArgument。
由于活动是在xaml中实现的(即使用工作流设计器),我必须搜索有关如何在xaml中将属性标记为“必需”的信息(类似于C#中的[RequiredArgument]
)并找到以下链接该怎么做:
http://msdn.microsoft.com/en-us/library/ee358733(v=vs.100).aspx
<x:Property Name="Operand1" Type="InArgument(x:Int32)">
<x:Property.Attributes>
<RequiredArgumentAttribute />
</x:Property.Attributes>
</x:Property>
这适用于所有InArguments。但是在为它实现测试时,我发现它与InOutArguments无法正常工作。如果我在测试中使用WorkflowInvoker.Invoke
运行我的xaml活动,而不提供任何参数,则会出现ArgumentException
抱怨所有必需的InArguments,但不会抱怨所需的InOutArgument。如果我使用所有必需的InArguments运行活动,但没有所需的InOutArgument,则不会抛出ArgumentException
。
这可能是Workflow Foundation中的错误吗?
有趣的是,如果我在工作流中使用此活动而不提供任何参数,我会在工作流设计器中看到这个红色感叹号,它会告诉我哪些参数需要输入。这里提到了InOutArgument ,这是预期的行为。
答案 0 :(得分:1)
Workflow引擎检查RequiredArgumentAttribute是否有一个活动调用另一个活动。您可以通过创建CodeActivity来查看此操作。使用断点,您可以使用CacheMetadata方法中的调试器停止监视,但在调用Execute方法之前会抛出异常。
但是,正如您所指出的,使用WorkflowInvoker直接调用具有InOut / Out参数的活动不会导致InOut / Out必需参数的异常。 RequiredArgumentAttribute要求参数具有绑定。它不要求参数具有值。这可以通过将引用变量与空值绑定来看出。 WorkflowInvoker类自动绑定到InOut / Out参数,以捕获其返回IDictionary对象的输出值。
为了为所需的InOut参数编写单元测试,您必须创建一个将自身绑定到您正在测试的活动的活动。以下代码段是我最近为此确切方案编写的代码。我正在使用CodePlex中的WorkflowInvokerTest类来封装WorkflowInvoker。
public abstract class ActivityTest
{
private class ArgumentTester : NativeActivity
{
public Collection<Variable> variables = new Collection<Variable>();
public Activity Test;
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata);
metadata.AddImplementationChild(Test);
foreach (var item in variables)
{
metadata.AddImplementationVariable(item);
}
}
protected override void Execute(NativeActivityContext context)
{
context.ScheduleActivity(Test);
}
}
protected WorkflowInvokerTest host;
protected void TestForRequiredArgument(string argName)
{
var d = (IDictionary<string, object>)host.InArguments;
d.Remove(argName);
try
{
dynamic activityToTest = System.Activator.CreateInstance(host.Activity.GetType());
ArgumentTester tester = new ArgumentTester
{
Test = activityToTest
};
foreach (var item in d)
{
Type t = typeof(Variable<>).MakeGenericType(item.Value.GetType());
Variable v = (Variable)Activator.CreateInstance(t, item.Key + "_Var", item.Value);
tester.variables.Add(v);
var prop = host.Activity.GetType().GetProperty(item.Key);
object arg = Activator.CreateInstance(prop.PropertyType, v);
prop.SetValue(activityToTest, arg);
}
var h = new WorkflowInvokerTest(tester);
h.TestActivity();
Assert.Fail("An exception should have been thrown.");
}
catch (InvalidWorkflowException ex)
{
Assert.IsTrue(ex.Message.Contains("'" + argName + "'"));
}
finally
{
host.Tracking.Trace();
}
}
}
然后我写了一个这样的测试:
[TestClass]
public class WageTest : ActivityTest
{
[TestInitialize]
public void InitializeTest()
{
host = new WorkflowInvokerTest(new WageActivity());
host.InArguments.Wage = 2000;
host.InArguments.IsFifthQuarter = false;
}
[TestMethod]
public void WageArgumentIsRequired()
{
base.TestForRequiredArgument("Wage");
}
[TestMethod]
public void IsFifthQuarterArgumentIsRequired()
{
base.TestForRequiredArgument("IsFifthQuarter");
}
//...
}
可以用泛型清理一下。我还在努力,但你明白了。