65.9K
CodeProject 正在变化。 阅读更多。
Home

FakeItEasy 中的陷阱

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2012 年 3 月 8 日

CPOL

2分钟阅读

viewsIcon

10954

FakeItEasy 中的一个陷阱及其解决方法

我目前正在进行一个基于 CQRS 的项目,使用 ncqrs 框架。今天,我进入了一个需要为一些领域服务进行模拟的阶段。我选择了 FakeItEasy 来处理这件事。所以我首先通过 NuGet 包将引用添加到我的 DomainScenario 项目。

对于我的一些复杂场景,我遇到了一些问题,修复其中一个测试花费了我大约 45 分钟。为了修复所有其他失败的测试,我不得不使用相同的技巧。

在解释我在 FakeItEasy 中遇到的问题之前,我必须提到我使用聚合根来准备我的事件存储。我的问题与这种特定的事件存储准备方式有关。

例如,我有一个如下所示的 Participant 聚合根

public class Participant : BisAggregateRoot
{
    private readonly IUniqueParticipantNrGuard _uniqueFormattedParticipantNrVerifier = 
            NcqrsEnvironment.Get<IUniqueParticipantNrGuard>();

    //other members left for convenience
    private string _participantNrFormat;
    private int _participantNr;
    private Gender _gender;
    private PartialDate _birthDate;
    private string _remark;

    public Guid ParticipantId { get { return EventSourceId; } }

    // ReSharper disable UnusedMember.Local
    private Participant()
    // ReSharper restore UnusedMember.Local
    {
    }

    public Participant(Guid participantId, ScientificStudy scientificStudy, 
        string participantNrFormat, 
        int participantNr, Gender gender, PartialDate birthdate)
        : base(participantId)
    {
        _uniqueFormattedParticipantNrVerifier.GuardIsUnique
        (scientificStudy.ScientificStudyId, participantId, 
        string.Format(participantNrFormat, participantNr));
        ApplyEvent(new ParticipantAddedEvent(Credentials)
                       {
                           ParticipantId = participantId,
                           ParticipantNrFormat = participantNrFormat,
                           ParticipantNr = participantNr,
                           ScientificStudyId = scientificStudy.ScientificStudyId,
                           Gender = gender.ActualValue,
                           Birthdate = birthdate.ActualValue
                       });
    }

    public void Edit(ScientificStudy scientificStudy, string participantNrFormat, 
           int participantNr, Gender gender, PartialDate birthdate, string remark)
    {
        //Some other business rules
        _uniqueFormattedParticipantNrVerifier.GuardIsUnique
        (scientificStudy.ScientificStudyId, ParticipantId,
         string.Format(participantNrFormat, participantNr));
        //Some other business rules

        ApplyEvent(new ParticipantEditedEvent(Credentials)
            {
                ParticipantId = ParticipantId,
                ParticipantNrFormat = participantNrFormat,
                ParticipantNr = participantNr,
                Gender = gender.ActualValue,
                Birthdate = birthdate.ActualValue
            });
    }

    protected void OnParticipantAdded(ParticipantAddedEvent e)
    {
        _claimedAliquots = new List<ClaimedAliquot>();
        _participantNrFormat = e.ParticipantNrFormat;
        _participantNr = e.ParticipantNr;
        _gender = new Gender(e.Gender);
        _birthDate = new PartialDate(e.Birthdate);
    }

    protected void OnParticipantEdited(ParticipantEditedEvent e)
    {
        _participantNrFormat = e.ParticipantNrFormat;
        _participantNr = e.ParticipantNr;
        _gender = new Gender(e.Gender);
        _birthDate = new PartialDate(e.Birthdate);
        _remark = e.Remark;
    }
}

正如你所看到的,这个聚合的编辑和构造都使用了相同的领域服务来验证参与者编号是否唯一。请记住这一点,因为这与我的 eventstore 准备方式有关,因为它与问题相关。

对于我的 eventstore,我使用我构建的一些流式构建器,它使用 domainobjects 来生成实际的事件。

