如何通过 InMemoryTestHarness 成功驱动 MassTransitStateMachine?

2024-03-05

跟进:如何编写 MassTransitStateMachine 单元测试? https://stackoverflow.com/questions/49763600/how-to-write-masstransitstatemachine-unit-tests

这是一个简单的测试类(使用 MS Test),用于一个名为的简单状态机ProcedureStateMachine(注意:这对我们来说不是一个真正的生产状态机......只是我曾经玩过的一个实验MassTransitStateMachine不久前..这似乎是一个方便的独立场所,可以尝试进行单元测试):

[TestClass]
public class ProcedureStateMachineTests
{
    private ProcedureStateMachine _machine;
    private InMemoryTestHarness _harness;
    private StateMachineSagaTestHarness<ProcedureContext, ProcedureStateMachine> _saga;

    [TestInitialize]
    public void SetUp()
    {
        _machine = new ProcedureStateMachine();
        _harness = new InMemoryTestHarness();
        _saga = _harness.StateMachineSaga<ProcedureContext, ProcedureStateMachine>(_machine);

        _harness.Start().Wait();
    }

    [TestCleanup]
    public void TearDown()
    {
        _harness.Stop().Wait();
    }

    [TestMethod]
    public async Task That_Can_Start()
    {
        // Arrange
        // Act
        await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
        {
            ProcedureId = Guid.NewGuid(),
            Steps = new List<string> {"A", "B", "C" }
        });

        // Assert
        var sagaContext = _saga.Created.First();
        sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(2);
    }
}

这是状态机类本身:

public class ProcedureStateMachine : MassTransitStateMachine<ProcedureContext>
{
    public State Processing { get; private set; }
    public State Cancelling { get; private set; }
    public State CompleteOk { get; private set; }
    public State CompleteError { get; private set; }
    public State CompleteCancelled { get; private set; }

    public Event<BeginProcessing> Begin { get; private set; }
    public Event<StepCompleted> StepDone { get; private set; }
    public Event<CancelProcessing> Cancel { get; private set; }
    public Event<FinalizeProcessing> Finalize { get; private set; }

    public ProcedureStateMachine()
    {
        InstanceState(x => x.CurrentState);

        Event(() => Begin);
        Event(() => StepDone);
        Event(() => Cancel);
        Event(() => Finalize);

        BeforeEnterAny(binder => binder
            .ThenAsync(context => Console.Out.WriteLineAsync(
                $"ENTERING STATE [{context.Data.Name}]")));

        Initially(
            When(Begin)
                .Then(context =>
                {
                    context.Instance.RemainingSteps = new Queue<string>(context.Data.Steps);
                })
                .ThenAsync(context => Console.Out.WriteLineAsync(
                    $"EVENT [{nameof(Begin)}]: Procedure [{context.Data.ProcedureId}] Steps [{string.Join(",", context.Data.Steps)}]"))
                .Publish(context => new ExecuteStep
                {
                    ProcedureId = context.Instance.CorrelationId,
                    StepId = context.Instance.RemainingSteps.Dequeue()
                })
                .Publish(context => new SomeFunMessage
                {
                    CorrelationId = context.Data.CorrelationId,
                    TheMessage = $"Procedure [{context.Data.CorrelationId} has begun..."
                })
                .TransitionTo(Processing)
            );

        During(Processing,
            When(StepDone)
                .Then(context =>
                {
                    if (null == context.Instance.AccumulatedResults)
                    {
                        context.Instance.AccumulatedResults = new List<StepResult>();
                    }
                    context.Instance.AccumulatedResults.Add(
                        new StepResult
                        {
                            CorrelationId = context.Instance.CorrelationId,
                            StepId = context.Data.StepId,
                            WhatHappened = context.Data.WhatHappened
                        });
                })
                .ThenAsync(context => Console.Out.WriteLineAsync(
                    $"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] Result [{context.Data.WhatHappened}] RemainingSteps [{string.Join(",", context.Instance.RemainingSteps)}]"))
                .If(context => !context.Instance.RemainingSteps.Any(),
                    binder => binder.TransitionTo(CompleteOk))
                .If(context => context.Instance.RemainingSteps.Any(),
                    binder => binder.Publish(context => new ExecuteStep
                    {
                        ProcedureId = context.Instance.CorrelationId,
                        StepId = context.Instance.RemainingSteps.Dequeue()
                    })),
            When(Cancel)
                .Then(context =>
                {
                    context.Instance.RemainingSteps.Clear();
                })
                .ThenAsync(context => Console.Out.WriteLineAsync(
                    $"EVENT [{nameof(Cancel)}]: Procedure [{context.Data.ProcedureId}] will be cancelled with following steps remaining [{string.Join(",", context.Instance.RemainingSteps)}]"))
                .TransitionTo(Cancelling)
            );

        During(Cancelling,
            When(StepDone)
                .Then(context =>
                {
                    context.Instance.SomeStringValue = "Booo... we cancelled...";
                })
                .ThenAsync(context => Console.Out.WriteLineAsync(
                    $"EVENT [{nameof(StepDone)}]: Procedure [{context.Data.ProcedureId}] Step [{context.Data.StepId}] completed while cancelling."))
                .TransitionTo(CompleteCancelled));

        During(CompleteOk, When(Finalize).Finalize());
        During(CompleteCancelled, When(Finalize).Finalize());
        During(CompleteError, When(Finalize).Finalize());

        // The "SetCompleted*" thing is what triggers purging of the state context info from the store (eg. Redis)...  without that, the 
        // old completed state keys will gradually accumulate and dominate the Redis store.
        SetCompletedWhenFinalized();
    }
}

