1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 单元测试规范流程

单元测试规范流程

时间:2021-06-23 13:47:27

相关推荐

单元测试规范流程

目录导航

一.测试用例编写规范及概念 1、单元测试主要任务2、单元测试的步骤3、测试数据制作4、评价维度5、评价手段6、代码覆盖率 二.实施方案 1、idea安装junit插件2、添加pom依赖:3、命名4、几种常用的注解(导org.junit.jupiter包)5、断言6、参数化测试7、MockMvc使用(模拟controller请求接收)8、几个方法的简单说明:9、增加app服务的验证签名之后的junit修改 三.CI流程中需要增加的项目 1、pom依赖2、profile3、测试代码中profile的使用4、测试数据的规范

一.测试用例编写规范及概念

1、单元测试主要任务

2、单元测试的步骤

3、测试数据制作

4、评价维度

5、评价手段

6、代码覆盖率

二.实施方案

1、idea安装junit插件

idea整合junit5之前,先安装配置junit插件:/nicolas12/article/details/81223938

2、添加pom依赖:

<!--junit5依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.3.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.3.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-params</artifactId><version>5.3.2</version><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.2</version><scope>test</scope></dependency>

3、命名

工具生成的名字一般是在测试的方法名前面加test及首字母大写,比如addApps -> testAddApps

但是一般测试不同的条件场景情况较多,所以一般建议不同的入参命名规则为:前面的名字不变,后面加入参的条件,举例为testAddAppsWhenChannelIdIsZero和testAddAppsWhenChannelNotExist就是不同的情况用When连接不同的条件。

4、几种常用的注解(导org.junit.jupiter包)

A、@Test 表示方法是一种测试方法B、@Disabled 表示会跳过此测试方法C、@DisplayName 为测试类或者测试方法自定义一个名称,举例

@DisplayName("test Disabled")@Disabled@Testvoid testDisabled(){log.info("test Disabled");}

D、@BeforeEach 表示方法在每个测试方法运行前都会运行

E、@AfterEach 表示方法在每个测试方法运行之后都会运行

F、@BeforeAll 表示方法在所有测试方法之前运行(类级别方法,必须位静态方法)

G、@AfterAll 表示方法在所有测试方法之后运行 (类级别方法,必须位静态方法)

