【问题标题】:How do you make methods relying on extension methods testable?您如何使依赖于扩展方法的方法可测试?
【发布时间】:2009-05-28 01:23:25
【问题描述】:

我有一个带有以下签名的扩展方法(在 BuildServerExtensions 类中)::

public static IEnumerable<BuildAgent> GetEnabledBuildAgents(
                                          this IBuildServer buildServer,
                                          string teamProjectName)
{
    // omitted agrument validation and irrelevant code
    var buildAgentSpec = buildServer.CreateBuildAgentSpec(teamProjectName);
}

还有另一个调用第一个方法(在 BuildAgentSelector 类中):

public BuildAgent Select(IBuildServer buildServer, string teamProjectName)
{
    // omitted argument validation
    IEnumerable<BuildAgent> serverBuildAgents = 
        buildServer.GetEnabledBuildAgents(teamProjectName);

    // omitted - test doesn't get this far
}

我正在尝试使用 MSTest 和 Rhino.Mocks (v3.4) 对其进行测试:

[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
    Mocks = new MockRepository();
    IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();

    BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
    using (Mocks.Record())
    {
        Expect.Call(buildServer.GetEnabledBuildAgents(TeamProjectName)).Return(null);
    }

    using (Mocks.Playback())
    {
        BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);

        Assert.IsNull(buildAgent);
    }
}

当我运行这个测试时,我得到:

System.InvalidOperationException:

之前的方法 IBuildServer.CreateBuildAgentSpec("TeamProjectName"); 需要返回值或抛出异常。

这显然是调用真正的扩展方法而不是测试实现。我的下一个倾向是尝试:

Expect.Call(BuildServerExtensions.GetEnabledBuildAgents(buildServer, TeamProjectName))
      .Return(null);

然后我注意到我对 Rhino.Mocks 拦截它的期望可能是错误的。

问题是:如何消除这种依赖并使 Select 方法可测试?

请注意,扩展方法和 BuildAgentSelector 类在同一个程序集中,我宁愿避免更改它或不得不转向扩展方法之外的其他东西,尽管如果我知道另一个模拟框架会处理这个问题,我会考虑它情况。

【问题讨论】:

    标签: unit-testing c#-3.0 tdd extension-methods rhino-mocks


    【解决方案1】:

    您的扩展方法实际上写得很好。它是一种无副作用的方法,并且正在扩展一个接口,而不是一个具体的类。你快到了,但你只需要走得更远一点。您正在尝试模拟 .GetEnabledBuildAgents(...) 扩展方法...但这实际上并不是可模拟的(除了 TypeMock Isolator 之外的任何东西,这是目前唯一可以实际模拟静态的东西...但是它相当昂贵.)

    您实际上对模拟您的扩展方法在内部调用的 IBuildAgent 方法感兴趣:.CreateBuildAgentSpec(...)。如果您考虑清楚,模拟 CreateBuildAgentSpec 方法将解决您的问题。扩展方法是“纯粹的”,所以真的不需要被嘲笑。它没有状态,也没有副作用。它调用 IBuildAgent 接口上的单个方法...这是指导您真正需要模拟的第一个线索。

    尝试以下方法:

    [TestMethod]
    public void SelectReturnsNullOnNullBuildAgents()
    {
        Mocks = new MockRepository();
        IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();
    
        BuildAgent agent = new BuildAgent { ... }; // Create an agent
        BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
        using (Mocks.Record())
        {
            Expect.Call(buildServer.CreateBuildAgentSpec(TeamProjectName)).Return(new List<BuildAgent> { agent });
        }
    
        using (Mocks.Playback())
        {
            BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);
    
            Assert.IsNull(buildAgent);
        }
    }
    

    通过创建一个 BuildAgent 实例,并在 List 中返回它,您可以有效地返回一个您的 Select 方法可以操作的 IEnumerable。那应该让你继续前进。如果仅仅返回一个基本的 BuildAgent 实例是不够的,或者如果您需要多个实例,您可能需要做一些额外的模拟。当谈到要返回的模拟结果时,Rhino.Mocks 可能是一个真正的痛苦。如果您遇到麻烦(根据我的经验,您很可能会遇到麻烦),我推荐您give Moq a try,因为它是一个更好且对测试人员更友好的框架。它不需要存储库,并且消除了 Rhino.Mocks 所需的 Record/Playback 和 using() 语句重符号。 Moq 还提供了其他框架尚未提供的额外功能,一旦您进入更繁重的模拟场景,您就会爱上(即 It.* 方法。)

    希望这会有所帮助。

    【讨论】:

      【解决方案2】:

      在休息并带着新的头脑回来之后,我意识到我实际上在 BuildAgentSelector 类中混合了一些关注点。我正在获取代理并选择他们。通过分离这两个关注点并将代理直接传递给 BuildAgentSelector 构造函数(或这样做的委托/接口),我能够分离关注点,删除对 buildServer 和 teamProjectName 参数的依赖,并简化接口进行中。它还实现了我在 BuildAgentSelector 类中寻找的可测试性结果。我也可以很好地单独测试扩展方法。

      但是,最终只是将测试问题转移到了别处。更好,因为关注的位置更好,但是 jrista 的答案可以解决问题,无论关注的位置在哪里。

      不得不模拟被测代码下面的第二层仍然有点难看。我基本上必须从我的扩展方法测试中模拟成功路径,并在我的其他测试中重用此代码 - 不难,但有点烦人。

      我会尝试 MOQ 并小心不要对编写扩展方法过于满意。

      【讨论】:

        【解决方案3】:

        测试调用真正的扩展方法,因为它是唯一的。模拟 IBuildServer 时不会创建测试实现,因为该方法不是 IBuildServer 的成员。

        使用您现在的设置没有干净的解决方案。

        理论上 TypeMock 会模拟静态类,但重构扩展方法会提供更好的可测试性。

        【讨论】:

          猜你喜欢
          • 2016-12-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多