public static class New
{
    public static ScientificStudy ScientificStudy(string nr = "M001", 
      string name = "Maastricht Study", 
      string participantNrFormat = "P{0}", string boxNameFormat = "B{0}")
    {
        return new ScientificStudy(Default.Id.For.ScientificStudy, nr, 
                   name, participantNrFormat, boxNameFormat);
    }

    public static Participant Participant(ScientificStudy scientificStudy, 
      string participantNrFormat = "P{0}", int participantNr = 1, 
      int gender = 0, string birthdate = "19861117")
    {
        var realGender = new Gender(gender);
        var realBirthDate = new PartialDate(birthdate);
        return new Participant(Default.Id.For.Participant, scientificStudy, 
               participantNrFormat, participantNr, realGender, realBirthDate);
    }

    //other members left for convenience
}

这个构建器在我的测试中像下面这样使用。请注意,GetChanges 方法是我的 aggregateRoots 上的一个扩展方法。

protected override void SetupDependencies()
{
    base.SetupDependencies();

    var eventStore = NcqrsEnvironment.Get<IEventStore>();
    var scientificStudy = New.ScientificStudy();
    var scientificStudyEvent = scientificStudy.TrackChanges().GetChanges();
    var participantEvents = New.Participant(scientificStudy).GetChanges();
    eventStore.Store(Prepare.Events(scientificStudyEvent).ForSourceUncomitted(
                     Default.Id.For.ScientificStudy, Default.Id.Random()));
    eventStore.Store(Prepare.Events(participantEvents).ForSourceUncomitted(
                     Default.Id.For.Participant, Default.Id.Random()));
}

对于我想要执行的测试,我首先像下面这样注册了我的 Fakes,这导致我的测试失败。我试图测试编辑是否会在将 participantNr 更改为已存在的 participantNr 时引发 ‘FormattedParticipantNrShouldBeUniqueException’。

protected override void RegisterFakesInConfiguration(EnvironmentConfigurationWrapper configuration)
{
    var commandService = new CommandService();
    commandService.RegisterExecutor(new EditParticipantCommandExecutor());

    var uniqueParticipantGuard = A.Fake<IUniqueParticipantNrGuard>();
    A.CallTo(uniqueParticipantGuard).DoesNothing().Once();
    A.CallTo(uniqueParticipantGuard).Throws(
                new FormattedParticipantNrShouldBeUniqueException(_formattedParticipantNr));

    configuration.Register<ICommandService>(commandService);
    configuration.Register(uniqueParticipantGuard);

    //other fakes left for convenience

    base.RegisterFakesInConfiguration(configuration);
}

经过 45 分钟的搜索和与同事的讨论,我们找到了解决方案。

protected override void RegisterFakesInConfiguration
(EnvironmentConfigurationWrapper configuration)
{
    var commandService = new CommandService();
    commandService.RegisterExecutor(new EditParticipantCommandExecutor());

    var uniqueParticipantGuard = A.Fake<IUniqueParticipantNrGuard>();
    A.CallTo(uniqueParticipantGuard).Throws(
      new FormattedParticipantNrShouldBeUniqueException(_formattedParticipantNr));
    A.CallTo(uniqueParticipantGuard).DoesNothing().Once();

    configuration.Register<ICommandService>(commandService);
    configuration.Register(uniqueParticipantGuard);

    //other fakes left for convenience

    base.RegisterFakesInConfiguration(configuration);
}

正如你所看到的,解决方案非常简单,但很难找到,因为调试并没有突出显示这是问题所在。

只需切换配置 Mocked/Faked 对象的顺序。

最后,我向你展示我的实际测试方法。

[Then]
public void it_should_throw_an_formatted_participant_nr_
            should_be_unique_exception_containing_the_formatted_participant_nr()
{
    var exception = (FormattedParticipantNrShouldBeUniqueException)CaughtException;
    exception.FormattedParticipantNr.Should().Be(_formattedParticipantNr);
}

我不确定我是否只是操作不当,或者 FakeItEasy 是否需要修复以防止你遇到这个问题。希望我能清楚地说明这个问题,并在你遇到相同问题时为你节省一些时间。

© . All rights reserved.