关于jquery:Java单元测试技巧之PowerMock

6次阅读

共计 42154 个字符,预计需要花费 106 分钟才能阅读完成。

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

前言

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

  1. 筹备环境

========

PowerMock 是一个扩大了其它如 EasyMock 等 mock 框架的、性能更加弱小的框架。PowerMock 应用一个自定义类加载器和字节码操作来模仿静态方法、构造方法、final 类和办法、公有办法、去除动态初始化器等等。

1.1. 引入 PowerMock 包

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

1.2. 集成 SpringMVC 我的项目

在 SpringMVC 我的项目中,须要在 pom.xml 文件中退出 JUnit 的 maven 依赖:

1.3. 集成 SpringBoot 我的项目

在 SpringBoot 我的项目中,须要在 pom.xml 文件中退出 JUnit 的 maven 依赖:

1.4. 一个简略的测试用例

这里,用 List 举例,模仿一个不存在的列表,然而返回的列表大小为 100。

public class ListTest {
    @Test
    public 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);
    }
} 
  1. 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;
    @Override
    public double getArea() {return width * height;}
}

public class RectangleTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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}),否则模仿办法不失效。

  1. 参数匹配器

=========

在执行单元测试时,有时候并不关怀传入的参数的值,能够应用参数匹配器。

5.1. 参数匹配器(any)

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

public class ListTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. verify 语句

============

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

格局:

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

用处:

用于模仿对象办法,间接返回冀望的值、异样、应答,或调用实在的办法,无需执行原始办法。

案例:

6.1. 验证调用办法

public class ListTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 公有属性

========

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 {
    @Autowired
    private UserService userService;
    @Test
    public 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 {
    @InjectMocks
    private UserService userService;
    @Test
    public void testGetUserLimit() {
        Long expected = 1000L;
        Whitebox.setInternalState(userService, "userLimit", expected);
        Long actual = userService.getUserLimit();
        Assert.assertEquals("返回值不相等", expected, actual);
    }
}

留神:须要加上注解 @RunWith(PowerMockRunner.class)。

  1. 公有办法

========

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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 那套办法进行模仿和验证办法。

  1. 次要注解

========

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 {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Test
    public 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 {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Captor
    private ArgumentCaptor<UserDO> argumentCaptor;
    @Before
    public void beforeTest() {MockitoAnnotations.openMocks(this);
    }
    @Test
    public 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 谬误。

  1. 相干观点

=========

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. 单元测试与集成测试的区别

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

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

前言

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

  1. 筹备环境

========

PowerMock 是一个扩大了其它如 EasyMock 等 mock 框架的、性能更加弱小的框架。PowerMock 应用一个自定义类加载器和字节码操作来模仿静态方法、构造方法、final 类和办法、公有办法、去除动态初始化器等等。

1.1. 引入 PowerMock 包

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

1.2. 集成 SpringMVC 我的项目

在 SpringMVC 我的项目中,须要在 pom.xml 文件中退出 JUnit 的 maven 依赖:

1.3. 集成 SpringBoot 我的项目

在 SpringBoot 我的项目中,须要在 pom.xml 文件中退出 JUnit 的 maven 依赖:

1.4. 一个简略的测试用例

这里,用 List 举例,模仿一个不存在的列表,然而返回的列表大小为 100。

public class ListTest {
    @Test
    public 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);
    }
} 
  1. 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;
    @Override
    public double getArea() {return width * height;}
}

public class RectangleTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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}),否则模仿办法不失效。

  1. 参数匹配器

=========

在执行单元测试时,有时候并不关怀传入的参数的值,能够应用参数匹配器。

5.1. 参数匹配器(any)

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

public class ListTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. verify 语句

============

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

格局:

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

用处:

用于模仿对象办法,间接返回冀望的值、异样、应答,或调用实在的办法,无需执行原始办法。

案例:

6.1. 验证调用办法

public class ListTest {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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);
    }
}
  1. 公有属性

========

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 {
    @Autowired
    private UserService userService;
    @Test
    public 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 {
    @InjectMocks
    private UserService userService;
    @Test
    public void testGetUserLimit() {
        Long expected = 1000L;
        Whitebox.setInternalState(userService, "userLimit", expected);
        Long actual = userService.getUserLimit();
        Assert.assertEquals("返回值不相等", expected, actual);
    }
}

留神:须要加上注解 @RunWith(PowerMockRunner.class)。

  1. 公有办法

========

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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 {
    @Test
    public 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 那套办法进行模仿和验证办法。

  1. 次要注解

========

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 {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Test
    public 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 {
    @Autowired
    private UserDAO userDAO;
    public void modifyUser(UserVO userVO) {UserDO userDO = new UserDO();
        BeanUtils.copyProperties(userVO, userDO);
        userDAO.modify(userDO);
    }
}

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @Mock
    private UserDAO userDAO;
    @InjectMocks
    private UserService userService;
    @Captor
    private ArgumentCaptor<UserDO> argumentCaptor;
    @Before
    public void beforeTest() {MockitoAnnotations.openMocks(this);
    }
    @Test
    public 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 谬误。

  1. 相干观点

=========

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. 单元测试与集成测试的区别

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

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

测试方法不同
单元测试中的次要办法是基于代码的白盒测试,集成测试中次要应用基于性能的黑盒测试。

测试工夫不同
集成测试要晚于单元测试。

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

作者:开发者小助手_LS

本文为阿里云原创内容,未经容许不得转载 测试对象不同 *
单元测试对象是实现了具体性能的程序单元,集成测试对象是概要设计规划中的模块及模块间的组合。

测试方法不同
单元测试中的次要办法是基于代码的白盒测试,集成测试中次要应用基于性能的黑盒测试。

测试工夫不同
集成测试要晚于单元测试。

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

作者:开发者小助手_LS
原文链接
本文为阿里云原创内容,未经容许不得转载

正文完
 0