【问题标题】:I'm using verify(mockito) here. Is it the correct way of testing my service?我在这里使用验证(模拟)。这是测试我的服务的正确方法吗?
【发布时间】:2020-11-20 14:22:59
【问题描述】:

我有一个与 Spring Boot 中的单元测试相关的问题。我有一个简单的用户管理应用程序,它对用户进行基本的 crud 操作。我使用根据端点在控制器内部调用的服务。例如:

for url = "/createUser" - service.createUser 被调用并处理所有活动(检查用户是否已经存在,以及其他异常处理)并最终将用户添加到数据库中。

现在我要对这个服务进行单元测试,以防创建用户,以下是我的代码:

@Test
    void testRegisterUser() {       
        Set<String> roles = new HashSet<>();        
        signUpRequest.setCustomername("s");
        signUpRequest.setCustomerid("1s");
        signUpRequest.setCustomername("custsomername");
        signUpRequest.setDescription("customser description");
        signUpRequest.setEmail("customser@mail.com");   
        signUpRequest.setPassword("12s3");
        signUpRequest.setRole(roles);  
        service.createUser(signUpRequest);
        verify(repository, times(1)).save(any(UserModel.class));
    }

最后我所做的只是调用服务的方法来创建用户。我已经嘲笑了这个服务和存储库。在最后一行,我正在验证存储库是否已被立即调用。

我想了解的内容是否正确?这是测试它的正确方法吗?我的学长想要在这里断言,但是如果 createUser 没有返回任何值,那么 assertEquals 有什么意义,因为我没有进行集成测试,这里没有数据库。我在这里测试的只是我的逻辑和模仿行为。我将不胜感激。提前致谢。

【问题讨论】:

  • 能否也提供您的存储库的代码,这将很有帮助。

标签: spring-boot junit mockito


【解决方案1】:

但是,您似乎编写了一个正确的测试用例(涵盖了所有服务线),但使用这种方法您可能会错过一些用例,例如 -

  1. 如果在从 SQL 级别保存数据时发生某些异常并抛出到 service ,即使这样,您的测试用例也会验证(在调用方法时),并且您将无法涵盖这种情况。

现在假设你使用了 CrudRepository , S save(S entity) 方法。 它在操作完成后返回对象,并在上述情况下抛出异常。 您实际上应该模拟存储库并设置 return ,然后断言肯定的情况,并期待我前面提到的情况的异常。

希望你觉得这很有用。

【讨论】:

    【解决方案2】:

    我的学长想要在这里断言,但是如果 createUser 没有返回任何值,那么 assertEquals 有什么意义,因为我没有进行集成测试,这里没有数据库。我在这里测试的只是我的逻辑和模仿行为。

    目前您只测试存储库的save 方法是否被一些UserModel 调用。这意味着您不知道UserModel 是否包含它应该包含的所有值。也许您的服务中存在映射错误,因此 request 模型的值未正确转换并映射到 UserModel 或者您稍后引入了这样的错误并且您的测试不会显示它。我想这就是前辈说他想要断言时的意思。

    你可以做的是使用mockito的ArgumentCaptor。它可以帮助您获取传递给存储库的UserModel,以便您可以进行断言。例如

    service.createUser(signUpRequest);
    
    ArgumentCaptor<UserModel> userModelCaptor = ArgumentCaptor.forClass(UserModel.class);
    verify(repository, times(1)).save(userModelCaptor.capture());
    
    UserModel userModel = userModelCaptor.getValue();
    
    // Make assertions - I assume that your UserModel has an accessor method for email
    assertEquals("customser@mail.com", userModel.getEmail());
    

    【讨论】:

      【解决方案3】:

      根据我的经验,这里有一些关于自动化测试的指南 -

      1. 在任何时候,我们都会尝试实现 100% 的自动化测试覆盖率。它可以使用集成测试或单元测试。如果可以使用单元测试来测试场景或功能,那么我更愿意选择单元测试而不是集成测试,原因如下 -
      • 单元测试总是快速运行
      • 理想情况下,单元测试不应依赖于外部依赖项,例如数据库或外部服务或特殊基础架构。每个单元测试用例都应该专注于测试尽可能小的功能。这将确保您可以在任何时间点运行单元测试用例,而无需依赖外部依赖项。作为单元测试的一部分,我们应该模拟所有其他依赖项。
      • 管理单元测试用例比管理集成测试用例以应对未来的任何功能更改要容易得多。
      1. 有时在我们尝试集成测试用例的场景中,仅使用单元测试用例很难实现 100% 的测试覆盖率。如果您的应用程序具有 UI 交互,那么仅使用单元测试用例无法测试这些交互,因此我们将通过集成测试用例来涵盖这些场景。

      2. 让我们举个例子 - 如果您的服务方法有两种不同的可能场景,并且您的方法是由浏览器上的用户操作调用的。作为集成测试的一部分,您可以涵盖一个负责 UI 功能的场景。您的服务方法中的其他场景可以仅使用单元测试用例来覆盖。这将确保可以测试您的整个功能。

      3. 理想情况下,我们会尝试实现 100% 的自动化测试覆盖率,但在某些情况下,当工程/基础设施成本超过自动化测试用例提供的价值时,我们会尝试避免这种情况。这是一个有意识的权衡。

      4. 由于缺乏资源,一些团队可能不会优先考虑 100% 的测试覆盖率。

      现在回到您的场景,如果不查看您的服务方法代码,很难给您一个满意的答案。您的单元测试用例仅检查您的服务方法是否正在调用仅调用一次的存储库方法。如果这涵盖了您的应用程序的全部功能,那么您一切都很好。没有错误或正确的方法,只要您的单元案例涵盖最多的功能并且易于维护,那么您就是好的。

      【讨论】:

        猜你喜欢
        • 2023-02-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-08
        • 1970-01-01
        • 2013-04-25
        相关资源
        最近更新 更多