C# 单元测试检索方法-冗余?

C# 单元测试检索方法-冗余?,c#,.net,unit-testing,mocking,xunit.net,C#,.net,Unit Testing,Mocking,Xunit.net,在我的服务层中有以下方法 public ModuleResponse GetModules(ModuleRequest request) { var response = new ModuleResponse(request.RequestId); try { response.Modules = Mapper.ToDataTransferObjects(ModuleDao.GetModules()); return response;

在我的服务层中有以下方法

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);
}
public ModuleResponse GetModules(ModuleRequest request)
{
    return _responder.CreateMappedDtoResponse(
        request,
        ModuleDao.GetModules,
        modules => new ModuleResponse {Modules = modules}));
}
[事实]
public void getModuleTest()
{
//安排
var mockModuleDao=Mock.Create();
var mockLog=Mock.Create();
var mockAuditDao=Mock.Create();
var moduleList=新列表
{
新模块项{Id=100,Category=“用户帐户”,Feature=“用户”},
新模块项{Id=101,Category=“用户帐户”,Feature=“角色权限”}
};
Arrange(dao=>dao.GetModules()).Returns(moduleList);
IUserManagementService userService=新的UserManagementService(mockModuleDao、mockLog、mockAuditDao);
var request=newmodulerequest().Prepare();
//表演
var actualResponse=userService.GetModules(请求);
//断言
Assert.Equal(AcknowledgeType.Success,actualResponse.Acknowledge);
等于(2,实际响应模块数);
}
现在我的代码中有一大堆其他的检索方法,类似于上面的一种

测试这些方法是否多余?我的意思是,除非我弄乱了映射的逻辑或其他什么,否则它们几乎肯定是通过测试的

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


这是否足够?或者,如何进一步提高这种单元测试的价值?

一如既往,这样的测试是否有价值取决于您的测试动机

  • 这段代码是关键任务吗
  • 如果代码失败,成本是多少
  • 如果出现错误,您能多容易地解决错误
失败的代价越高,测试一段代码就越重要

GetModules
方法至少做四件事:

  • 它从DAO返回模块
  • 它将DAO中的模块映射到所需的返回类型
  • 如果出现问题,它将返回错误消息
  • 它记录可能发生的任何错误
GetModulesTest
测试这四项职责中的一项,这意味着还需要另外三项测试才能完全覆盖
GetModules
方法

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

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

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

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

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

可以想象,您可以将此方法(及其所有同类)转化为类似的方式:

[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);
}
public ModuleResponse GetModules(ModuleRequest request)
{
    return _responder.CreateMappedDtoResponse(
        request,
        ModuleDao.GetModules,
        modules => new ModuleResponse {Modules = modules}));
}
现在,在这一点上,我认为你可以对这种方法的单元测试提出一个合理的论点。您将测试此方法的实现,而不是其行为。您的单元测试将测试您使用给定的参数调用给定的方法,就这样

但是,即使你决定做一个纯粹的单元测试,你也可以想象只有一个单元测试可以编写,而之前你需要编写四个单元测试来全面介绍这个方法。然后为
CreateMappedDtoResponse
方法(以及它可能将部分工作委托给的任何方法)编写适当的单元测试,就得到了一个干燥、经过良好测试的系统,测试数量只占测试数量的一小部分。如果您更改了一个常见的职责,比如异常日志策略,您可以在一个地方更改它,更改一个单元测试,然后就可以完成了


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

您是正确的。您的测试实际上并没有测试您认为它正在测试的内容-您实际上是在测试您的映射。将测试的范围限制在映射上,测试就有价值。因此,一般来说,除非我的检索方法包含某种行为逻辑,否则它们是多余的?以我的经验。。对除非您在服务和映射器之间执行一些集成测试(即检查服务方法是否调用了某些方法),否则它是多余的。现在,如果您的服务方法实际上有其他逻辑,那么当然,测试它们。。但我在这里看到的只是对映射的测试。纯TDD无论如何都会声明测试它(我认为)。。但我个人不这么认为。TDD纯粹主义者登记入住。:-)正如@SimonWhitehead所猜测的,我认为您应该进行这些测试,但原因如下:它们将您的注意力吸引到了冗余代码/功能上(我敢打赌,冗余的不仅仅是测试)。我们可以将其视为一种必要的邪恶——在某些情况下确实如此——或者我们可以重新审视我们的设计,并考虑如何重构它。还有未来的变化争论。如果您编写了一个测试,则该方法的未来更改将包含在测试中。。惠尔