单元测试(Unit Testing),是指对软件或项目中最小可测试单元进行正确性检验的测试工作。单元是人为规定最小可测试的功能模块,可以是一个模块,一个函数或者一个类。单元测试需要与模块开发进行隔离情况下进行测试。
成都创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:网站设计制作、成都网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的平江网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
在程序开发完成后,我们往往不能保证程序 100% 的正确,通过单元测试的编写,我们可以通过自动化的测试程序将我们的输入输出程序进行定义,通过断言来 Check 各个 Case 的结果,检测我们的程序。以提高程序的正确性,稳定性,可靠性,节省程序开发时间。我们在项目中主要用到的单元测试框架有 Spring-Boot-Test TestNG、PowerMock 等。
TestNG,即 Testing, Next Generation,下一代测试技术,是一套根据 JUnit 和 NUnit 思想而构建的利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。
PowerMock 也是一个单元测试模拟框架,它是在其它单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持,对静态初始化过程的移除等强大的功能。
以 Spring-Boot 项目为例,首先我们需要添加 TestNG + ProwerMock 依赖依赖如下:
org.springframework.boot spring-boot-starter-test test org.testng testng ${testng.version} test org.powermock powermock-api-mockito2 ${powermock.version} test org.powermock powermock-module-junit4 ${powermock.version} test org.powermock powermock-module-testng ${powermock.version} test
增加测试代码
- import com.test.testng.dto.OrderDto;
- import com.test.testng.dto.UserDto;
- import org.mockito.*;
- import org.powermock.modules.testng.PowerMockTestCase;
- import org.testng.annotations.BeforeMethod;
- import org.testng.annotations.Test;
- import static org.junit.jupiter.api.Assertions.*;
- import static org.mockito.Mockito.when;
- public class OrderServiceTest extends PowerMockTestCase {
- @BeforeMethod
- public void before() {
- MockitoAnnotations.openMocks(this);
- }
- @InjectMocks
- private OrderService orderService;
- @Mock
- private UserService userService;
- // 正常测试
- @Test
- public void testCreateOrder() {
- //1. mock method start
- UserDto userDto = new UserDto();
- userDto.setId(100);
- when(userService.get()).thenReturn(userDto);
- //2. call business method
- OrderDto order = orderService.createOrder(new OrderDto());
- //3. assert
- assertEquals(order.getId(), 100);
- }
- // 异常测试
- @Test
- public void testCreateOrderEx() {
- //1. mock method start
- when(userService.get()).thenThrow(new RuntimeException());
- Exception exception = null;
- try {
- //2. call business method
- orderService.createOrder(new OrderDto());
- } catch (RuntimeException e) {
- exception = e;
- }
- //3. assert
- assertNotNull(exception);
- }
- }
- //静态方法
- UserDto dto = new UserDto();
- dto.setId(100000);
- PowerMockito.mockStatic(UserService.class);
- PowerMockito.when(UserService.loginStatic()).thenReturn(dto);
- UserDto userDto = UserService.loginStatic();
- assertEquals(100000, userDto.getId().intValue());
- //字段赋值
- ReflectionTestUtils.setField(orderService, "rateLimit", 99);
- // 模拟私有方法
- MemberModifier.stub(MemberMatcher.method(UserService.class, "get1")).toReturn(new UserDto());
- // 测试私有方法
- Method method = PowerMockito.method(UserService.class, "get1", Integer.class);
- Object userDto = method.invoke(userService, 1);
- assertTrue(userDto instanceof UserDto);
在测试数据比较多的时候,我们可以通过 @DataProvider 生成数据源,通过 @Test(dataProvider = "xxx") 使用数据, 如下所示:
- import com.test.testng.BaseTest;
- import com.test.testng.dto.UserDto;
- import org.mockito.InjectMocks;
- import org.testng.annotations.DataProvider;
- import org.testng.annotations.Test;
- import static org.testng.Assert.assertFalse;
- import static org.testng.AssertJUnit.assertTrue;
- public class UserServiceTest2 extends BaseTest {
- @InjectMocks
- private UserService userService;
- // 定义数据源
- @DataProvider(name = "test")
- public static Object[][] userList() {
- UserDto dto1 = new UserDto();
- UserDto dto2 = new UserDto();
- dto2.setSex(1);
- UserDto dto3 = new UserDto();
- dto3.setSex(1);
- dto3.setFlag(1);
- UserDto dto4 = new UserDto();
- dto4.setSex(1);
- dto4.setFlag(1);
- dto4.setAge(1);
- return new Object[][] {{dto1, null}, {dto2, null}, {dto3, null}, {dto4, null}};
- }
- // 正确场景
- @Test
- public void testCheckEffectiveUser() {
- UserDto dto = new UserDto();
- dto.setSex(1);
- dto.setFlag(1);
- dto.setAge(18);
- boolean result = userService.checkEffectiveUser(dto);
- assertTrue(result);
- }
- // 错误场景
- @Test(dataProvider = "test")
- public void testCheckEffectiveUser(UserDto dto, Object object) {
- boolean result = userService.checkEffectiveUser(dto);
- assertFalse(result);
- }
- }
案例:
1.判断有效用户: 年龄大于 18 并且 sex = 1 并且 flag = 1
- public boolean checkEffectiveUser(UserDto dto) {
- // 判断有效用户: 年龄大于 18 并且 sex = 1 并且 flag = 1
- return Objects.equals(dto.getSex(), 1) &&
- Objects.equals(dto.getFlag(), 1) &&
- dto.getAge() != null && dto.getAge() >= 18;
- }
2.拆分逻辑。将其转换为最简单的 if ... else 语句。然后增加的单元测试,如下所示:
- public boolean checkEffectiveUser(UserDto dto) {
- if (!Objects.equals(dto.getSex(), 1)) {
- return false;
- }
- if (!Objects.equals(dto.getFlag(), 1)) {
- return false;
- }
- if (dto.getAge() == null) {
- return false;
- }
- if (dto.getAge() < 18) {
- return false;
- }
- return true;
- }
3.拆分后我们可以看到,咱们只需要 5 条单元测试就能做到全覆盖。
- public class UserServiceTest extends BaseTest {
- @InjectMocks
- private UserService userService;
- // 覆盖第一个 return
- @Test
- public void testCheckEffectiveUser_0() {
- UserDto dto =new UserDto();
- boolean result = userService.checkEffectiveUser(dto);
- assertFalse(result);
- }
- // 覆盖第二个 return
- @Test
- public void testCheckEffectiveUser_1() {
- UserDto dto =new UserDto();
- dto.setSex(1);
- boolean result = userService.checkEffectiveUser(dto);
- assertFalse(result);
- }
- // 覆盖第三个 return
- @Test
- public void testCheckEffectiveUser_2() {
- UserDto dto =new UserDto();
- dto.setSex(1);
- dto.setFlag(1);
- boolean result = userService.checkEffectiveUser(dto);
- assertFalse(result);
- }
- // 覆盖第四个 return
- @Test
- public void testCheckEffectiveUser_3() {
- UserDto dto =new UserDto();
- dto.setSex(1);
- dto.setFlag(1);
- dto.setAge(1);
- boolean result = userService.checkEffectiveUser(dto);
- assertFalse(result);
- }
- // 覆盖第五个 return
- @Test
- public void testCheckEffectiveUser_4() {
- UserDto dto =new UserDto();
- dto.setSex(1);
- dto.setFlag(1);
- dto.setAge(18);
- boolean result = userService.checkEffectiveUser(dto);
- assertTrue(result);
- }
- }
4.单测覆盖率检测检测
1.assert:断言是 java 的一个保留字,用来对程序进行调试,后接逻辑运算表达式,如下:
- int a = 0, b = 1;
- assert a == 0 && b == 0;
- // 使用方法:javac编译源文件,再 java -ea class文件名即可。
2.在 Spring-Boot 中可以使用 Spring 提供的 Assert 类的方法对前端来的参数进行校验,如:
- // 检查年龄 >= 18 岁
- public boolean checkUserAge(UserDto dto){
- Assert.notNull(dto.getAge(), "用户年龄不能为空");
- Assert.isTrue(dto.getAge() >= 18, "用户年龄不能小于 18 岁");
- return Boolean.TRUE;
- }
3.如果是需要转换为,rest api 返回的统一相应消息,我们可以通过:
- @ControllerAdvice
- public class GlobalExceptionHandler {
- @ResponseBody
- @ExceptionHandler(value = IllegalArgumentException.class)
- public Response
handleArgError(IllegalArgumentException e){ - return new Response().failure().message(e.getMessage());
- }
- }
原则上来讲,在功能模块的设计过程中我们应该遵循一下原则(参考 《软件工程-结构化设计准则》):
参考文档
https://testng.org/doc/
https://github.com/powermock/powermock
https://www.netconcepts.cn/detail-41004.html
文章题目:TestNG+PowerMock单元测试
文章网址:http://www.shufengxianlan.com/qtweb/news36/287886.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联