如何测试自动命名状态机?
How to test Automatonymous state machines?
我正在尝试为自动命名状态机编写测试,但我在做对时遇到了一些麻烦,而且我发现的文档很少。
这是我目前的一项测试:
[TestFixture]
public class MyProcessStateMachineTests
{
InMemoryTestHarness _Harness;
MyProcessStateMachine _Machine;
StateMachineSagaTestHarness<MyProcess, MyProcessStateMachine> _Saga;
[OneTimeSetUp]
public void ConfigureMessages()
{
MessageCorrelation.UseCorrelationId<RequestMyDetails>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<FileAttached>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<PDFGenerated>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<CustomerAttachFile>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<AddCustomerNote>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<EmailPublished>(x => x.CorrelationId);
}
[SetUp]
public void InitializeTestHarness()
{
_Harness = new InMemoryTestHarness();
_Machine = new MyProcessStateMachine( /* snip */ );
_Saga = _Harness.StateMachineSaga<MyProcess, MyProcessStateMachine>(_Machine);
_Harness.Start().Wait();
}
[TearDown]
public void StopTestHarness()
{
_Harness.Stop();
}
[Test]
public async Task ShouldAttachToCustomer()
{
var sagaId = Guid.NewGuid();
var custId = Guid.NewGuid();
var fileAttached = BuildFileAttachedMessage(sagaId);
await _Harness.InputQueueSendEndpoint.Send(BuildStartMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildDetailsReceivedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildPdfGeneratedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(fileAttached);
// Next line is based on [the answer here][1]
// Once the above messages are all consumed and processed,
// the state machine should be in AwaitingEmail state
await _Saga.Match(x =>
x.CorrelationId == sagaId
&& x.CurrentState == _Machine.AwaitingEmail.Name,
new TimeSpan(0, 0, 30));
// Grab the instance and Assert stuff...
}
// Snip...
}
鉴于 _Saga.Match 调用找到匹配项,我希望所有消息都已处理,我应该能够获取我的状态机实例和已发布的事件并检查它们的值 - 但事实并非如此不是这样的。当我 运行 夹具中的测试时,有时我得到的实例已经消耗并发布了预期的消息;有时它还不完全。
我尝试使用以下方式获取我的实例:
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
或通过以下方式获取已发布的事件:
var test = _Harness.Published
.FirstOrDefault(x => x.MessageType == typeof(IAttachFile) && x.Context.CorrelationId == sagaId);
但是 Match 调用成功并不重要,状态机实例(和发布的事件)并不总是存在。
我假设来自 Automatonymous、MassTransit 或测试工具的异步进程导致了不一致。有帮助吗?
使用 MassTransit 进行测试,MassTransit.Automatonymous 和 MassTransit.TestFramework 5.1.2.1528,Automatonymous 4.1.1.102,
编辑:
进一步审查,我发现当我遇到问题时,调用 Match( ... )
没有成功 - 它超时了。 (我一直错误地假设超时会引发异常。)
如果这可能对其他人有帮助,这就是我最终让它工作的方式:
[TestFixture]
public class ProcessStateMachineTests : InMemoryTestFixture
{
TimeSpan _TestTimeout = new TimeSpan(0, 1, 0);
ProcessStateMachine _Machine;
InMemorySagaRepository<Process> _Repository;
protected override void ConfigureInMemoryReceiveEndpoint(
IInMemoryReceiveEndpointConfigurator configurator)
{
_Machine = new ProcessStateMachine();
_Repository = new InMemorySagaRepository<Process>();
configurator.StateMachineSaga(_Machine, _Repository);
}
[OneTimeSetUp]
public void ConfigureMessages()
{
// Message correlation and such happens in here
ProcessStateMachine.ConfigureMessages();
}
[Test]
public async Task OnInitializationIStartProcessIsConsumed()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var msg = InMemoryTestHarness.Consumed
.Select<IStartProcess>(x => x.Context.Message.RequestId == sagaId)
.FirstOrDefault();
// Assert against msg for expected results
}
[Test]
public async Task OnStartProcessAddCustomerNoteAndRequestDetailsPublished()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var pubdNoteAddedMsg = InMemoryTestHarness.Published
.Select<IAddCustomerNote>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
var pubdDetailsReqdMsg = InMemoryTestHarness.Published
.Select<IRequestDetails>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
Assert.IsTrue(pubdNoteAddedMsg != null);
Assert.IsTrue(pubdDetailsReqdMsg != null);
Assert.AreEqual(sagaId, pubdNoteAddedMsg.Context.CorrelationId);
Assert.AreEqual(sagaId, pubdDetailsReqdMsg.Context.CorrelationId);
Assert.AreEqual(customerId, pubdNoteAddedMsg.Context.Message.CustomerId);
Assert.IsFalse(String.IsNullOrEmpty(pubdNoteAddedMsg.Context.Message.Note));
}
private async Task SetupStateMachine(
Guid sagaId,
Guid customerId,
String toState)
{
if (String.IsNullOrEmpty(toState))
return;
await MoveStateMachineForward(BuildStartMessage(), x => x.AwaitingDetails);
var awaitingDetailsId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, x => x.AwaitingDetails, _TestTimeout);
Assert.IsNotNull(awaitingDetailsId, "Error, expected state machine in AwaitingDetails state");
if (toState == _Machine.AwaitingDetails.Name)
return;
// ...and more stuff to move to later states, depending on
// where I want my test's starting point to be...
async Task MoveStateMachineForward<T>(
T message,
Func<ProcessStateMachine, Automatonymous.State> targetState)
where T : class
{
await InputQueueSendEndpoint.Send(message);
var foundSagaId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, targetState, _TestTimeout);
Assert.IsTrue(foundSagaId.HasValue);
}
IStartProcess BuildStartMessage()
{
return new StartProcessMessage(sagaId, customerId);
}
}
}
对于遇到相同问题的任何人:您尝试获取实例的方式
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
可以这样固定
var instanceIds = await _sagaHarness.Match(
instance => instance.CorrelationId == sagaId
&& instance.CurrentState == _machine.Working.Name,
new TimeSpan(0, 0, 30));
条件中包含预期状态很重要。仅通过 correlationid 获取实例然后测试 CurrentState 可能会失败,因为设置状态(saga 执行是异步的)可能需要一些时间。
请参阅 https://github.com/balintn22/AutomatonymousTestExample
中的完整示例
我正在尝试为自动命名状态机编写测试,但我在做对时遇到了一些麻烦,而且我发现的文档很少。
这是我目前的一项测试:
[TestFixture]
public class MyProcessStateMachineTests
{
InMemoryTestHarness _Harness;
MyProcessStateMachine _Machine;
StateMachineSagaTestHarness<MyProcess, MyProcessStateMachine> _Saga;
[OneTimeSetUp]
public void ConfigureMessages()
{
MessageCorrelation.UseCorrelationId<RequestMyDetails>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<FileAttached>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<PDFGenerated>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<CustomerAttachFile>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<AddCustomerNote>(x => x.CorrelationId);
MessageCorrelation.UseCorrelationId<EmailPublished>(x => x.CorrelationId);
}
[SetUp]
public void InitializeTestHarness()
{
_Harness = new InMemoryTestHarness();
_Machine = new MyProcessStateMachine( /* snip */ );
_Saga = _Harness.StateMachineSaga<MyProcess, MyProcessStateMachine>(_Machine);
_Harness.Start().Wait();
}
[TearDown]
public void StopTestHarness()
{
_Harness.Stop();
}
[Test]
public async Task ShouldAttachToCustomer()
{
var sagaId = Guid.NewGuid();
var custId = Guid.NewGuid();
var fileAttached = BuildFileAttachedMessage(sagaId);
await _Harness.InputQueueSendEndpoint.Send(BuildStartMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildDetailsReceivedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(BuildPdfGeneratedMessage(sagaId));
await _Harness.InputQueueSendEndpoint.Send(fileAttached);
// Next line is based on [the answer here][1]
// Once the above messages are all consumed and processed,
// the state machine should be in AwaitingEmail state
await _Saga.Match(x =>
x.CorrelationId == sagaId
&& x.CurrentState == _Machine.AwaitingEmail.Name,
new TimeSpan(0, 0, 30));
// Grab the instance and Assert stuff...
}
// Snip...
}
鉴于 _Saga.Match 调用找到匹配项,我希望所有消息都已处理,我应该能够获取我的状态机实例和已发布的事件并检查它们的值 - 但事实并非如此不是这样的。当我 运行 夹具中的测试时,有时我得到的实例已经消耗并发布了预期的消息;有时它还不完全。
我尝试使用以下方式获取我的实例:
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
或通过以下方式获取已发布的事件:
var test = _Harness.Published
.FirstOrDefault(x => x.MessageType == typeof(IAttachFile) && x.Context.CorrelationId == sagaId);
但是 Match 调用成功并不重要,状态机实例(和发布的事件)并不总是存在。
我假设来自 Automatonymous、MassTransit 或测试工具的异步进程导致了不一致。有帮助吗?
使用 MassTransit 进行测试,MassTransit.Automatonymous 和 MassTransit.TestFramework 5.1.2.1528,Automatonymous 4.1.1.102,
编辑:
进一步审查,我发现当我遇到问题时,调用 Match( ... )
没有成功 - 它超时了。 (我一直错误地假设超时会引发异常。)
如果这可能对其他人有帮助,这就是我最终让它工作的方式:
[TestFixture]
public class ProcessStateMachineTests : InMemoryTestFixture
{
TimeSpan _TestTimeout = new TimeSpan(0, 1, 0);
ProcessStateMachine _Machine;
InMemorySagaRepository<Process> _Repository;
protected override void ConfigureInMemoryReceiveEndpoint(
IInMemoryReceiveEndpointConfigurator configurator)
{
_Machine = new ProcessStateMachine();
_Repository = new InMemorySagaRepository<Process>();
configurator.StateMachineSaga(_Machine, _Repository);
}
[OneTimeSetUp]
public void ConfigureMessages()
{
// Message correlation and such happens in here
ProcessStateMachine.ConfigureMessages();
}
[Test]
public async Task OnInitializationIStartProcessIsConsumed()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var msg = InMemoryTestHarness.Consumed
.Select<IStartProcess>(x => x.Context.Message.RequestId == sagaId)
.FirstOrDefault();
// Assert against msg for expected results
}
[Test]
public async Task OnStartProcessAddCustomerNoteAndRequestDetailsPublished()
{
var sagaId = Guid.NewGuid();
var customerId = Guid.NewGuid();
await SetupStateMachine(sagaId, customerId, _Machine.AwaitingDetails.Name);
var pubdNoteAddedMsg = InMemoryTestHarness.Published
.Select<IAddCustomerNote>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
var pubdDetailsReqdMsg = InMemoryTestHarness.Published
.Select<IRequestDetails>()
.FirstOrDefault(x => x.Context.Message.RequestId == sagaId);
Assert.IsTrue(pubdNoteAddedMsg != null);
Assert.IsTrue(pubdDetailsReqdMsg != null);
Assert.AreEqual(sagaId, pubdNoteAddedMsg.Context.CorrelationId);
Assert.AreEqual(sagaId, pubdDetailsReqdMsg.Context.CorrelationId);
Assert.AreEqual(customerId, pubdNoteAddedMsg.Context.Message.CustomerId);
Assert.IsFalse(String.IsNullOrEmpty(pubdNoteAddedMsg.Context.Message.Note));
}
private async Task SetupStateMachine(
Guid sagaId,
Guid customerId,
String toState)
{
if (String.IsNullOrEmpty(toState))
return;
await MoveStateMachineForward(BuildStartMessage(), x => x.AwaitingDetails);
var awaitingDetailsId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, x => x.AwaitingDetails, _TestTimeout);
Assert.IsNotNull(awaitingDetailsId, "Error, expected state machine in AwaitingDetails state");
if (toState == _Machine.AwaitingDetails.Name)
return;
// ...and more stuff to move to later states, depending on
// where I want my test's starting point to be...
async Task MoveStateMachineForward<T>(
T message,
Func<ProcessStateMachine, Automatonymous.State> targetState)
where T : class
{
await InputQueueSendEndpoint.Send(message);
var foundSagaId = await _Repository.ShouldContainSagaInState(
sagaId, _Machine, targetState, _TestTimeout);
Assert.IsTrue(foundSagaId.HasValue);
}
IStartProcess BuildStartMessage()
{
return new StartProcessMessage(sagaId, customerId);
}
}
}
对于遇到相同问题的任何人:您尝试获取实例的方式
var inst = _Saga.Sagas.FirstOrDefault(x => x.Saga.CorrelationId == sagaId);
可以这样固定
var instanceIds = await _sagaHarness.Match(
instance => instance.CorrelationId == sagaId
&& instance.CurrentState == _machine.Working.Name,
new TimeSpan(0, 0, 30));
条件中包含预期状态很重要。仅通过 correlationid 获取实例然后测试 CurrentState 可能会失败,因为设置状态(saga 执行是异步的)可能需要一些时间。 请参阅 https://github.com/balintn22/AutomatonymousTestExample
中的完整示例