【问题标题】:Unit testing retrieval methods - redundant?单元测试检索方法——冗余?
【发布时间】:2014-06-07 15:27:20
【问题描述】:

我的服务层有以下方法

public ModuleResponse GetModules(ModuleRequest request)
{
    var response = new ModuleResponse(request.RequestId);
    try
    {
        response.Modules = Mapper.ToDataTransferObjects(ModuleDao.GetModules());
        return response;
    }
    catch (Exception ex)
    {
        Log.Error(ex);
        response.Acknowledge = AcknowledgeType.Failure;
        response.Message = "An error occured.";
        return response;
    }
}

我有一个用 xUnit 编写的单元测试,如下所示:

[Fact]
public void GetModulesTest()
{
    //Arrange            
    var mockModuleDao = Mock.Create<IModuleDao>();
    var mockLog = Mock.Create<ILog>();
    var mockAuditDao = Mock.Create<IAuditDao>();

    var moduleList = new List<ModuleItem>
    {
        new ModuleItem {Id = 100, Category = "User Accounts", Feature = "Users"},
        new ModuleItem {Id = 101, Category = "User Accounts", Feature = "Roles Permissions"}
    };

    mockModuleDao.Arrange(dao => dao.GetModules()).Returns(moduleList);

    IUserManagementService userService = new UserManagementService(mockModuleDao, mockLog, mockAuditDao);

    var request = new ModuleRequest().Prepare();

    //Act
    var actualResponse = userService.GetModules(request);

    //Assert
    Assert.Equal(AcknowledgeType.Success, actualResponse.Acknowledge);
    Assert.Equal(2, actualResponse.Modules.Count);
}

现在我的代码中有一大堆检索方法,类似于上面的方法。

测试这些方法是多余的吗?我的意思是,它们几乎可以肯定通过测试,除非我搞砸了我的映射逻辑或其他东西。

另外,在测试检索方法时,我应该测试什么?在我上面的场景中,我有 2 个断言语句,1 个用于检查响应是否成功,第 2 个是检查列表的计数。

这就足够了吗?或者如何进一步改进以提高这种单元测试的价值?

【问题讨论】:

  • 你是正确的。您的测试实际上并没有测试您认为它正在测试的内容 - 您实际上是在测试您的映射。将您的测试范围限制为映射,并且测试具有价值。
  • 所以一般来说,除非我的检索方法包含某种行为逻辑,否则它们是多余的?
  • 根据我的经验.. 是的。除非您在服务和映射器之间执行一些集成测试——即检查某些方法是否被服务方法调用——否则它是多余的。现在,如果您的服务方法实际上有其他逻辑,那么当然,测试它们.. 但我在这里看到的只是对映射的测试。纯 TDD 无论如何都会声明测试它(我认为).. 但我个人不会。
  • TDD 纯粹主义者检查。:-) 正如@SimonWhitehead 所猜测的,我认为您应该进行这些测试,但原因如下:它们引起了您的注意冗余代码/功能(我敢打赌,不只是 测试 是冗余的)。我们可以把它写成必要的邪恶——在某些情况下确实如此——或者我们可以重新检查我们的设计并考虑可以重构它的方式。
  • 还有未来变化的说法。如果您编写了一个测试,将来对此方法的更改将被覆盖。而如果您没有编写一个,则可以进行更改并且您的测试覆盖率会降低。这实际上是足以让我改变看法的理由。我在此收回我之前所说的一切:)

标签: c# .net unit-testing mocking xunit.net


【解决方案1】:

现在我的代码中有一大堆检索方法,类似于上面的方法。

真的吗?他们不觉得有点……重复吗?

我认为 Lilshieste 提出了一个非常恰当的观点,即单元测试的一个内在价值是它们突出了此类可维护性问题。你可能会说它们让代码闻起来更刺鼻。

Mark Seemann 为您向我们展示的这一方法确定了四个个人责任。单一职责原则将规定你应该只拥有一个。

您可以想象将这种方法(及其所有同类方法)变成更像这样的东西:

public ModuleResponse GetModules(ModuleRequest request)
{
    return _responder.CreateMappedDtoResponse(
        request,
        ModuleDao.GetModules,
        modules => new ModuleResponse {Modules = modules}));
}

现在,在这一点上,我认为您可以提出一个体面的论据来反对对这种方法进行单元测试。你几乎是在测试这个方法的实现,而不是它的行为。您的单元测试将测试您使用给定参数调用给定方法,就是这样!

但是,即使您决定成为一个纯粹主义者并对此进行单元测试,您实际上也只能编写一个单元测试,而不是之前完全涵盖此方法所需的四个单元测试。然后,您为CreateMappedDtoResponse 方法(以及它可能将部分工作委托给的任何方法)编写适当的单元测试,并且您拥有一个干燥的、经过良好测试的系统,其测试数量只是其中的一小部分。如果你改变了一个共同的责任,比如你的异常记录策略,你可以在一个地方改变它,改变一个单元测试,然后就完成了。

因此,即使您的单元测试从未为您发现错误,作为一个纯粹主义者也可以帮助您避免可维护性问题,该问题会迫使您首先编写同样多的额外代码,并且可能会重新编写以后的代码一样多。当然,只有在您知道要听取单元测试并相应地更改设计时才会发生这种情况。

【讨论】:

    【解决方案2】:

    与往常一样,这样的测试是否有价值取决于您进行测试的动机。

    • 这段代码是关键任务吗?
    • 如果该代码失败,费用是多少?
    • 如果出现错误,您解决错误的难易程度如何?

    失败的成本越高,测试一段代码就越重要。

    GetModules 方法至少做了四件事:

    • 它从 DAO 返回模块。
    • 它将 DAO 中的模块映射到所需的返回类型。
    • 如果出现问题,它会返回错误消息。
    • 它会记录任何可能发生的错误。

    GetModulesTest 测试了这四个职责中的一个,这意味着仍然需要另外三个测试才能完全覆盖GetModules 方法。

    编写小粒度的单元测试很有价值,因为它可以让您将一段复杂的生产代码分解为一组简单、易于理解的单元测试。有时,这些单元测试变得非常简单,以至于你会开始怀疑它的价值,但价值不在单个单元测试中——而是在简单测试的积累中,它们一起指定整个系统应该如何工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-17
      • 1970-01-01
      • 2019-10-03
      • 1970-01-01
      • 2011-08-17
      相关资源
      最近更新 更多