当前位置:首页>java>Java单元测试技巧之PowerMock

Java单元测试技巧之PowerMock

  • 2026-01-23 10:00:26
Java单元测试技巧之PowerMock

前言
高德的技术大佬向老师在谈论方法论时说到:“复杂的问题要简单化,简单的问题要深入化。”
这句话让我感触颇深,这何尝不是一套编写代码的方法——把一个复杂逻辑拆分为许多简单逻辑,然后把每一个简单逻辑进行深入实现,最后把这些简单逻辑整合为复杂逻辑,总结为八字真言即是“化繁为简,由简入繁”。
编写Java单元测试用例,其实就是把“复杂的问题要简单化”——即把一段复杂的代码拆解成一系列简单的单元测试用例;写好Java单元测试用例,其实就是把“简单的问题要深入化”——即学习一套方法、总结一套模式并应用到实践中。这里,作者根据日常的工作经验,总结了一些Java单元测试技巧,以供大家交流和学习。
一  准备环境
PowerMock是一个扩展了其它如EasyMock等mock框架的、功能更加强大的框架。PowerMock使用一个自定义类加载器和字节码操作来模拟静态方法、构造方法、final类和方法、私有方法、去除静态初始化器等等。
1  引入PowerMock包
为了引入PowerMock包,需要在pom.xml文件中加入下列maven依赖:
<dependency>    <groupId>org.powermock</groupId>    <artifactId>powermock-module-junit4</artifactId>    <version>2.0.9</version>    <scope>test</scope></dependency><dependency>    <groupId>org.powermock</groupId>    <artifactId>powermock-api-mockito2</artifactId>    <version>2.0.9</version>    <scope>test</scope></dependency>

2  集成SpringMVC项目
在SpringMVC项目中,需要在pom.xml文件中加入JUnit的maven依赖:
<dependency>    <groupId>junit</groupId>    <artifactId>junit</artifactId>    <version>4.12</version>    <scope>test</scope></dependency>
3  集成SpringBoot项目
在SpringBoot项目中,需要在pom.xml文件中加入JUnit的maven依赖:
<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-test</artifactId>    <scope>test</scope></dependency>
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);    }}
二  mock语句
1  mock方法

声明:

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

@RunWith(PowerMockRunner.class)

@PrepareForTest({TargetClass.class})

模拟非final类普通方法
@Getter@Setter@ToStringpublic 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);    }}
模拟final类或final方法
@Getter@Setter@ToStringpublic 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  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);    }}
三  spy语句
如果一个对象,我们只希望模拟它的部分方法,而希望其它方法跟原来一样,可以使用PowerMockito.spy方法代替PowerMockito.mock方法。于是,通过when语句设置过的方法,调用的是模拟方法;而没有通过when语句设置的方法,调用的是原有方法。
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);    }}
2  spy对象
声明:

T PowerMockito.spy(T object);

用途:用于模拟对象的部分方法。
案例:
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);    }}

四  when语句
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();

用途:用于模拟对象方法,先执行原始方法,再返回期望的值、异常、应答,或调用真实的方法。
返回期望值
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);    }}

返回期望异常
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);    }}
返回期望应答
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);    }}
调用真实方法
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);    }}
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异常。
返回期望值
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);    }}
返回期望异常
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);    }}
返回期望应答
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);    }}
模拟无返回值
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);    }}
调用真实方法
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);    }}
3  两种模式的主要区别
两种模式都用于模拟对象方法,在mock实例下使用时,基本上是没有差别的。但是,在spy实例下使用时,when().thenReturn()模式会执行原方法,而doReturn().when()模式不会执行原方法。
测试服务类
@Slf4j@Servicepublic 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  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  参数匹配器(any)
Mockito提供Mockito.anyInt()Mockito.anyStringMockito.any(Class<T> 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);    }}

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);    }}
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);    }}
六  verify语句
验证是确认在模拟过程中,被测试方法是否已按预期方式与其任何依赖方法进行了交互。
格式:

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

用途:用于模拟对象方法,直接返回期望的值、异常、应答,或调用真实的方法,无需执行原始方法。
案例:
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();    }}

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等次数验证器。
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);    }}

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);    }}

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  验证静态方法
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  ReflectionTestUtils.setField方法
在用原生JUnit进行单元测试时,我们一般采用ReflectionTestUtils.setField方法设置私有属性值。
@Servicepublic 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方法设置的是代理实例,从而导致设置不生效。
2 Whitebox.setInternalState方法
现在使用PowerMock进行单元测试时,可以采用Whitebox.setInternalState方法设置私有属性值。
@Servicepublic 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  模拟私有方法
通过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);    }}

通过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;    }}

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);    }}
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那套方法进行模拟和验证方法。
九  主要注解
PowerMock为了更好地支持SpringMVC/SpringBoot项目,提供了一系列的注解,大大地简化了测试代码。
1  @RunWith注解

@RunWith(PowerMockRunner.class)

指定JUnit 使用 PowerMock 框架中的单元测试运行器。
2  @PrepareForTest注解