调试此测试时,_harnessBeginProcessing消息中的Sent集合,但是里面什么都没有_saga.Created收藏。看来我缺少一些管道,导致发送消息时线束实际驱动状态机?

====

删除.Wait()来电来自SetUp() and TearDown()并将测试更新为以下内容不会改变行为:

    [TestMethod]
    public async Task That_Can_Start()
    {
        try
        {
            await _harness.Start();
            // Arrange

            // Act
            await _harness.InputQueueSendEndpoint.Send(new BeginProcessing
            {
                ProcedureId = Guid.NewGuid(),
                Steps = new List<string> {"A", "B", "C"}
            });

            // Assert
            var sagaContext = _saga.Created.First();
            sagaContext.Saga.RemainingSteps.ShouldHaveCountOf(3);
        }
        finally
        {
            await _harness.Stop();
        }
    }

事实证明,如上所示的测试代码受到了之间的竞争条件的影响。_harness.InputQueueSendEndpoint.Send操作和一些异步(超出了等待的范围)Send等待)中的行为StateMachineSagaTestHarness。因此,测试代码的“断言”阶段在创建传奇并允许处理发送的消息之前执行。

深入挖掘SagaTestHarness编写了一些代码后,我发现了一些辅助方法,我可以使用它们来等待传奇的某些条件得到满足。方法有:

/// <summary>
/// Waits until a saga exists with the specified correlationId
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> Exists(Guid sagaId, TimeSpan? timeout = null)

/// <summary>
/// Waits until at least one saga exists matching the specified filter
/// </summary>
/// <param name="filter"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<IList<Guid>> Match(Expression<Func<TSaga, bool>> filter, TimeSpan? timeout = null)

/// <summary>
/// Waits until the saga matching the specified correlationId does NOT exist
/// </summary>
/// <param name="sagaId"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public async Task<Guid?> NotExists(Guid sagaId, TimeSpan? timeout = null)

所以我决定使用类似的东西await _saga.Match(s => null != s.RemainingSteps);这样可以有效地复制我后来的断言,并等待直到超时(默认为 30 秒)或后来断言的条件变为真(因此可以安全地断言)..以先到者为准。

这会让我暂时摆脱困境,直到我能想出更好的方法来知道安全带何时“被抓住”并准备好接受审讯。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何通过 InMemoryTestHarness 成功驱动 MassTransitStateMachine? 的相关文章