@Autowiredprivate WebApplicationContext wac;private MockMvc mockMvc;@BeforeAllpublic static void BeforeEach() throws Exception {//在所有测试方法运行前运行log.info("Run before all test methods run");}@BeforeEachpublic void before() throws Exception {mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();}

H、@RepeatedTest(重复测试,测试高并发用)

I、@EnabledOnOs(在什么环境执行),如 @EnabledOnOs({ LINUX, MAC }),也可以自定义举例:

@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Test@EnabledOnOs(MAC)@interface TestOnMac {}@TestOnMacvoid testOnMac() {// ...}

J、@EnabledOnJre(基于哪个版本的jre执行)如 @EnabledOnJre({ JAVA_9, JAVA_10 })

5、断言

A、assertEquals 断言预期值和实际值相等(参数可以是int double string等等)

B、assertFalse 断言条件为假

C、assertNotNull 断言不为空

D、assertTrue 断言条件为真 举例:

//参数为intassertEquals(Math.round(Double.valueOf(gson.fromJson(result, Map.class).get("code").toString())),0);//参数位字符串assertEquals(result,"");//参数为booleanassertTrue(Math.round(Double.valueOf(gson.fromJson(result, Map.class).get("code").toString()))==0);

E、assumeTrue 假设为true时才会执行,如果为false,那么将会直接停止执行 举例:

assumeTrue(!gson.fromJson(result, Map.class).get("msg").toString().contains("channelId must more than or equal to 1"));assumeTrue(!gson.fromJson(result, Map.class).get("msg").toString().contains("name must hava a value"));assumeTrue(!gson.fromJson(result, Map.class).get("msg").toString().contains("appKey must hava a value"));

6、参数化测试

@ParameterizedTest(替代@Test)

@CsvSource(多个参数的多组测试)

@ValueSource(单个参数的多组测试)

@EnumSource 其实跟@ValueSource差不多,只不过可以复用枚举类。举例:

public enum ActivityLimitEnum {LIMIT(1,"封顶"),UNLIMIT(0,"上不封顶");}@ParameterizedTest@EnumSource(ActivityLimitEnum.class)@DisplayName("封顶和不封顶")void test(ActivityLimitEnum activityLimitEnum) {if (ActivityLimitEnum.LIMIT.equals(activityLimitEnum)) {assertFalse(false);} else if (ActivityLimitEnum.UNLIMIT.equals(activityLimitEnum)) {assertTrue(true);}}

@MethodSource(将一个方法的返回值作为测试方法的入参,引用的方法返回值必须是Stream, Iterator 或者Iterable) 如:

@ParameterizedTest@MethodSource("stringGenerator")public void test(String str){System.out.println(str);}static Stream<String> stringGenerator(){return Stream.of("hello", "world", "let's", "test");}

7、MockMvc使用(模拟controller请求接收)

A、GET请求

/*** Method: findAppListByChannelId(Integer channelId)*/@DisplayName("query apps information by channelId")@ParameterizedTest@ValueSource(strings = {"1", "2", "3"})public void testFindAppListByChannelId(String channelId) throws Exception {String result = mockMvc.perform(get("/v1/appver/apps/list-by-channel") //请求的url,请求的方法是get.param("channelId", channelId) //添加参数.contentType(MediaType.APPLICATION_JSON_UTF8)).andExpect(status().isOk()) //返回的状态是200.andReturn().getResponse().getContentAsString(); //将相应的数据转换为字符串assertTrue(Math.round(Double.valueOf(gson.fromJson(result, Map.class).get("code").toString()))==0);}

注意:get请求的参数全部写String类型,由param方法自动装配成需要的类型。

B、POST请求

/*** Method: addApps(AppsBo appsBo)*/@DisplayName("add apps information success")@ParameterizedTest@CsvSource({"app1, key1,1", "app2, key2,1"})public void testAddAppsSuccess(String name, String appKey, int channdlId) throws Exception {log.info("test testAddAppsSuccess end");Date date = new Date();AppsBo appsBo = new AppsBo();appsBo.setName(name);appsBo.setChannelId(channdlId);appsBo.setAppKey(appKey);String requestBody = gson.toJson(appsBo);Map map = gson.fromJson(requestBody, Map.class);map.put("createTime", date.getTime());String newRequestBody = gson.toJson(map);String result = mockMvc.perform(MockMvcRequestBuilders.post("/v1/appver/apps/add").content(newRequestBody).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andReturn().getResponse().getContentAsString();|-----|-----|-----| //assumeTrue的参数为true时才继续往下走,否则就停止到此assumeTrue(!gson.fromJson(result, Map.class).get("msg").toString().contains("The channel not exist"));assertEquals(Math.round(Double.valueOf(gson.fromJson(result, Map.class).get("code").toString())),0);log.info("test testAddAppsSuccess finished");}

8、几个方法的简单说明:

最后注意:因为测试不能影响数据库的数据,所以测试类上都要加事务回滚 如下:

@Slf4j@SpringBootTest@ExtendWith(SpringExtension.class)@Rollback@Transactionalpublic class AppverAppsControllerTest {

测试用例得出的某些问题:

1、添加数据时,要考虑重名判断,用assumeTrue判断返回的校验提示语。

2、修改数据和删除时,要考虑参数Id是否在表中存在,存在和不存的返回结果不一样,所以建议加个自定义校验,用assumeTrue判断返回的校验提示语或者code码。

9、增加app服务的验证签名之后的junit修改

a.注入appId和appSecret,当然配置文件中已经配置此属性。

@Value("${app.id}")private String appId;@Value("${app.secret}")private String appSecret;

b.在before方法中得到需要添加到header中的值

@BeforeEachpublic void before() throws Exception {header=buildSignatureHeaders();mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();}

/*** 创建鉴权头文件信息* @return header信息*/private HttpHeaders buildSignatureHeaders() {Long ts = System.currentTimeMillis();TreeMap<String, String> params = new TreeMap<>();params.put("bys_appId", appId);params.put("bys_timestamp", ts.toString());params.put("bys_secret", appSecret);String sign = SignUtils.sign(params);HttpHeaders header = new HttpHeaders();header.set("bys_appId", appId);header.set("bys_timestamp", Long.toString(ts));header.set("bys_signature", sign);return header;}

c.mockMvc调用接口时添加header签名

String result = mockMvc.perform(post("/v1.0/coupon/store/add").headers(header).content(newRequestBody).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andReturn().getResponse().getContentAsString();

三.CI流程中需要增加的项目

1、pom依赖

a) 由于ci流程中需要搜集单元测试的代码覆盖率,并合并到SonarQube平台以供查看,应此需要在build.plugins中增加:

<plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.8.5</version><executions><execution><id>prepare-agent</id><goals><goal>prepare-agent</goal></goals></execution><execution><id>report</id><phase>test</phase><goals><goal>report</goal></goals></execution></executions></plugin><plugin><groupId>org.sonarsource.scanner.maven</groupId><artifactId>sonar-maven-plugin</artifactId><version>3.7.0.1746</version></plugin>

b) 为了防止ci流程中执行单元测试时对真实数据库操作影响,引入h2 db,并在2. profile中设置。因此需要在dependencies中增加:

<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>1.4.197</version><scope>compile</scope></dependency>

2、profile

由于ci流程中执行单元测试时在一个独立的环境,因此需要提供单独的profile配置。在test/resources中增加

application-test.yml

application-test.yml中应包含如下信息

a) 如果有数据库,则应包含

spring:# DataSource StoreConfigdatasource:driver-class-name: org.h2.Driverurl: jdbc:h2:mem:h2test;DB_CLOSE_DELAY=-1;MODE=MySQLusername: sapassword: sah2:console:enabled: true# 如果使用了flywayflyway: enabled: false

b) **如果使用了redis,应将database设置与真实环境不一样的库

c) 其他应用中用到的信息,比如我们使用的鉴权信息,有应用配置的电话号码国家区号等等

例:

# 应用授权信息bys:app:# appIdid: ${APP_ID:10000003}# secretsecret: ${APP_SECRET:99F11D010F20A896FED5D0FEEEFE031C}

3、测试代码中profile的使用

a) 一般情况下我们均使用testprofile,添加类注解@ActiveProfiles(“test”)比如:

@ExtendWith(SpringExtension.class)@ActiveProfiles("test")@SpringBootTestclass UserControllerTest {

b) 如有特殊测试需要测试真实数据中的数据,请使用注解ActiveProfiles引入相应环境的 profile,该profile必须是在java/resources或者test/resources中真实存在的

4、测试数据的规范

a) 上传类的测试文件应放置在test/resources/data/upload

b) 由于测试过程中需要mock大量数据进行测试,可放置jsontest/resources/data/mock中,测试类在before中引入

