unit-testing - 您如何使依赖于扩展方法的方法可测试?

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

我有一个具有以下签名的扩展方法(在 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:

Previous method IBuildServer.CreateBuildAgentSpec("TeamProjectName"); requires a return value or an exception to throw.



这显然是在调用真正的扩展方法而不是测试实现。我的下一个倾向是尝试:
Expect.Call(BuildServerExtensions.GetEnabledBuildAgents(buildServer, TeamProjectName))
      .Return(null);

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

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

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

最佳答案

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

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

尝试以下操作:

[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.* 方法。)

希望这可以帮助。

关于unit-testing - 您如何使依赖于扩展方法的方法可测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/918776/

相关文章:

Activity 有动画时的Android单元测试

c# - 如何在不同的测试数据存储格式之间进行选择?

c++ - 是否可以将 MS 单元测试从 EXE 转发到 DLL?

C# 匿名类型不能分配给——它是只读的

c# - 在c#中将结构转换为char指针

javascript - Mocha测试-解决assertion_error

Django 单元测试 : threads in TestCase do not see the records but TransactionTestCase ones do

java - 计算单元测试中的方法调用次数

unit-testing - 服务和 DAO 层的 JUNIT 测试

c# - 将 ASP.NET 成员资格提供程序数据库与您自己的数据库一起使用?