随机推荐

  • 如何将数据从一个视图控制器传递到另一个 SWIFT

    我正在制作一个应用程序 其中带有搜索栏和范围栏的表格视图必须连接到详细视图控制器 并且该详细视图控制器必须根据选择的单元格显示数据 我有一个数组 其中设置了用于排序和搜索项目的结构 我需要保留此功能 我的详细视图控制器有另一个 swift
  • 打印文本区域文本 - 全长(高度)?

    我有一个网络表单 我的客户希望用户能够打印出来 使用 CSS 进行一些样式设置就可以很好地工作 但是 我有几个textaear字段 如果用户键入的内容超过文本区域的高度 则打印时该类型将被截断 我努力了textarea height 100
  • D3 中的折线和图例间距

    我使用下面的示例 希望在饼图之外有图例 并且还具有文本的折线以及每个切片的计数和百分比 使用当前代码 我在饼内有饼图 当我将鼠标悬停在切片上时 会显示文本和百分比 非常感谢您的帮助 谢谢 有人可以帮忙吗 因为我无法继续前进
  • 获取 linq 返回 IEnumerable 结果

    如何将以下 SQL 查询转换为 C 中的 LINQ 我不需要两个表中的所有列 结果集应该是 IEnumerable
  • 管道转换后获取 ngFor 中数组的长度

    我有以下模板 div Here is the length of my ngFor l div 不幸的是 ngFor 中不存在长度 如何解决此问题以使 ngFor 中的长度可用 另一种解决方案可能如下 div Here is the len
  • 我可以将 sqlbulkcopy 与 Azure SQL Paas 结合使用吗?

    我可以将 sqlbulkcopy 与 Azure SQL Paas 结合使用吗 我有一个可以批量复制到数据库的应用程序 我们正在使用 SQL PaaS 对其进行测试 SQLBulkCopy 似乎失败了 我以为我读到某处不支持此功能 但在 A
  • Canvas对象标签:不是我最初给它的类型

    我有一个画布 我向其中添加各种对象 例如文本对象等 我向每个画布子对象添加 标签 属性 在下面的示例中 我为绝对支持的 tag 属性添加了一个字典 import tkinter window tkinter Tk myCanvas tkin
  • Facebook 点赞按钮显示所有 Facebook 页面的点赞数均为 0

    更新 所以这很大 如果您尝试使用 赞 按钮链接到任何 Facebook 页面 则不会起作用 尝试访问 Facebook 之类的创建页面 http developers facebook com docs reference plugins
  • 在 Perl 中比较字符串时如何忽略重音符号?

    我有这个测验应用程序 我将人们输入的内容与正确的答案相匹配 现在 我所做的基本上是 if input answer i print you won 这很好 就好像答案是 鱼 一样 用户可以输入 一条鱼 并被视为一个好的答案 我面临的问题是
  • 获取 C 字符串中索引引用的字符

    我有一根绳子 char foo abcdefgh 我想写一个for循环 一一打印出所有字符 a b c etc 这是在C中 好吧 这是一个问题 所以我要回答它 但我的答案会有点不寻常 include
  • 为什么 Int32.MaxValue = 2147483648? [复制]

    这个问题在这里已经有答案了 我想知道为什么 NET 中 Int32 的最大可能值是 2147483647 而不是 2147483648 因为 2 1 2147483648 谢谢 An Int32以32位存储 而不是31位 其范围的一半由负数
  • 在 Mac(Chrome 和 Safari)上,文字看起来比 PC 上更粗体,为什么?

    在查看不同的操作系统时 我注意到 尤其是在 Chrome 和 Safari 中 Mac Book 上的文字看起来比 PC 上的文字更粗体 是不是只有浏览器 Web 套件才具有解释 CSS 和 HTML 的能力 而操作系统本身却没有呢 如果上
  • r 将列数未知的长数据整形为宽数据

    我确信这是微不足道的 但我找不到如何做到这一点 我有一个数据框 其中有个人 每个人都可以有多个属性 并且每个属性都以多种方式分类 目前它是长条形的 记录看起来像这样 示意性的形式 实际上有点复杂 IndividualID Property
  • 好的Java图算法库吗? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这个问题的答案是社区努力 help privileges edit community wiki 编辑现有答案以改进这篇文章 目前不接受新
  • 使用 Python 发送特定目录中文件更改的电子邮件通知

    我想编写一个在特定目录中查找的函数的脚本 对于文件 如果有新文件 应该发出通知邮件 我已经准备了一个脚本 它正在目录中查找新文件 它写道 有关新文件的通知进入控制台 但现在我想在有新文件到达时立即通过电子邮件通知 有人可以帮忙吗 impor
  • SQL Server 集成安全性

    我一直在努力研究 SQL Server 中与安全相关的问题 我们正在开发一个面向 SQL Server 2008 的 NET 应用程序 并且我们希望使用 FileStream 现在我发现 如果您使用集成安全性 SQL Server 只允许通
  • FxCop:复合词应被视为离散术语

    FxCop 希望我将用户名拼写为大写 N 即 UserName 因为它是一个复合词 然而 由于一致性原因 我们需要用小写 n 来拼写它 所以要么是用户名 要么是用户名 我尝试通过将以下部分添加到该部分来调整 CodeAnalysisDict
  • 什么是 DepthwiseConv2D 和 SeparableConv2D?它与 keras 中的普通 Conv2D 层有何不同?

    我在查看 EfficientnetB0 的架构时注意到了 DepthwiseConv2D 操作 做了一些挖掘 发现还有一个 SeparableConv2D 这些操作到底是什么 DepthwiseConv2d 执行深度空间卷积的第一步 分别对
  • mysql 在一个查询中计算两个表中的记录?

    我有三个 MySQL 表 patient paID paCode paAccountID foreign key test tsID tsName tsPatientID foreign key tsAccountID foreign ke
  • 如何通过 InMemoryTestHarness 成功驱动 MassTransitStateMachine?

    跟进 如何编写 MassTransitStateMachine 单元测试 https stackoverflow com questions 49763600 how to write masstransitstatemachine uni