赞
踩
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。——维基百科
黑盒测试又称为功能测试
或数据驱动测试
,测试过程中,程序看作成一个黑色盒子,看不到盒子内部代码结构。
黑盒测试主要根据功能需求设计测试用例进行测试,是一种从软件外部实施的测试方式。多次输入参数,测试查看程序是否正常或达到预期。
黑盒只知道软件的功能(能干什么),但是不知道软件的实现(怎么干的)。
白盒测试又称为结构测试
或逻辑驱动测试
,测试过程中,程序看作一个透明盒子,能够看清盒子内部的代码和结构,这样测试人员对程序代码的逻辑有所知晓。
穷举路径的方式传参,检查代码所有结构是否正常或符合预期。单元测试属于白盒测试。
白盒知道软件的实现(怎么干的),不需要管软件的功能(能干什么)。
程序每条可执行语句至少执行一次,即测试用例覆盖所有语句。
也称为分支覆盖,针对判定表达式,true或false两种真假判定,程序中每一个判断的分支至少经历一次。
比如,判定表达式:a > 0 && b > 0
设计测试数据
- a > 0 && b > 0(判定表达式的值为“真”)
- a <= 0 && b <= 0(判定表达式的值为“假”)
- 复制代码
满足判定的所有分支覆盖(此时真、假分支都覆盖)。
针对判断语句中的条件,程序中每个判断中的每个条件取值至少满足一次,针对条件语句。
比如,判定表达式:a > 0 && b > 0
设计测试数据
- a <= 0 && b <= 0(判定表达式的值为“假”)
- a > 0 && b <= 0(判定表达式的值为“假”)
- 复制代码
保证每个条件取值一次,而不管分支是否覆盖全面(此时只覆盖假分支)。
判定条件中所有可能条件成立与否至少执行一次取值、所有真假判断的可能结果至少执行一次。
比如,判定表达式:a > 0 && b > 0
设计测试数据
- a > 0 && b > 0(判定表达式的值为“真”)
- a <= 0 && b <= 0(判定表达式的值为“假”)
- 复制代码
所有可能的条件取值组合至少执行一次。
比如,判定表达式:a > 0 && b > 0
设计测试数据
- a > 0 && b > 0(判定表达式的值为“真”)
- a <= 0 && b <= 0(判定表达式的值为“假”)
- a > 0 && b <= 0(判定表达式的值为“假”)
- a <= 0 && b > 0(判定表达式的值为“假”)
- 复制代码
判定中所有可能的条件组合。
所有可能执行的路径。
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- 复制代码
与src/main同级目录,创建src/test,其他类路径与main包中保持一致。(可通过创建类的快捷方法自动创建路径)
创建类对应单测类的快捷方法:只需将双击这个类,鼠标右键,然后选择go to到Test。
选择要测试的方法,同时,可以选择JUnit版本等,点击OK
- @Slf4j
- @RestController
- @RequestMapping("/demo")
- @Api(value = "DemoController", tags = "Demo管理模块")
- public class DemoController implements DemoApi {
- /**
- * service
- */
- @Autowired
- private DemoService demoService;
-
-
- @Override
- @ApiOperation(value = "新增", notes = "新增")
- @PostMapping("/create")
- public Boolean add(@RequestHeader("appCode") String appCode,
- @RequestBody DemoDTO demoDTO) {
- boolean addFlag = demoService.add(demoDTO);
- if (addFlag) {
- // 刷新资源
- demoService.refreshMap(appCode);
- }
- return addFlag;
- }
-
-
- @Override
- @ApiOperation(value = "修改", notes = "修改")
- @PostMapping("/update")
- public Boolean update(@RequestHeader("appCode") String appCode,
- @RequestBody DemoDTO demoDTO) {
-
- boolean addFlag = demoService.update(demoDTO);
- if (addFlag) {
- // 刷新资源
- demoService.refreshMap(appCode);
- }
- return addFlag;
- }
-
-
- @Override
- @ApiOperation(value = "删除", notes = "删除")
- @DeleteMapping("/delById")
- public Boolean deleteById(@RequestHeader("appCode") String appCode, @RequestParam Long id) {
- boolean deleteFlag = demoService.deleteById(id);
- if (deleteFlag) {
- // 刷新资源
- demoService.refreshMap(appCode);
- }
- return addFlag;
- }
-
-
- @Override
- @ApiOperation(value = "列表", notes = "列表")
- @GetMapping("/list")
- public List<DemoVO> list() {
- return demoService.list();
- }
- }
- 复制代码
- @RunWith(SpringRunner.class)
- public class DemoControllerTest {
- /**
- * mock mvc
- */
- private MockMvc mockMvc;
-
-
- /**
- * 注入实例
- */
- @InjectMocks
- private DemoController demoController;
-
-
- /**
- * service mock
- */
- @Mock
- private DemoService demoService;
-
- /**
- * appCode
- */
- private String appCode;
-
-
- /**
- * before设置
- */
- @Before
- public void setUp() {
- //初始化带注解的对象
- MockitoAnnotations.openMocks(this);
- //构造mockmvc
- mockMvc = MockMvcBuilders.standaloneSetup(demoController).build();
- //appCode
- appCode = "AppCode_test";
- }
-
-
- /**
- * 测试testAdd
- */
- @Test
- public void testAdd() throws Exception {
- //构建dto
- DemoDTO demoDTO = new DemoDTO();
- //setId
- demoDTO.setId(-1L);
- //setName
- demoDTO.setName("test");
- //mock service方法
- PowerMockito.when(demoService.add(demoDTO)).thenReturn(true);
- //构造body
- String body = JSONObject.toJSONString(demoDTO);
- //执行mockmvc
- this.mockMvc.perform(MockMvcRequestBuilders.post("/demo/create")
- //传参
- .header("appCode", appCode).content(body).contentType(MediaType.APPLICATION_JSON_VALUE))
- //mock返回
- .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
- }
-
-
- /**
- * 测试testUpdate
- */
- @Test
- public void testUpdate() throws Exception {
- //构建dto
- DemoDTO demoDTO = new DemoDTO();
- //setId
- demoDTO.setId(-1L);
- //setName
- demoDTO.setName("test");
- //mock service方法
- PowerMockito.when(demoService.update(demoDTO)).thenReturn(true);
- //构造body
- String body = JSONObject.toJSONString(demoDTO);
- //执行mockmvc
- this.mockMvc.perform(MockMvcRequestBuilders.post("/demo/update")
- //传参
- .header("appCode", appCode).content(body).contentType(MediaType.APPLICATION_JSON_VALUE))
- //mock返回
- .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
- }
-
-
- /**
- * 测试testDelete
- */
- @Test
- public void testDelete() throws Exception {
- //Id
- Long id = 1000L;
- //mock service方法
- PowerMockito.when(demoService.deleteById(id)).thenReturn(true);
- //执行mockmvc 方法一
- // this.mockMvc.perform(MockMvcRequestBuilders.delete("/demo/delById?id={id}",id)
- // //传参
- // .header("appCode", appCode).contentType(MediaType.APPLICATION_JSON_VALUE))
- // //mock返回
- //.andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
- // }
- //方法二
- this.mockMvc.perform(MockMvcRequestBuilders.delete("/demo/delById")
- //传参
- .header("appCode", appCode).param("id", "1000").contentType(MediaType.APPLICATION_JSON_VALUE))
- //mock返回
- .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
- }
-
-
- /**
- * 测试testList
- */
- @Test
- public void testList() throws Exception {
- this.mockMvc.perform(MockMvcRequestBuilders.get("/demo/list")
- //传参
- .contentType(MediaType.APPLICATION_JSON_VALUE))
- //mock返回
- .andExpect(status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();
- }
- }
- 复制代码
- @Service
- public class MenuService {
- @Autowired
- private MenuMapper menuMapper;
-
- @Transactional(readOnly = true)
- public List<Menu> listMenus() {
- final List<Menu> result = menuMapper.list();
- return result;
- }
- }
- 复制代码
@TestConfiguration
创建一个测试用配置,该配置中提供了一个 MenuService Bean
的声明。- @Import(MenuServiceTestConfig.class)
- @ContextConfiguration(classes =MenuServiceTestConfig.class)
- @Autowired
- //此处通过 @Autowired 自动注入的是上面通过 @TestConfiguration 声明的 Bean。
- @RunWith(SpringRunner.class)
- public class MenuServiceTest {
- @TestConfiguration
- static class MenuServiceTestConfig {
- @Bean
- public MenuService mockMenuService() {
- return new MenuService();
- }
- }
- @Autowired
- private MenuService MenuService;
- @MockBean
- private MenuMapper MenuMapper;
- @Test
- public void listMenus() {
- List<Menu> menus = new ArrayList<Menu>() {{
- this.add(new Menu());
- }};
- Mockito.when(menuMapper.list()).thenReturn(menus);
- List<Menu> result = menuService.listMenus();
- Assertions.assertThat(result.size()).isEqualTo(menus.size());
- }
- }
- 复制代码
- A:Automatic(自动化)
- I:Independent(独立性)
- R:Repeatable(可重复)
- 复制代码
mock是指在测试过程中,创建一个虚拟的对象来模拟指定对象的行为。
- <properties>
- <powermock.version>2.0.9</powermock.version>
- </properties>
- <dependencies>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-module-junit4</artifactId>
- <version>${powermock.version}</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.powermock</groupId>
- <artifactId>powermock-api-mockito2</artifactId>
- <version>${powermock.version}</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
- 复制代码
注意:
此依赖适合JUnit4.4及以上。
mock模板
- @RunWith(PowerMockRunner.class)
- @PowerMockRunnerDelegate(SpringRunner.class)
- @PrepareForTest({XxxUtils.class})
- public class DemoServiceImplTest {
- /**
- * 注入service
- */
- @InjectMocks
- private DemoServiceImpl demoService;
-
- /**
- * mapper
- */
- @MockBean
- private DemoMapper demoMapper;
-
- @Before
- public void setUp() {
- //构建mybatis缓存
- TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), DemoEntity.class);
- }
-
- @Test
- public void testCreateDemo() {
- //todo
- }
- }
- 复制代码
mock私有构造方法
以部门封装的Result类为例,其中一些private构造方法无法构造参数,需mock构造。
- Result<List<UserInfoDTO>> result = Whitebox.invokeConstructor(Result.class, "200", "", true, userInfoDTOList);
- 复制代码
mock私有方法和私有属性
- //mock 当前service类
- ServiceA serviceMock= PowerMockito.mock(ServiceA.class);
- //内部调用其他service需设置非公共成员
- Whitebox.setInternalState(serviceMock, "serviceB", serviceB);
- //mock调用其他service方法
- PowerMockito.when(serviceB.getStr("xxx")).thenReturn("xxxxx");
- //调用内部私有方法
- Whitebox.invokeMethod(serviceMock, "privateMethod1", demoDto);
- 复制代码
说明:
mock静态方法
- @PrepareForTest({StaticXXX.class})
- 复制代码
- //mock静态类
- PowerMockito.mockStatic(StaticXXX.class);
- //mock调用静态方法
- PowerMockito.when(StaticXXX.method01("param01", "param02")).thenReturn(xxx);
- 复制代码
mock配置
- @MockBean
- private DemoProperties demoProperties ;
- 复制代码
- //构造demoProperties
- DemoProperties .DemoFirstProperties demoFirstProperties = new DemoProperties .DemoFirstProperties ();
- //塞值
- demoFirstProperties .setFirstParams("xxxx");
- //mock properties
- PowerMockito.when(demoProperties .getDemoFirstProperties ()).thenReturn(demoFirstProperties );
- 复制代码
mock对象类的公有方法值返回
- public class SmsResponse implements Serializable {
- /**
- * 编码 1-成功
- */
- private Integer code;
- /**
- * 返回内容
- */
- private String msg;
- public boolean isSuccess(){
- return null != code && 200 == code;
- }
- public Integer getCode() {
- return code;
- }
- public void setCode(Integer code) {
- this.code = code;
- }
- public String getMsg() {
- return msg;
- }
- public void setMsg(String msg) {
- this.msg = msg;
- }
- }
- 复制代码
- // 发送短信
- SmsResponse smsResponse = this.smsNoticeService.sendSms(sms);
- if (smsResponse.isSuccess()) {
- if (Objects.nonNull(noticeTimes) && Objects.nonNull(smsNotice)) {
- // 短信发送成功,更新通知标识
- noticeTimes.put(NoticeChannelEnum.SMS.getDesc(),smsNotice);
- }
- result.put(MSG,"");
- return result;
- }
- 复制代码
此时若想进入到if分支内,就需要mock操作
- SmsResponse smsResponse = PowerMockito.mock(SmsResponse.class);
- PowerMockito.when(smsResponse.isSuccess()).thenReturn(true);
- when(this.smsNoticeService.sendSms(any(Sms.class))).thenReturn(smsResponse);
- 复制代码
使用@RunWith(SpringRunner.class)声明该测试是在Spring环境中进行,这样Spring相关注解就会被识别生效。
结合powermock使用时,容易造成一些类和方法的冲突,导致方法找不到。
依赖版本对应:
Mockito | PowerMock |
---|---|
2.8.9+ | 2.x |
2.8.0-2.8.9 | 1.7.x |
2.7.5 | 1.7.0RC4 |
2.4.0 | 1.7.0RC2 |
2.0.0-beta - 2.0.42-beta | 1.6.5-1.7.0RC |
1.10.8 - 1.10.x | 1.6.2 - 2.0 |
1.9.5-rc1 - 1.9.5 | 1.5.0 - 1.5.6 |
1.9.0-rc1 & 1.9.0 | 1.4.10 - 1.4.12 |
1.8.5 | 1.3.9 - 1.4.9 |
1.8.4 | 1.3.7 & 1.3.8 |
1.8.3 | 1.3.6 |
1.8.1 & 1.8.2 | 1.3.5 |
1.8 | 1.3 |
1.7 | 1.2.5 |
有一些service类中注入的其他类,是通过构造函数注入,而非@Autowired或者@Resource注解。如
- @Service
- public class MenuService {
-
- private MenuMapper menuMapper;
- @Autowired
- public MenuService(final MenuMapper menuMapper) {
- this.menuMapper = menuMapper;
- }
-
- @Transactional(readOnly = true)
- public List<Menu> listMenus() {
- final List<Menu> result = menuMapper.list();
- return result;
- }
- }
- 复制代码
构造service
- @RunWith(SpringRunner.class)
- public class MenuServiceTest {
-
- private static final MenuMapper menuMapper = Mockito.mock(MenuMapper.class);
-
- @TestConfiguration
- static class MenuServiceTestConfig {
- @Bean
- public MenuService mockMenuService() {
- return new MenuService(menuMapper);
- }
- }
- @Autowired
- private MenuService MenuService;
- @MockBean
- private MenuMapper MenuMapper;
- @Test
- public void listMenus() {
- List<Menu> menus = new ArrayList<Menu>() {{
- this.add(new Menu());
- }};
- Mockito.when(menuMapper.list()).thenReturn(menus);
- List<Menu> result = menuService.listMenus();
- Assertions.assertThat(result.size()).isEqualTo(menus.size());
- }
- }
- 复制代码
mock静态方法时,报错该静态类prepared for test
使用到lambda表达式有set方法的地方,单测报错com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: can not find lambda cache for this entity
在单测类中@Before方法中加入代码手动触发缓存信息收集。
- @Before
- public void setUp() {
- //构建mybatis缓存
- TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), XxxEntity.class);
- }
- 复制代码
其中,XxxEntity.class为表实体类。
作者:Andya
链接:https://juejin.cn/post/7222577873793138747
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。