static List<MembershipCardTypePo> list;//SpyBean不会影响其他的case,但是写法不同如下:Mockito.doReturn(true).when(tracingContext).hasGroup();@SpyBeanIMembershipCardTypeService iMembershipCardTypeService;@MockBeanIMembershipCardTypeService iMembershipCardTypeService;@AutowiredGson gson;@BeforeAllpublic static void BeforeEach() throws Exception {MembershipCardTypePo membershipCardTypePo=new MembershipCardTypePo();list=new ArrayList<>();list.add(membershipCardTypePo);//在所有测试方法运行前运行log.info("Run before all test methods run");}

/*** Method: findMembershipCardTypeListByStore(List<Integer> storeIdList)*/@DisplayName("find membership card type list by store success")@Testpublic void testFindMembershipCardTypeListByStoreSuccess() throws Exception {given(this.iMembershipCardTypeService.findMembershipCardTypeListByStore(any())).willReturn(list);//Mockito.when(iMembershipCardTypeService.findMembershipCardTypeListByStore(any())).thenReturn(list);log.info("test testFindMembershipCardTypeListByStoreSuccess start");String result = mockMvc.perform(get("/v1.0/membership/card/type/list-by-store").headers(header).param("storeIdList","1,2").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andReturn().getResponse().getContentAsString();log.info("result:{}", result);//assumeTrue的参数为true时才继续往下走,否则就停止到此assertEquals(Math.round(Double.valueOf(gson.fromJson(result, Map.class).get("code").toString())), 0);log.info("test testFindMembershipCardTypeListByStoreSuccess finished");}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。