前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你会写测试代码吗?

你会写测试代码吗?

作者头像
凯哥的Java技术活
发布2022-07-08 14:07:45
6130
发布2022-07-08 14:07:45
举报

测试是企业软件开发不可缺少的一部分。

翻开任何一个优秀的开源框架源码,会发现在测试的包里面有不亚于源码的代码量。如何快速的编写出针对性的测试代码,也是一门绝活。

这里不展开讲解Mockito等测试框架,只针对Spring Boot应用,给出Spring Boot开发中常用的测试方法,帮助你进行快速测试开发。

导入依赖


Maven

代码语言:javascript
复制
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

Gradle

代码语言:javascript
复制
testImplemention "org.springframework.boot:spring-boot-starter-test"

注解


@SpringBootTest : 从当前的标记该注解的测试类开始找,直至找到@SpringBootApplication或者@SpringBootConfiguration。就从标记了上述两个注解的类开始扫描bean。

也就是说,你可以在Test类里面自定义项目启动类。

比如:

下面是你的项目启动入口

代码语言:javascript
复制
@SpringBootApplication
@Import(ClassA.class)
public class DemoSpringApplication {

  public static void main(String[] args) {
    new SpringApplication(DemoSpringApplication.class).run(args);
  }
}

如果你的测试类如下

代码语言:javascript
复制
@SpringBootTest
public class WhereToScanTest {

  @Test
  void works(@Autowired ApplicationContext applicationContext){
    Assertions.assertThat(applicationContext.getBean(ClassA.class)).isNotNull();
  }
  
}

那么DemoSpringApplication就会是应用启动类,能够测试通过。

但是如果,你在WhereToScanTest该包下创建一个@SpringConfiguration注解的类,只是简单加上一个@SpringBootConfiguration注解,测试就会失败。

代码语言:javascript
复制
@SpringBootTest
public class WhereToScanTest {

  @Test
  void works(@Autowired ApplicationContext applicationContext){
    Assertions.assertThat(applicationContext.getBean(ClassA.class)).isNotNull();
  }

  @SpringBootConfiguration
  static class MyConfig{

  }
}

这是因为只扫描标记了@SpringBootConfiguration的MyConfig类下的包,并不会创建ClassA的bean。

使用参数


代码语言:javascript
复制
@SpringBootTest(args = "--app.test=true")
public class ArgumentTest {

  @Test
  void argsWorks(@Autowired ApplicationArguments args){
    assertThat(args.getOptionNames().contains("app.test"));
    assertThat(args.getOptionValues("app.test")).containsOnly("true");
  }
}

测试web应用程序


如果只是用@SpringBootTest注解,不会开启web环境,如果想要测试web代码,可以加上@AutoConfigureMockMvc注解。

实例代码:

代码语言:javascript
复制
@RestController
public class DemoController {

  @RequestMapping("/hello")
  public String hello(){
    return "Hello from demo";
  }
}
代码语言:javascript
复制
@SpringBootTest
@AutoConfigureMockMvc
public class MockMvcTest {

  @Test
  void mockMvcWorks(@Autowired MockMvc mockMvc) throws Exception {
    mockMvc.perform(get("/hello")).andExpect(status().isOk()).andExpect(content().string("Hello from demo"));
  }

}

注意:这会扫描所有的spring注解并实例化完整的ApplicationContext,也就是启动整个Spring应用,如果你想只测试mvc部分,可以考虑使用@WebMvcTest。该测试是启动一个mock的web环境。

如果想测试真实的sever,使用如下注解,推荐使用一个随机端口进行测试。

代码语言:javascript
复制
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class RunningServerTest {

  private WebTestClient webTestClient;

  @LocalServerPort
  private int port;

  private String url;

  @BeforeEach
  void before(){
    this.url = "http://localhost:" + port;
    this.webTestClient = WebTestClient.bindToServer().responseTimeout(Duration.ofSeconds(10)).baseUrl(url).build();
  }

  @Test
  void runningServerWorks(@Autowired WebTestClient webTestClient){
    this.webTestClient.get().uri("/hello").exchange().expectStatus().isOk().expectBody().equals("Hello from demo");
  }
}

其中@LocalServerPort会将程序启动的端口注入到port字段里去。

Mock


适合哪种情况?某些服务在开发环境无法调用,那么就需要mock,mock意思是模拟,也就是说模拟某些bean来进行你想要的测试。

