我在这里使用验证(模拟)。这是测试我的服务的正确方法吗?



我有一个关于Spring引导中的单元测试的问题。我有一个简单的用户管理应用程序,它可以对user进行基本的crud操作。我使用的服务在控制器内部调用,具体取决于端点。例如:

对于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));
}

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

我想了解的是,我的做法是否正确?这是测试它的正确方法吗?我的Senior想在这里断言,但如果createUser没有返回任何值,assertEquals有什么意义,因为我没有进行集成测试,这里没有数据库。我在这里测试的只是我的逻辑和模仿行为。如果能给我一个详尽的答复,我将不胜感激。提前谢谢。

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

  1. 如果在保存SQL级别的数据时发生异常,并将其抛出到服务,即使这样,您的测试用例也会进行验证(方法调用时),并且您将无法覆盖此场景

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

希望你觉得这个有用。

我的Senior想要在这里断言,但如果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());

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

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

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

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

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

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

最新更新