@PrepareForTest({ TargetClass.class })

当需要模拟final类、final方法或静态方法时,需要添加@PrepareForTest注解,并指定方法所在的类。如果需要指定多个类,在{}中添加多个类并用逗号隔开即可。
3  @Mock注解
@Mock注解创建了一个全部Mock的实例,所有属性和方法全被置空(0或者null)。
4  @Spy注解
@Spy注解创建了一个没有Mock的实例,所有成员方法都会按照原方法的逻辑执行,直到被Mock返回某个具体的值为止。
注意:@Spy注解的变量需要被初始化,否则执行时会抛出异常。
5  @InjectMocks注解
@InjectMocks注解创建一个实例,这个实例可以调用真实代码的方法,其余用@Mock或@Spy注解创建的实例将被注入到用该实例中。
@Servicepublic 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());    }}

6  @Captor注解
@Captor注解在字段级别创建参数捕获器。但是,在测试方法启动前,必须调用MockitoAnnotations.openMocks(this)进行初始化。
@Servicepublic 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());    }}

7  @PowerMockIgnore注解
为了解决使用PowerMock后,提示ClassLoader错误。
十  相关观点
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,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。

2  为什么要使用Mock?
根据网络相关资料,总结观点如下:
Mock可以用来解除外部服务依赖,从而保证了测试用例的独立性
现在的互联网软件系统,通常采用了分布式部署的微服务,为了单元测试某一服务而准备其它服务,存在极大的依耐性和不可行性。
Mock可以减少全链路测试数据准备,从而提高了编写测试用例的速度
传统的集成测试,需要准备全链路的测试数据,可能某些环节并不是你所熟悉的。最后,耗费了大量的时间和经历,并不一定得到你想要的结果。现在的单元测试,只需要模拟上游的输入数据,并验证给下游的输出数据,编写测试用例并进行测试的速度可以提高很多倍。
Mock可以模拟一些非正常的流程,从而保证了测试用例的代码覆盖率
根据单元测试的BCDE原则,需要进行边界值测试(Border)和强制错误信息输入(Error),这样有助于覆盖整个代码逻辑。在实际系统中,很难去构造这些边界值,也能难去触发这些错误信息。而Mock从根本上解决了这个问题:想要什么样的边界值,只需要进行Mock;想要什么样的错误信息,也只需要进行Mock。
Mock可以不用加载项目环境配置,从而保证了测试用例的执行速度
在进行集成测试时,我们需要加载项目的所有环境配置,启动项目依赖的所有服务接口。往往执行一个测试用例,需要几分钟乃至几十分钟。采用Mock实现的测试用例,不用加载项目环境配置,也不依赖其它服务接口,执行速度往往在几秒之内,大大地提高了单元测试的执行速度。
3  单元测试与集成测试的区别
在实际工作中,不少同学用集成测试代替了单元测试,或者认为集成测试就是单元测试。这里,总结为了单元测试与集成测试的区别:
测试对象不同
单元测试对象是实现了具体功能的程序单元,集成测试对象是概要设计规划中的模块及模块间的组合。
测试方法不同
单元测试中的主要方法是基于代码的白盒测试,集成测试中主要使用基于功能的黑盒测试。
测试时间不同
集成测试要晚于单元测试。
测试内容不同
单元测试主要是模块内程序的逻辑、功能、参数传递、变量引用、出错处理及需求和设计中具体要求方面的测试;而集成测试主要验证各个接口、接口之间的数据传递关系,及模块组合后能否达到预期效果。

技术公开课
《Java面向对象编程》

面向对象是Java语言之中最为重要的特征。本课程共32章,详细讲解面向对象的定义、类与对象的组成、内存分析、封装性、继承性、多态性等基础概念,以及面向对象之中重要的代码模型、数据分析方式、核心设计模式原理与分析使用。

点击“阅读原文”开始学习吧~

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-02-10 09:02:05 HTTP/2.0 GET : https://f.mffb.com.cn/a/459021.html
  2. 运行时间 : 0.821044s [ 吞吐率:1.22req/s ] 内存消耗:4,832.47kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=b30b01b92fef37bdb4069abb347ec77c
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000357s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000626s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.001352s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.002276s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000555s ]
  6. SELECT * FROM `set` [ RunTime:0.000196s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000605s ]
  8. SELECT * FROM `article` WHERE `id` = 459021 LIMIT 1 [ RunTime:0.010091s ]
  9. UPDATE `article` SET `lasttime` = 1770685325 WHERE `id` = 459021 [ RunTime:0.001468s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 65 LIMIT 1 [ RunTime:0.000353s ]
  11. SELECT * FROM `article` WHERE `id` < 459021 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.026549s ]
  12. SELECT * FROM `article` WHERE `id` > 459021 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.097697s ]
  13. SELECT * FROM `article` WHERE `id` < 459021 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.364940s ]
  14. SELECT * FROM `article` WHERE `id` < 459021 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.071519s ]
  15. SELECT * FROM `article` WHERE `id` < 459021 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.172019s ]
0.822726s