例如你定义了一个远程访问的service,但是开发环境无法调通,则可以模拟。

@MockBean的用法

代码语言:javascript
复制
@SpringBootTest
public class MockBeanTests {

  @MockBean
  private RemoterService remoterService;

  @Test
  void mockBeanWorks(){
    //如果调用sayHello方法,返回Hello mock bean
    given(remoterService.sayHello()).willReturn("Hello mock bean");
    String s = this.remoterService.sayHello();
    assertThat(s).isEqualTo("Hello mock bean");
  }
}

@MockBean 向测试程序注入了一个RemoteService的Bean,但是具体怎么定义方法怎么执行是需要你来说明的。其中given()方法是Mockito测试框架的方法,意思是如果调用remoteService的sayHello方法,就返回“Hello mock bean”。

分模块测试(WebMVC)


如果使用SpringBootTest,就是扫描整个应用内的bean。在一个项目中可能有很多的Spring Boot Starter,例如只想测试mvc,而不想测试jdbc,那么就需要使用@...Test。

使用@WebMvcTest注解,只会自动配置webmvc相关的功能,只会扫描如下的Bean。

@Controller

@ControllerAdvice

@JsonComponent

Converter

Filter

WebMvcConfigure

...

指定只测试某个Controller:

代码语言:javascript
复制
@WebMvcTest(UserController.class)
public class MockMvcTest {

  @MockBean
  private UserService userService;

  @Autowired
  private MockMvc mvc;

  @Test
  void mockMvcWorks(@Autowired MockMvc mockMvc) throws Exception {
    mockMvc.perform(get("/hello")).andExpect(status().isOk()).andExpect(content().string("Hello from demo"));
  }

  @Test
  void mcvWorks() throws Exception{
    given(userService.getUserName()).willReturn("Tyler");
    this.mvc.perform(get("/user/name")).andExpect(status().isOk()).andExpect(content().string("Tyler"));
  }

}

@WebMvcTest(xxxController.class) 只向web中添加该controller,例如该例子只会有UserController,如果还有其他Controller定义其他的@RequestMapping,在测试程序中访问是会404,因为这里我们只定义加载了UserController。

分模块测试(Data JPA )


和上面的mvc模块一样,@DataJpaTest也是只开启JPA相关自动配置,只扫描@Entinty和JpaRepository。使用@DataJpaTest在会回退事务,所以不用担心会向数据库插入无效的数据,默认该注解会使用内嵌的内存数据库,如果想要使用你本地的例如localshot:3306数据库,需要使用如下注解。

可以注入TestEntityManager进行一些操作,也可以注入测试自定义的Repository。

代码语言:javascript
复制
@AutoConfigureTestDatabase(replace = Replace.NONE)
代码语言:javascript
复制
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    void testExample() throws Exception {
        this.entityManager.persist(new User("sboot", "1234"));
        User user = this.repository.findByUsername("sboot");
        assertThat(user.getUsername()).isEqualTo("sboot");
        assertThat(user.getEmployeeNumber()).isEqualTo("1234");
    }

}

Spring其他测试方法


如果你什么注解也不想用,既不想测试Data JPA 也不想测试 mvc,只是想注册几个bean,然后启动做些测试,那么也可以用下面两个类。

可以用ApplicationContextRunner,该类是一个标准的,无web的环境。

可以直接用ApplicationContext,该类是Spring为应用程序提供配置的核心接口,例如AnnotationConfigApplicationContext。

用法如下:

代码语言:javascript
复制
public class ApplicationContextRunnerTests {

  private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();

  @Test
  void works(){
    contextRunner.withBean("classa",ClassA.class).run((context -> {
      ClassA bean = context.getBean(ClassA.class);
      assertThat(context).hasBean("classA");
    }));
  }
}
代码语言:javascript
复制
public class AnnotationConfigTests {

  private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

  @Test
  void works(){
    this.context.register(ClassA.class);
    this.context.refresh();
    Assertions.assertThat(this.context.containsBean("classA")).isTrue();
  }
}

总结


能够写出有针对性的测试代码,其实也不是一件容易的事,如果你对代码质量有较高要求,代码层面测试是不可缺少的一部分。希望这篇文章能帮到你一二。这里只是大概列出了一些测试案例,养成代码测试的习惯,更多测试的技巧可以在不断的测试中自己挖掘。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-05-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 凯哥的Java技术活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档