Java单元测试技巧之PowerMock

简介: 高德的技术大佬向老师在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。” 这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现,最后把这些简单逻辑整合为复杂逻辑,总结为八字真言即是“化繁为简,由简入繁”。

lADPDgfLQrxGx4vNAoDNBAA_1024_640.jpg_720x720q90g.jpg

作者 | 常意
来源 | 阿里技术公众号

前言

高德的技术大佬向老师在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”
这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现,最后把这些简单逻辑整合为复杂逻辑,总结为八字真言即是“化繁为简,由简入繁”。
编写Java单元测试用例,其实就是把“复杂的问题要简单化”——即把一段复杂的代码拆解成一系列简单的单元测试用例;写好Java单元测试用例,其实就是把“简单的问题要深入化”——即学习一套方法、总结一套模式并应用到实践中。这里,作者根据日常的工作经验,总结了一些Java单元测试技巧,以供大家交流和学习。

1. 准备环境

PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final类和方法、私有方法、去除静态初始化器等等。

1.1. 引入PowerMock包

为了引入PowerMock包,需要在pom.xml文件中加入下列maven依赖:

image.png

1.2. 集成SpringMVC项目

在SpringMVC项目中,需要在pom.xml文件中加入JUnit的maven依赖:

image.png

1.3. 集成SpringBoot项目

在SpringBoot项目中,需要在pom.xml文件中加入JUnit的maven依赖:

image.png

1.4. 一个简单的测试用例

这里,用List举例,模拟一个不存在的列表,但是返回的列表大小为100。

public class ListTest {@Testpublic void testSize() {Integer expected = 100;List list = PowerMockito.mock(List.class);PowerMockito.when(list.size()).thenReturn(expected);Integer actual = list.size();Assert.assertEquals("返回值不相等", expected, actual);}
}

2. mock语句

2.1. mock方法

声明:
T PowerMockito.mock(Class clazz);
用途:
可以用于模拟指定类的对象实例。
当模拟非final类(接口、普通类、虚基类)的非final方法时,不必使用@RunWith和@PrepareForTest注解。当模拟final类或final方法时,必须使用@RunWith和@PrepareForTest注解。注解形如:
@RunWith(PowerMockRunner.class)
@PrepareForTest({TargetClass.class})

2.1.1. 模拟非final类普通方法

@Getter
@Setter
@ToString
public class Rectangle implements Sharp {private double width;private double height;@Overridepublic double getArea() {return width * height;}
}public class RectangleTest {@Testpublic void testGetArea() {double expectArea = 100.0D;Rectangle rectangle = PowerMockito.mock(Rectangle.class);PowerMockito.when(rectangle.getArea()).thenReturn(expectArea);double actualArea = rectangle.getArea();Assert.assertEquals("返回值不相等", expectArea, actualArea, 1E-6D);}
}

2.1.2. 模拟final类或final方法

@Getter
@Setter
@ToString
public final class Circle {private double radius;public double getArea() {return Math.PI * Math.pow(radius, 2);}
}@RunWith(PowerMockRunner.class)
@PrepareForTest({Circle.class})
public class CircleTest {@Testpublic void testGetArea() {double expectArea = 3.14D;Circle circle = PowerMockito.mock(Circle.class);PowerMockito.when(circle.getArea()).thenReturn(expectArea);double actualArea = circle.getArea();Assert.assertEquals("返回值不相等", expectArea, actualArea, 1E-6D);}
}

2.2. mockStatic方法

声明:
PowerMockito.mockStatic(Class clazz);
用途:
可以用于模拟类的静态方法,必须使用“@RunWith”和“@PrepareForTest”注解。

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {@Testpublic void testIsEmpty() {String string = "abc";boolean expected = true;PowerMockito.mockStatic(StringUtils.class);PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(expected);boolean actual = StringUtils.isEmpty(string);Assert.assertEquals("返回值不相等", expected, actual);}
}

3. spy语句

如果一个对象,我们只希望模拟它的部分方法,而希望其它方法跟原来一样,可以使用PowerMockito.spy方法代替PowerMockito.mock方法。于是,通过when语句设置过的方法,调用的是模拟方法;而没有通过when语句设置的方法,调用的是原有方法。

3.1. spy类

声明:
PowerMockito.spy(Class clazz);
用途:
用于模拟类的部分方法。
案例:

public class StringUtils {public static boolean isNotEmpty(final CharSequence cs) {return !isEmpty(cs);}public static boolean isEmpty(final CharSequence cs) {return cs == null || cs.length() == 0;}
}@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {@Testpublic void testIsNotEmpty() {String string = null;boolean expected = true;PowerMockito.spy(StringUtils.class);PowerMockito.when(StringUtils.isEmpty(string)).thenReturn(!expected);boolean actual = StringUtils.isNotEmpty(string);Assert.assertEquals("返回值不相等", expected, actual);}
}

3.2. spy对象

声明:
T PowerMockito.spy(T object);
用途:
用于模拟对象的部分方法。
案例:

public class UserService {private Long superUserId;public boolean isNotSuperUser(Long userId) {return !isSuperUser(userId);}public boolean isSuperUser(Long userId) {return Objects.equals(userId, superUserId);}
}@RunWith(PowerMockRunner.class)
public class UserServiceTest {@Testpublic void testIsNotSuperUser() {Long userId = 1L;boolean expected = false;UserService userService = PowerMockito.spy(new UserService());PowerMockito.when(userService.isSuperUser(userId)).thenReturn(!expected);boolean actual = userService.isNotSuperUser(userId);Assert.assertEquals("返回值不相等", expected, actual);}
}

4. when语句

4.1. when().thenReturn()模式

声明:

PowerMockito.when(mockObject.someMethod(someArgs)).thenReturn(expectedValue);
PowerMockito.when(mockObject.someMethod(someArgs)).thenThrow(expectedThrowable);
PowerMockito.when(mockObject.someMethod(someArgs)).thenAnswer(expectedAnswer);
PowerMockito.when(mockObject.someMethod(someArgs)).thenCallRealMethod();
用途:
用于模拟对象方法,先执行原始方法,再返回期望的值、异常、应答,或调用真实的方法。

4.1.1. 返回期望值

public class ListTest {@Testpublic void testGet() {int index = 0;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.when(mockList.get(index)).thenReturn(expected);Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.1.2. 返回期望异常

public class ListTest {@Test(expected = IndexOutOfBoundsException.class)public void testGet() {int index = -1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.when(mockList.get(index)).thenThrow(new IndexOutOfBoundsException());Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.1.3. 返回期望应答

public class ListTest {@Testpublic void testGet() {int index = 1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.when(mockList.get(index)).thenAnswer(invocation -> {Integer value = invocation.getArgument(0);return value * 100;});Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.1.4. 调用真实方法

public class ListTest {@Testpublic void testGet() {int index = 0;Integer expected = 100;List<Integer> oldList = new ArrayList<>();oldList.add(expected);List<Integer> spylist = PowerMockito.spy(oldList);PowerMockito.when(spylist.get(index)).thenCallRealMethod();Integer actual = spylist.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.2. doReturn().when()模式

声明:
PowerMockito.doReturn(expectedValue).when(mockObject).someMethod(someArgs);
PowerMockito.doThrow(expectedThrowable).when(mockObject).someMethod(someArgs);
PowerMockito.doAnswer(expectedAnswer).when(mockObject).someMethod(someArgs);
PowerMockito.doNothing().when(mockObject).someMethod(someArgs);
PowerMockito.doCallRealMethod().when(mockObject).someMethod(someArgs);
用途:
用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。
注意:
千万不要使用以下语法:
PowerMockito.doReturn(expectedValue).when(mockObject.someMethod(someArgs));
PowerMockito.doThrow(expectedThrowable).when(mockObject.someMethod(someArgs));
PowerMockito.doAnswer(expectedAnswer).when(mockObject.someMethod(someArgs));
PowerMockito.doNothing().when(mockObject.someMethod(someArgs));
PowerMockito.doCallRealMethod().when(mockObject.someMethod(someArgs));
虽然不会出现编译错误,但是在执行时会抛出UnfinishedStubbingException异常。

4.2.1. 返回期望值

public class ListTest {@Testpublic void testGet() {int index = 0;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doReturn(expected).when(mockList).get(index);Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.2.2. 返回期望异常

public class ListTest {@Test(expected = IndexOutOfBoundsException.class)public void testGet() {int index = -1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doThrow(new IndexOutOfBoundsException()).when(mockList).get(index);Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.2.3. 返回期望应答

public class ListTest {@Testpublic void testGet() {int index = 1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doAnswer(invocation -> {Integer value = invocation.getArgument(0);return value * 100;}).when(mockList).get(index);Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.2.4. 模拟无返回值

public class ListTest {@Testpublic void testClear() {List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doNothing().when(mockList).clear();mockList.clear();Mockito.verify(mockList).clear();}
}

4.2.5. 调用真实方法

public class ListTest {@Testpublic void testGet() {int index = 0;Integer expected = 100;List<Integer> oldList = new ArrayList<>();oldList.add(expected);List<Integer> spylist = PowerMockito.spy(oldList);PowerMockito.doCallRealMethod().when(spylist).get(index);Integer actual = spylist.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

4.3. 两种模式的主要区别

两种模式都用于模拟对象方法,在mock实例下使用时,基本上是没有差别的。但是,在spy实例下使用时,when().thenReturn()模式会执行原方法,而doReturn().when()模式不会执行原方法。
测试服务类:

@Slf4j@Service
public class UserService {public long getUserCount() {log.info("调用获取用户数量方法");return 0L;}
}

使用when().thenReturn()模式:

@RunWith(PowerMockRunner.class)
public class UserServiceTest {@Testpublic void testGetUserCount() {Long expected = 1000L;UserService userService = PowerMockito.spy(new UserService());PowerMockito.when(userService.getUserCount()).thenReturn(expected);Long actual = userService.getUserCount();Assert.assertEquals("返回值不相等", expected, actual);}
}

在测试过程中,将会打印出"调用获取用户数量方法"日志。

使用doReturn().when()模式:

@RunWith(PowerMockRunner.class)
public class UserServiceTest {@Testpublic void testGetUserCount() {Long expected = 1000L;UserService userService = PowerMockito.spy(new UserService());PowerMockito.doReturn(expected).when(userService).getUserCount();Long actual = userService.getUserCount();Assert.assertEquals("返回值不相等", expected, actual);}
}

在测试过程中,不会打印出"调用获取用户数量方法"日志。

4.4. whenNew模拟构造方法

声明:

PowerMockito.whenNew(MockClass.class).withNoArguments().thenReturn(expectedObject);
PowerMockito.whenNew(MockClass.class).withArguments(someArgs).thenReturn(expectedObject);
用途:
用于模拟构造方法。
案例:

public final class FileUtils {public static boolean isFile(String fileName) {return new File(fileName).isFile();}
}@RunWith(PowerMockRunner.class)
@PrepareForTest({FileUtils.class})
public class FileUtilsTest {@Testpublic void testIsFile() throws Exception {String fileName = "test.txt";File file = PowerMockito.mock(File.class);PowerMockito.whenNew(File.class).withArguments(fileName).thenReturn(file);PowerMockito.when(file.isFile()).thenReturn(true);Assert.assertTrue("返回值为假", FileUtils.isFile(fileName));}
}

注意:需要加上注解@PrepareForTest({FileUtils.class}),否则模拟方法不生效。

5. 参数匹配器

在执行单元测试时,有时候并不关心传入的参数的值,可以使用参数匹配器。

5.1. 参数匹配器(any)

Mockito提供Mockito.anyInt()、Mockito.anyString、Mockito.any(Class clazz)等来表示任意值。

public class ListTest {@Testpublic void testGet() {int index = 1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.when(mockList.get(Mockito.anyInt())).thenReturn(expected);Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

5.2. 参数匹配器(eq)

当我们使用参数匹配器时,所有参数都应使用匹配器。 如果要为某一参数指定特定值时,就需要使用Mockito.eq()方法。

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {@Testpublic void testStartWith() {String string = "abc";String prefix = "b";boolean expected = true;PowerMockito.spy(StringUtils.class);PowerMockito.when(StringUtils.startsWith(Mockito.anyString(), Mockito.eq(prefix))).thenReturn(expected);boolean actual = StringUtils.startsWith(string, prefix);Assert.assertEquals("返回值不相等", expected, actual);}
}

5.3. 附加匹配器

Mockito的AdditionalMatchers类提供了一些很少使用的参数匹配器,我们可以进行参数大于(gt)、小于(lt)、大于等于(geq)、小于等于(leq)等比较操作,也可以进行参数与(and)、或(or)、非(not)等逻辑计算等。

public class ListTest {@Testpublic void testGet() {int index = 1;Integer expected = 100;List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.when(mockList.get(AdditionalMatchers.geq(0))).thenReturn(expected);PowerMockito.when(mockList.get(AdditionalMatchers.lt(0))).thenThrow(new IndexOutOfBoundsException());Integer actual = mockList.get(index);Assert.assertEquals("返回值不相等", expected, actual);}
}

6. verify语句

验证是确认在模拟过程中,被测试方法是否已按预期方式与其任何依赖方法进行了交互。

格式:

Mockito.verify(mockObject[,times(int)]).someMethod(somgArgs);

用途:

用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。

案例:

6.1. 验证调用方法

public class ListTest {@Testpublic void testGet() {List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doNothing().when(mockList).clear();mockList.clear();Mockito.verify(mockList).clear();}
}

6.2. 验证调用次数

public class ListTest {@Testpublic void testGet() {List<Integer> mockList = PowerMockito.mock(List.class);PowerMockito.doNothing().when(mockList).clear();mockList.clear();Mockito.verify(mockList, Mockito.times(1)).clear();}
}

除times外,Mockito还支持atLeastOnce、atLeast、only、atMostOnce、atMost等次数验证器。

6.3. 验证调用顺序

public class ListTest {@Testpublic void testAdd() {List<Integer> mockedList = PowerMockito.mock(List.class);PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());mockedList.add(1);mockedList.add(2);mockedList.add(3);InOrder inOrder = Mockito.inOrder(mockedList);inOrder.verify(mockedList).add(1);inOrder.verify(mockedList).add(2);inOrder.verify(mockedList).add(3);}
}

6.4. 验证调用参数

public class ListTest {@Testpublic void testArgumentCaptor() {Integer[] expecteds = new Integer[] {1, 2, 3};List<Integer> mockedList = PowerMockito.mock(List.class);PowerMockito.doReturn(true).when(mockedList).add(Mockito.anyInt());for (Integer expected : expecteds) {mockedList.add(expected);}ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);Mockito.verify(mockedList, Mockito.times(3)).add(argumentCaptor.capture());Integer[] actuals = argumentCaptor.getAllValues().toArray(new Integer[0]);Assert.assertArrayEquals("返回值不相等", expecteds, actuals);}
}

6.5. 确保验证完毕

Mockito提供Mockito.verifyNoMoreInteractions方法,在所有验证方法之后可以使用此方法,以确保所有调用都得到验证。如果模拟对象上存在任何未验证的调用,将会抛出NoInteractionsWanted异常。

public class ListTest {@Testpublic void testVerifyNoMoreInteractions() {List<Integer> mockedList = PowerMockito.mock(List.class);Mockito.verifyNoMoreInteractions(mockedList); // 执行正常mockedList.isEmpty();Mockito.verifyNoMoreInteractions(mockedList); // 抛出异常}
}

备注:Mockito.verifyZeroInteractions方法与Mockito.verifyNoMoreInteractions方法相同,但是目前已经被废弃。

6.6. 验证静态方法

Mockito没有静态方法的验证方法,但是PowerMock提供这方面的支持。

@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class StringUtilsTest {@Testpublic void testVerifyStatic() {PowerMockito.mockStatic(StringUtils.class);String expected = "abc";StringUtils.isEmpty(expected);PowerMockito.verifyStatic(StringUtils.class);ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);StringUtils.isEmpty(argumentCaptor.capture());Assert.assertEquals("参数不相等", argumentCaptor.getValue(), expected);}
}

7. 私有属性

7.1. ReflectionTestUtils.setField方法

在用原生JUnit进行单元测试时,我们一般采用ReflectionTestUtils.setField方法设置私有属性值。

@Service
public class UserService {@Value("${system.userLimit}")private Long userLimit;public Long getUserLimit() {return userLimit;}
}public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void testGetUserLimit() {Long expected = 1000L;ReflectionTestUtils.setField(userService, "userLimit", expected);Long actual = userService.getUserLimit();Assert.assertEquals("返回值不相等", expected, actual);}
}

注意:在测试类中,UserService实例是通过@Autowired注解加载的,如果该实例已经被动态代理,ReflectionTestUtils.setField方法设置的是代理实例,从而导致设置不生效。

7.2. Whitebox.setInternalState方法

现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。

@Service
public class UserService {@Value("${system.userLimit}")private Long userLimit;public Long getUserLimit() {return userLimit;}
}@RunWith(PowerMockRunner.class)
public class UserServiceTest {@InjectMocksprivate UserService userService;@Testpublic void testGetUserLimit() {Long expected = 1000L;Whitebox.setInternalState(userService, "userLimit", expected);Long actual = userService.getUserLimit();Assert.assertEquals("返回值不相等", expected, actual);}
}

注意:需要加上注解@RunWith(PowerMockRunner.class)。

8. 私有方法

8.1. 模拟私有方法

8.1.1. 通过when实现

public class UserService {private Long superUserId;public boolean isNotSuperUser(Long userId) {return !isSuperUser(userId);}private boolean isSuperUser(Long userId) {return Objects.equals(userId, superUserId);}
}@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {@Testpublic void testIsNotSuperUser() throws Exception {Long userId = 1L;boolean expected = false;UserService userService = PowerMockito.spy(new UserService());PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);boolean actual = userService.isNotSuperUser(userId);Assert.assertEquals("返回值不相等", expected, actual);}
}

8.1.2. 通过stub实现

通过模拟方法stub(存根),也可以实现模拟私有方法。但是,只能模拟整个方法的返回值,而不能模拟指定参数的返回值。

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {@Testpublic void testIsNotSuperUser() throws Exception {Long userId = 1L;boolean expected = false;UserService userService = PowerMockito.spy(new UserService());PowerMockito.stub(PowerMockito.method(UserService.class, "isSuperUser", Long.class)).toReturn(!expected);boolean actual = userService.isNotSuperUser(userId);Assert.assertEquals("返回值不相等", expected, actual;}
}

8.3. 测试私有方法

@RunWith(PowerMockRunner.class)
public class UserServiceTest9 {@Testpublic void testIsSuperUser() throws Exception {Long userId = 1L;boolean expected = false;UserService userService = new UserService();Method method = PowerMockito.method(UserService.class, "isSuperUser", Long.class);Object actual = method.invoke(userService, userId);Assert.assertEquals("返回值不相等", expected, actual);}
}

8.4. 验证私有方法

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest10 {@Testpublic void testIsNotSuperUser() throws Exception {Long userId = 1L;boolean expected = false;UserService userService = PowerMockito.spy(new UserService());PowerMockito.when(userService, "isSuperUser", userId).thenReturn(!expected);boolean actual = userService.isNotSuperUser(userId);PowerMockito.verifyPrivate(userService).invoke("isSuperUser", userId);Assert.assertEquals("返回值不相等", expected, actual);}
}

这里,也可以用Method那套方法进行模拟和验证方法。

9. 主要注解

PowerMock为了更好地支持SpringMVC/SpringBoot项目,提供了一系列的注解,大大地简化了测试代码。

9.1. @RunWith注解

@RunWith(PowerMockRunner.class)

指定JUnit 使用 PowerMock 框架中的单元测试运行器。

9.2. @PrepareForTest注解

@PrepareForTest({ TargetClass.class })

当需要模拟final类、final方法或静态方法时,需要添加@PrepareForTest注解,并指定方法所在的类。如果需要指定多个类,在{}中添加多个类并用逗号隔开即可。

9.3. @Mock注解

@Mock注解创建了一个全部Mock的实例,所有属性和方法全被置空(0或者null)。

9.4. @Spy注解

@Spy注解创建了一个没有Mock的实例,所有成员方法都会按照原方法的逻辑执行,直到被Mock返回某个具体的值为止。

注意:@Spy注解的变量需要被初始化,否则执行时会抛出异常。

9.5. @InjectMocks注解

@InjectMocks注解创建一个实例,这个实例可以调用真实代码的方法,其余用@Mock或@Spy注解创建的实例将被注入到用该实例中。

@Service
public class UserService {@Autowiredprivate UserDAO userDAO;public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();BeanUtils.copyProperties(userVO, userDO);userDAO.modify(userDO);}
}@RunWith(PowerMockRunner.class)
public class UserServiceTest {@Mockprivate UserDAO userDAO;@InjectMocksprivate UserService userService;@Testpublic void testCreateUser() {UserVO userVO = new UserVO();userVO.setId(1L);userVO.setName("changyi");userVO.setDesc("test user");userService.modifyUser(userVO);ArgumentCaptor<UserDO> argumentCaptor = ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(argumentCaptor.capture());UserDO userDO = argumentCaptor.getValue();Assert.assertNotNull("用户实例为空", userDO);Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId());Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName());Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc());}
}

9.6. @Captor注解

@Captor注解在字段级别创建参数捕获器。但是,在测试方法启动前,必须调用MockitoAnnotations.openMocks(this)进行初始化。

@Service
public class UserService {@Autowiredprivate UserDAO userDAO;public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();BeanUtils.copyProperties(userVO, userDO);userDAO.modify(userDO);}
}@RunWith(PowerMockRunner.class)
public class UserServiceTest {@Mockprivate UserDAO userDAO;@InjectMocksprivate UserService userService;@Captorprivate ArgumentCaptor<UserDO> argumentCaptor;@Beforepublic void beforeTest() {MockitoAnnotations.openMocks(this);}@Testpublic void testCreateUser() {UserVO userVO = new UserVO();userVO.setId(1L);userVO.setName("changyi");userVO.setDesc("test user");userService.modifyUser(userVO);Mockito.verify(userDAO).modify(argumentCaptor.capture());UserDO userDO = argumentCaptor.getValue();Assert.assertNotNull("用户实例为空", userDO);Assert.assertEquals("用户标识不相等", userVO.getId(), userDO.getId());Assert.assertEquals("用户名称不相等", userVO.getName(), userDO.getName());Assert.assertEquals("用户描述不相等", userVO.getDesc(), userDO.getDesc());}
}

9.7. @PowerMockIgnore注解

为了解决使用PowerMock后,提示ClassLoader错误。

10. 相关观点

10.1. 《Java开发手册》规范

【强制】好的单元测试必须遵守AIR原则。 说明:单元测试在线上运行时,感觉像空气(AIR)一样感觉不到,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。

A:Automatic(自动化)

I:Independent(独立性)

R:Repeatable(可重复)

【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。

【强制】单元测试是可以重复执行的,不能受到外界环境的影响。

说明:单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。

正例:为了不受外界环境影响,要求设计代码时就把SUT的依赖改成注入,在测试时用spring 这样的DI框架注入一个本地(内存)实现或者Mock实现。

【推荐】编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。

B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。

C:Correct,正确的输入,并得到预期的结果。

D:Design,与设计文档相结合,来编写单元测试。

E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。

10.2. 为什么要使用Mock?

根据网络相关资料,总结观点如下:

Mock可以用来解除外部服务依赖,从而保证了测试用例的独立性。

现在的互联网软件系统,通常采用了分布式部署的微服务,为了单元测试某一服务而准备其它服务,存在极大的依耐性和不可行性。

Mock可以减少全链路测试数据准备,从而提高了编写测试用例的速度。

传统的集成测试,需要准备全链路的测试数据,可能某些环节并不是你所熟悉的。最后,耗费了大量的时间和经历,并不一定得到你想要的结果。现在的单元测试,只需要模拟上游的输入数据,并验证给下游的输出数据,编写测试用例并进行测试的速度可以提高很多倍。

Mock可以模拟一些非正常的流程,从而保证了测试用例的代码覆盖率。

根据单元测试的BCDE原则,需要进行边界值测试(Border)和强制错误信息输入(Error),这样有助于覆盖整个代码逻辑。在实际系统中,很难去构造这些边界值,也能难去触发这些错误信息。而Mock从根本上解决了这个问题:想要什么样的边界值,只需要进行Mock;想要什么样的错误信息,也只需要进行Mock。

Mock可以不用加载项目环境配置,从而保证了测试用例的执行速度。

在进行集成测试时,我们需要加载项目的所有环境配置,启动项目依赖的所有服务接口。往往执行一个测试用例,需要几分钟乃至几十分钟。采用Mock实现的测试用例,不用加载项目环境配置,也不依赖其它服务接口,执行速度往往在几秒之内,大大地提高了单元测试的执行速度。

10.3. 单元测试与集成测试的区别

在实际工作中,不少同学用集成测试代替了单元测试,或者认为集成测试就是单元测试。这里,总结为了单元测试与集成测试的区别:

测试对象不同
单元测试对象是实现了具体功能的程序单元,集成测试对象是概要设计规划中的模块及模块间的组合。

测试方法不同
单元测试中的主要方法是基于代码的白盒测试,集成测试中主要使用基于功能的黑盒测试。

测试时间不同
集成测试要晚于单元测试。

测试内容不同
单元测试主要是模块内程序的逻辑、功能、参数传递、变量引用、出错处理及需求和设计中具体要求方面的测试;而集成测试主要验证各个接口、接口之间的数据传递关系,及模块组合后能否达到预期效果。

原文链接
本文为阿里云原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/513463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OceanBase再破纪录!核心成员陈萌萌:坚持HTAP就是坚持我们做数据库的初心

简介&#xff1a; 2021年5月20日&#xff0c;据国际事务处理性能委员会&#xff08;TPC&#xff0c;Transaction Processing Performance Council&#xff09;官网披露&#xff0c;蚂蚁集团自主研发的分布式关系型数据库OceanBase在数据分析型基准测试&#xff08;TPC-H&#x…

快成物流科技 x mPaaS | 小程序容器加持下的技术架构“提质增效”

简介&#xff1a; 大前端团队如何选型技术&#xff1f;如何快速上手&#xff1f;如何高效协同&#xff1f;让我们看看快成科技如何解决这一问题。 导言 从 2017 年开始&#xff0c;GMTC“移动技术大会”就更名为“大前端技术大会”。发展至今&#xff0c;混合开发、原生开发、前…

直接 root Android 设备,会「隐身」的恶意软件 AbstractEmu 正在偷偷作恶

整理 | 梦依丹出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;“我就点一下&#xff0c;钱就没了”&#xff01;手机不仅给我们带来便利&#xff0c;而且还记录着我们方方面面的信息&#xff0c;甚至是一言一行。正因此&#xff0c;它成了漏洞制作者、恶意软件黑客…

进入中国内地第31年的麦当劳 ,为什么还能不断吸引新消费人群?

简介&#xff1a; 麦当劳的数字化转型从2016年开始全面推行&#xff0c;力求无论何时何地何种方式&#xff0c;消费者都能随心享受麦当劳的产品与服务&#xff0c;数字化转型在过去几年取得显著效果&#xff01;而阿里云数据中台的引入&#xff0c;将成为麦当劳数字化转型在拓展…

配置审计(Config)变配报警设置

简介&#xff1a; 本文作者【紫极zj】&#xff0c;本篇将主要介绍通过配置审计的自定义规则等服务&#xff0c;对负载均衡进行预警行为的相关介绍。 前言 配置审计&#xff08;Config&#xff09;将您分散在各地域的资源整合为全局资源列表&#xff0c;可便捷地搜索全局资源&…

漫画:什么是 “元宇宙” ?

作者|小灰来源|程序员小灰什么是更高的自由度呢&#xff1f;或许有人觉得&#xff0c;我们在网络游戏当中&#xff0c;不是也很自由吗&#xff1f;想怎么玩就怎么玩。但是&#xff0c;无论一款网络游戏的元素有多么丰富&#xff0c;游戏当中的角色、任务、职业、道具、场景&…

程序员写好技术文章的几点小技巧

简介&#xff1a; 去年成为了内网技术分享平台的年度作者&#xff0c;受邀写一篇关于“如何写好文章”的文章。我本身并不喜欢写字&#xff0c;去年写的几篇文章&#xff0c;涉及的话题自带流量&#xff0c;所以阅读量多了一些&#xff0c;谈不上有多擅长。不过还是决定分享一下…

雅虎、领英接连退出中国,GitHub 会受到影响吗?

整理 | 郑丽媛出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;继半个月前微软宣布关闭领英&#xff08;即 LinkedIn&#xff09;在华业务后&#xff0c;本周二&#xff0c;雅虎也宣布了最新消息&#xff1a;自 2021 年 11 月 1 日起&#xff0c;用户将无法从中国大…

高德打车构建可观测性系统实践

简介&#xff1a; 互联网工程的高速发展&#xff0c;分布式、微服务、容器化架构的流行&#xff0c;互联网已全面进入云原生时代。构建系统的方式由最初的单体大应用演变为分布式架构&#xff0c;一台服务器可能仅存几小时甚至几分钟&#xff0c;这种复杂性大大增加了把系统运行…

java script 代码放在jsp 还是放在servlet_ServletContext JSP

会话&#xff1a;四种&#xff1a;1 &#xff1a;Session–保存在服务器上默认的30分2&#xff1a;Cookie 客户端的&#xff0c;maxAge3&#xff1a;重写 url - > url;jsessionidxxxxxxx - > response.encodeRedirectUri(url);4&#xff1a;隐藏表单 1&#xff1a;Serv…

飞猪基于 Serverless 的云+端实践与思考

简介&#xff1a; 过去两年&#xff0c;飞猪前端一直在积极地进行 Serverless 建设和实践&#xff0c;2019 年 - 2020 年我们和集团 Node 架构组、研发平台一起完成了基础能力的建设和业务试点&#xff0c;成为集团率先落地 Serverless 实践的 BU&#xff0c;2020 年 - 2021 年…

unc 目录不受支持_Shopify平台对于店铺模版都提供哪些支持

在自定义Shopify模版之前&#xff0c;请确保您了解可用的支持级别。如果您要进行基本的自定义&#xff0c;则可以从模版开发人员处获取支持。如果您要对模版进行大量更改&#xff0c;请参阅我们的模版支持的其他资源列表。若要了解 Shopify 不支持的自定义&#xff0c;请参阅我…

CSS——定位、CSS高级技巧、修饰属性

1、定位 作用&#xff1a;灵活的改变盒子在网页中的位置 实现&#xff1a; 定位模式&#xff1a;position边偏移&#xff1a;设置盒子的位置 leftrighttopbottom 1.1 相对定位 position&#xff1a;relative <!DOCTYPE html> <html lang"en"> <…

Hologres如何支持亿级用户UV计算

简介&#xff1a; 本文将介绍阿里云Hologres如何基于RoaringBitmap进行UV等高复杂度计算的方案&#xff0c;实现亿级用户万级标签亚秒级分析&#xff0c;帮助用户从Kylin平滑迁移到Hologres&#xff0c;实现更实时、开发更灵活、功能更完善的多维分析能力。 背景介绍 在用户行…

location 拦截所有_电脑广告拦截软件 Adguard Premium

每日一谈我们上个网的时候经常会遇到很多烦人的广告、在线跟踪等&#xff0c;不仅导致你的网站加载速度非常的慢&#xff0c;并且还可能会导致你遇到一些恶意软件和威胁。为了避免这种情况的产生&#xff0c;今天我为大家推荐这款广告拦截软件来阻止你浏览器中的广告&#xff0…

事务消息应用场景、实现原理与项目实战(附全部源码)

简介&#xff1a; 从应用场景出发&#xff0c;给出解决方案与实现原理&#xff0c;并提供整套工业级实现源码。 作者&#xff1a;丁威 活动中心场景介绍 在电商系统上线初期&#xff0c;往往会进行一些“拉新”活动&#xff0c;例如活动部门提出新用户注册送积分、送优惠券活…

request用法_3分钟短文:说说Laravel页面会话之间的数据保存Session用法

引言我们知HTTP请求是没有状态的&#xff0c;两个请求之间没有直接的关联关系。但大多数情况下&#xff0c; 我们需要保持用户的会话间数据的连续性&#xff0c;这时&#xff0c;为了数据安全起见&#xff0c; 有必要在服务器上临时存储一些上下文数据了。这就是 session 设计的…

调研邀请:我们到底需要什么样的低代码平台?

《乔布斯传》中有这样一段话&#xff1a;“有人会说&#xff0c;顾客想要什么产品就提供什么产品&#xff0c;但这并不是我的做事方式。我的职责是在人们还没有意识到需求之前&#xff0c;就研发出他们想要的&#xff0c;我们的任务是搞定那些还没有形成“定论”的事情。”这段…

面向K8s设计误区

简介&#xff1a; K8s 取其精华去其糟粕&#xff0c;是我们程序员应该做的事情。 K8s设计模式 Kubernetes是一个具有普遍意义的容器编排工具&#xff0c;它提供了一套基于容器构建分布式系统的基础依赖&#xff0c;其意义等同于Linux在操作系统中的地位&#xff0c;可以认为是…

电脑word在哪_word是什么?小学生:单词,大学生:论文排版工具

word是什么&#xff0c;对于不同人会有不同的理解&#xff0c;它可能只是一个单词&#xff0c;它也可能是一个排版工具。今天就以我自己的经历给大家讲述一下&#xff0c;人生的不同阶段&#xff0c;word分别是什么。一、小学阶段&#xff0c;好像是一个单词我们那个时候的小学…