Java编程技巧之单元测试用例编写流程

丰富的线上&线下活动,深入探索云世界

做任务,得社区积分和周边

最真实的开发者用云体验

让每位学生受益于普惠算力

让创作激发创新

资深技术专家手把手带教

遇见技术追梦人

技术交流,直击现场

海量开发者使用工具、手册,免费下载

极速、全面、稳定、安全的开源镜像

开发手册、白皮书、案例集等实战精华

为开发者定制的Chrome浏览器插件

清代杰出思想家章学诚有一句名言:“学必求其心得,业必贵其专精。”

意思是:学习上一定要追求心得体会,事业上一定要贵以专注精深。做技术就是这样,一件事如果做到了极致,就必然会有所心得体会。作者最近在一个项目上,追求单元测试覆盖率到极致(行覆盖率96.11%,分支覆盖率93.35%),所以才有了这篇心得体会。

Mockito是一个单元测试模拟框架,可以让你写出优雅、简洁的单元测试代码。Mockito采用了模拟技术,模拟了一些在应用中依赖的复杂对象,从而把测试对象和依赖对象隔离开来。

PowerMock是一个单元测试模拟框架,是在其它单元测试模拟框架的基础上做出扩展。通过提供定制的类加载器以及一些字节码篡改技术的应用,PowerMock实现了对静态方法、构造方法、私有方法以及final方法的模拟支持等强大的功能。但是,正因为PowerMock进行了字节码篡改,导致部分单元测试用例并不被JaCoco统计覆盖率。

通过作者多年单元测试的编写经验,优先推荐使用Mockito提供的功能;只有在Mockito提供的功能不能满足需求时,才会采用PowerMock提供的功能;但是,不推荐使用影响JaCoco统计覆盖率的PowerMock功能。在本文中,我们也不会对影响JaCoco统计覆盖率的PowerMock功能进行介绍。

下面,将以Mockito为主、以PowerMock为辅,介绍一下如何编写单元测试用例。

为了引入Mockito和PowerMock包,需要在maven项目的pom.xml文件中加入以下包依赖:

org.powermockpowermock-core${powermock.version}testorg.powermockpowermock-api-mockito2${powermock.version}testorg.powermockpowermock-module-junit4${powermock.version}test其中,powermock.version为2.0.9,为当前的最新版本,可根据实际情况修改。在PowerMock包中,已经包含了对应的Mockito和JUnit包,所以无需单独引入Mockito和JUnit包。

一个典型的服务代码案例如下:

UserServiceTest.java:

{"name":"test"}userCreateDO.json:

{"id":1,"name":"test"}userModifyDO.json:

{"id":1,"name":"test"}通过执行以上测试用例,可以看到对源代码进行了100%的行覆盖。

通过上一章编写Java类单元测试用例的实践,可以总结出以下Java类单元测试用例的编写流程:

上面一共有3个测试用例,这里只以测试用例testCreateUserWithNew(创建用户-新)为例说明。

第1步是定义对象阶段,主要包括定义被测对象、模拟依赖对象(类成员)、注入依赖对象(类成员)3大部分。

在编写单元测试时,首先需要定义被测对象,或直接初始化、或通过Spy包装……其实,就是把被测试服务类进行实例化。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;2.1.2.模拟依赖对象(类成员)在一个服务类中,我们定义了一些类成员对象——服务(Service)、数据访问对象(DAO)、参数(Value)等。在Spring框架中,这些类成员对象通过@Autowired、@Value等方式注入,它们可能涉及复杂的环境配置、依赖第三方接口服务……但是,在单元测试中,为了解除对这些类成员对象的依赖,我们需要对这些类成员对象进行模拟。

/**模拟依赖对象*//**用户DAO*/@MockprivateUserDAOuserDAO;/**标识生成器*/@MockprivateIdGeneratoridGenerator;2.1.3.注入依赖对象(类成员)当模拟完这些类成员对象后,我们需要把这些类成员对象注入到被测试类的实例中。以便在调用被测试方法时,可能使用这些类成员对象,而不至于抛出空指针异常。

/**定义被测对象*//**用户服务*/@InjectMocksprivateUserServiceuserService;/***在测试之前*/@BeforepublicvoidbeforeTest(){//注入依赖对象Whitebox.setInternalState(userService,"canModify",Boolean.TRUE);}2.2.模拟方法阶段第2步是模拟方法阶段,主要包括模拟依赖对象(参数或返回值)、模拟依赖方法2大部分。

通常,在调用一个方法时,需要先指定方法的参数,然后获取到方法的返回值。所以,在模拟方法之前,需要先模拟该方法的参数和返回值。

LonguserId=1L;2.2.2.模拟依赖方法在模拟完依赖的参数和返回值后,就可以利用Mockito和PowerMock的功能,进行依赖方法的模拟。如果依赖对象还有方法调用,还需要模拟这些依赖对象的方法。

//模拟依赖方法//模拟依赖方法:userDAO.getByNameMockito.doReturn(null).when(userDAO).getIdByName(Mockito.anyString());//模拟依赖方法:idGenerator.nextMockito.doReturn(userId).when(idGenerator).next();2.3.调用方法阶段第3步是调用方法阶段,主要包括模拟依赖对象(参数)、调用被测方法、验证参数对象(返回值)3步。

在调用被测方法之前,需要模拟被测方法的参数。如果这些参数还有方法调用,还需要模拟这些参数的方法。

Stringtext=ResourceHelper.getResourceAsString(getClass(),"userCreateVO.json");UserVOuserCreate=JSON.parseObject(text,UserVO.class);2.3.2.调用被测方法在准备好参数对象后,就可以调用被测试方法了。如果被测试方法有返回值,需要定义变量接收返回值;如果被测试方法要抛出异常,需要指定期望的异常。

userService.createUser(userCreate)2.3.3.验证数据对象(返回值)在调用被测试方法后,如果被测试方法有返回值,需要验证这个返回值是否符合预期;如果被测试方法要抛出异常,需要验证这个异常是否满足要求。

Assert.assertEquals("用户标识不一致",userId,userService.createUser(userCreate));2.4.验证方法阶段第4步是验证方法阶段,主要包括验证依赖方法、验证数据对象(参数)、验证依赖对象3步。

作为一个完整的测试用例,需要对每一个模拟的依赖方法调用进行验证。

//验证依赖方法//验证依赖方法:userDAO.getByNameMockito.verify(userDAO).getIdByName(userCreate.getName());//验证依赖方法:idGenerator.nextMockito.verify(idGenerator).next();//验证依赖方法:userDAO.createArgumentCaptoruserCreateCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).create(userCreateCaptor.capture());2.4.2.验证数据对象(参数)对应一些模拟的依赖方法,有些参数对象是被测试方法内部生成的。为了验证代码逻辑的正确性,就需要对这些参数对象进行验证,看这些参数对象值是否符合预期。

text=ResourceHelper.getResourceAsString(getClass(),"userCreateDO.json");Assert.assertEquals("用户创建不一致",text,JSON.toJSONString(userCreateCaptor.getValue()));2.4.3.验证依赖对象作为一个完整的测试用例,应该保证每一个模拟的依赖方法调用都进行了验证。正好,Mockito提供了一套方法,用于验证模拟对象所有方法调用都得到了验证。

//验证依赖对象Mockito.verifyNoMoreInteractions(idGenerator,userDAO);

直接构建一个对象,总是简单又直接。

UserServiceuserService=newUserService();3.2.利用Mockito.spy方法Mockito提供一个spy功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都是真实方法,除非主动去模拟对应方法。所以,利用spy功能来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。

UserServiceuserService=Mockito.spy(newUserService());UserServiceuserService=Mockito.spy(UserService.class);AbstractOssServiceossService=Mockito.spy(AbstractOssService.class);3.3.利用@Spy注解@Spy注解跟Mockito.spy方法一样,可以用来定义被测对象,适合于需要模拟被测类自身方法的情况,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassCompanyServiceTest{@SpyprivateUserServiceuserService=newUserService();...}注意:@Spy注解对象需要初始化。如果是虚基类或接口,可以用Mockito.mock方法实例化。

@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。所以,@InjectMocks注解本身就可以用来定义被测对象。@InjectMocks注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassUserServiceTest{@InjectMocksprivateUserServiceuserService;...}

在编写单元测试用例时,需要模拟各种依赖对象——类成员、方法参数和方法返回值。

如果需要构建一个对象,最简单直接的方法就是——定义对象并赋值。

LonguserId=1L;StringuserName="admin";UserDOuser=newUser();user.setId(userId);user.setName(userName);ListuserIdList=Arrays.asList(1L,2L,3L);4.2.反序列化对象如果对象字段或层级非常庞大,采用直接构建对象方法,可能会编写大量构建程序代码。这种情况,可以考虑反序列化对象,将会大大减少程序代码。由于JSON字符串可读性高,这里就以JSON为例,介绍反序列化对象。

反序列化模型对象:

Stringtext=ResourceHelper.getResourceAsString(getClass(),"user.json");UserDOuser=JSON.parseObject(text,UserDO.class);反序列化集合对象:

Stringtext=ResourceHelper.getResourceAsString(getClass(),"userList.json");ListuserList=JSON.parseArray(text,UserDO.class);反序列化映射对象:

Stringtext=ResourceHelper.getResourceAsString(getClass(),"userMap.json");MapuserMap=JSON.parseObject(text,newTypeReference>(){});4.3.利用Mockito.mock方法Mockito提供一个mock功能,用于拦截那些尚未实现或不期望被真实调用的方法,默认所有方法都已被模拟——方法为空并返回默认值(null或0),除非主动执行doCallRealMethod或thenCallRealMethod操作,才能够调用真实的方法。

利用Mockito.mock方法模拟依赖对象,主要用于以下几种情形:

MockClassmockClass=Mockito.mock(MockClass.class);ListuserIdList=(List)Mockito.mock(List.class);4.4.利用@Mock注解@Mock注解跟Mockito.mock方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Mock注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassUserServiceTest{@MockprivateUserDAOuserDAO;...}4.5.利用Mockito.spy方法Mockito.spy方法跟Mockito.mock方法功能相似,只是Mockito.spy方法默认所有方法都是真实方法,除非主动去模拟对应方法。

UserServiceuserService=Mockito.spy(newUserService());UserServiceuserService=Mockito.spy(UserService.class);AbstractOssServiceossService=Mockito.spy(AbstractOssService.class);4.6.利用@Spy注解@Spy注解跟Mockito.spy方法一样,可以用来模拟依赖对象,适用于普通类、接口和虚基类。@Spy注解需要配合@RunWith注解使用。

当模拟完这些类成员对象后,我们需要把这些类成员对象注入到被测试类的实例中。以便在调用被测试方法时,可能使用这些类成员对象,而不至于抛出空指针异常。

如果类定义了Setter方法,可以直接调用方法设置字段值。

userService.setMaxCount(100);userService.setUserDAO(userDAO);5.2.利用ReflectionTestUtils.setField方法注入JUnit提供ReflectionTestUtils.setField方法设置属性字段值。

ReflectionTestUtils.setField(userService,"maxCount",100);ReflectionTestUtils.setField(userService,"userDAO",userDAO);5.3.利用Whitebox.setInternalState方法注入PowerMock提供Whitebox.setInternalState方法设置属性字段值。

Whitebox.setInternalState(userService,"maxCount",100);Whitebox.setInternalState(userService,"userDAO",userDAO);5.4.利用@InjectMocks注解注入@InjectMocks注解用来创建一个实例,并将其它对象(@Mock、@Spy或直接定义的对象)注入到该实例中。@InjectMocks注解需要配合@RunWith注解使用。

@RunWith(PowerMockRunner.class)publicclassUserServiceTest{@MockprivateUserDAOuserDAO;privateBooleancanModify;@InjectMocksprivateUserServiceuserService;...}5.5.设置静态常量字段值有时候,我们需要对静态常量对象进行模拟,然后去验证是否执行了对应分支下的方法。比如:需要模拟Lombok的@Slf4j生成的log静态常量。但是,Whitebox.setInternalState方法和@InjectMocks注解并不支持设置静态常量,需要自己实现一个设置静态常量的方法:

publicfinalclassFieldHelper{publicstaticvoidsetStaticFinalField(Class<>clazz,StringfieldName,ObjectfieldValue)throwsNoSuchFieldException,IllegalAccessException{Fieldfield=clazz.getDeclaredField(fieldName);FieldUtils.removeFinalModifier(field);FieldUtils.writeStaticField(field,fieldValue,true);}}具体使用方法如下:

FieldHelper.setStaticFinalField(UserService.class,"log",log);注意:经过测试,该方法对于int、Integer等基础类型并不生效,应该是编译器常量优化导致。

在模拟完依赖的参数和返回值后,就可以利用Mockito和PowerMock的功能,进行依赖方法的模拟。如果依赖对象还有方法调用,还需要模拟这些依赖对象的方法。

Mockito.doNothing().when(userDAO).delete(userId);6.1.2.模拟方法单个返回值Mockito.doReturn(user).when(userDAO).get(userId);Mockito.when(userDAO.get(userId)).thenReturn(user);6.1.3.模拟方法多个返回值直接列举出多个返回值:

Mockito.doReturn(record0,record1,record2,null).when(recordReader).read();Mockito.when(recordReader.read()).thenReturn(record0,record1,record2,null);转化列表为多个返回值:

ListrecordList=...;Mockito.doReturn(recordList.get(0),recordList.subList(1,recordList.size()).toArray()).when(recordReader).read();Mockito.when(recordReader.read()).thenReturn(recordList.get(0),recordList.subList(1,recordList.size()).toArray());6.1.4.模拟方法定制返回值MapuserMap=...;Mockito.doAnswer(invocation->userMap.get(invocation.getArgument(0))).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenReturn(invocation->userMap.get(invocation.getArgument(0)));Mockito.when(userDAO.get(Mockito.anyLong())).then(invocation->userMap.get(invocation.getArgument(0)));6.1.5.模拟方法抛出单个异常指定单个异常类型:

Mockito.doThrow(PersistenceException.class).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenThrow(PersistenceException.class);指定单个异常对象:

Mockito.doThrow(exception).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenThrow(exception);6.1.6.模拟方法抛出多个异常指定多个异常类型:

Mockito.doThrow(PersistenceException.class,RuntimeException.class).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenThrow(PersistenceException.class,RuntimeException.class);指定多个异常对象:

Mockito.doThrow(exception1,exception2).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenThrow(exception1,exception2);6.1.7.直接调用真实方法Mockito.doCallRealMethod().when(userService).getUser(userId);Mockito.when(userService.getUser(userId)).thenCallRealMethod();6.2.根据参数模拟方法Mockito提供do-when语句和when-then语句模拟方法。

对于无参数的方法模拟:

Mockito.doReturn(deleteCount).when(userDAO).deleteAll();Mockito.when(userDAO.deleteAll()).thenReturn(deleteCount);6.2.2.模拟指定参数方法对于指定参数的方法模拟:

Mockito.doReturn(user).when(userDAO).get(userId);Mockito.when(userDAO.get(userId)).thenReturn(user);6.2.3.模拟任意参数方法在编写单元测试用例时,有时候并不关心传入参数的具体值,可以使用Mockito参数匹配器的any方法。Mockito提供了anyInt、anyLong、anyString、anyList、anySet、anyMap、any(Classclazz)等方法来表示任意值。

Mockito.doReturn(user).when(userDAO).get(Mockito.anyLong());Mockito.when(userDAO.get(Mockito.anyLong())).thenReturn(user);6.2.4.模拟可空参数方法Mockito参数匹配器的any具体方法,并不能够匹配null对象。而Mockito提供一个nullable方法,可以匹配包含null对象的任意对象。此外,Mockito.any()方法也可以用来匹配可空参数。

Mockito.doReturn(user).when(userDAO).queryCompany(Mockito.anyLong(),Mockito.nullable(Long.class));Mockito.when(userDAO.queryCompany(Mockito.anyLong(),Mockito.any())).thenReturn(user);6.2.5.模拟必空参数方法同样,如果要匹配null对象,可以使用isNull方法,或使用eq(null)。

Mockito.doReturn(user).when(userDAO).queryCompany(Mockito.anyLong(),Mockito.isNull());Mockito.when(userDAO.queryCompany(Mockito.anyLong(),Mockito.eq(null))).thenReturn(user);6.2.6.模拟不同参数方法Mockito支持按不同的参数分别模拟同一方法。

Mockito.doReturn(user1).when(userDAO).get(1L);Mockito.doReturn(user2).when(userDAO).get(2L);...注意:如果一个参数满足多个模拟方法条件,会以最后一个模拟方法为准。

对于一些变长度参数方法,可以按实际参数个数进行模拟:

Mockito.when(userService.delete(Mockito.anyLong()).thenReturn(true);Mockito.when(userService.delete(1L,2L,3L).thenReturn(true);也可以用Mockito.any()模拟一个通用匹配方法:

Mockito.when(userService.delete(Mockito.any()).thenReturn(true);注意:Mockito.any()并不等于Mockito.any(Classtype),前者可以匹配null和类型T的可变参数,后者只能匹配T必填参数。

PowerMock提供对final方法的模拟,方法跟模拟普通方法一样。但是,需要把对应的模拟类添加到@PrepareForTest注解中。

//添加@PrepareForTest注解@PrepareForTest({UserService.class})//跟模拟普通方法完全一致Mockito.doReturn(userId).when(idGenerator).next();Mockito.when(idGenerator.next()).thenReturn(userId);6.3.2.模拟私有方法PowerMock提供提对私有方法的模拟,但是需要把私有方法所在的类放在@PrepareForTest注解中。

PowerMockito.doReturn(true).when(UserService.class,"isSuper",userId);PowerMockito.when(UserService.class,"isSuper",userId).thenReturn(true);6.3.3.模拟构造方法PowerMock提供PowerMockito.whenNew方法来模拟构造方法,但是需要把使用构造方法的类放在@PrepareForTest注解中。

PowerMockito.whenNew(UserDO.class).withNoArguments().thenReturn(userDO);PowerMockito.whenNew(UserDO.class).withArguments(userId,userName).thenReturn(userDO);6.3.4.模拟静态方法PowerMock提供PowerMockito.mockStatic和PowerMockito.spy来模拟静态方法类,然后就可以模拟静态方法了。同样,需要把对应的模拟类添加到@PrepareForTest注解中。

在准备好参数对象后,就可以调用被测试方法了。

如果把方法按访问权限分类,可以简单地分为有访问权限和无访问权限两种。但实际上,Java语言中提供了public、protected、private和缺失共4种权限修饰符,在不同的环境下又对应不同的访问权限。具体映射关系如下:

修饰符

本类

本包

子类

其它

public

protected

缺省

private

下面,将根据有访问权限和无访问权限两种情况,来介绍如何调用被测方法。

可以直接调用有访问权限的构造方法。

UserDOuser=newUser();UserDOuser=newUser(1L,"admin");7.1.2.调用无访问权限的构造方法调用无访问权限的构造方法,可以使用PowerMock提供的Whitebox.invokeConstructor方法。

Whitebox.invokeConstructor(NumberHelper.class);Whitebox.invokeConstructor(User.class,1L,"admin");备注:该方法也可以调用有访问权限的构造方法,但是不建议使用。

可以直接调用有访问权限的普通方法。

userService.deleteUser(userId);Useruser=userService.getUser(userId);7.2.2.调用无权限访问的普通方法调用无访问权限的普通方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

Useruser=(User)Whitebox.invokeMethod(userService,"isSuper",userId);也可以使用PowerMock提供Whitebox.getMethod方法和PowerMockito.method方法,可以直接获取对应类方法对象。然后,通过Method的invoke方法,可以调用没有访问权限的方法。

Methodmethod=Whitebox.getMethod(UserService.class,"isSuper",Long.class);Methodmethod=PowerMockito.method(UserService.class,"isSuper",Long.class);Useruser=(User)method.invoke(userService,userId);备注:该方法也可以调用有访问权限的普通方法,但是不建议使用。

可以直接调用有访问权限的静态方法。

booleanisPositive=NumberHelper.isPositive(-1);7.3.2.调用无权限访问的静态方法调用无权限访问的静态方法,可以使用PowerMock提供的Whitebox.invokeMethod方法。

Stringvalue=(String)Whitebox.invokeMethod(JSON.class,"toJSONString",object);备注:该方法也可以调用有访问权限的静态方法,但是不建议使用。

在单元测试中,验证是确认模拟的依赖方法是否按照预期被调用或未调用的过程。Mockito提供了许多方法来验证依赖方法调用,给我们编写单元测试用例带来了很大的帮助。

Mockito.verify(userDAO).deleteAll();8.1.2.验证指定参数方法调用Mockito.verify(userDAO).delete(userId);Mockito.verify(userDAO).delete(Mockito.eq(userId));8.1.3.验证任意参数方法调用Mockito.verify(userDAO).delete(Mockito.anyLong());8.1.4.验证可空参数方法调用Mockito.verify(userDAO).queryCompany(Mockito.anyLong(),Mockito.nullable(Long.class));8.1.5.验证必空参数方法调用Mockito.verify(userDAO).queryCompany(Mockito.anyLong(),Mockito.isNull());8.1.6.验证可变参数方法调用对于一些变长度参数方法,可以按实际参数个数进行验证:

Mockito.verify(userService).delete(Mockito.any(Long.class));Mockito.verify(userService).delete(1L,2L,3L);也可以用Mockito.any()进行通用验证:

Mockito.verify(userService).delete(Mockito.any());8.2.验证方法调用次数8.2.1.验证方法默认调用1次Mockito.verify(userDAO).delete(userId);8.2.2.验证方法从不调用Mockito.verify(userDAO,Mockito.never()).delete(userId);8.2.3.验证方法调用n次Mockito.verify(userDAO,Mockito.times(n)).delete(userId);8.2.4.验证方法调用至少1次Mockito.verify(userDAO,Mockito.atLeastOnce()).delete(userId);8.2.5.验证方法调用至少n次Mockito.verify(userDAO,Mockito.atLeast(n)).delete(userId);8.2.2.验证方法调用最多1次Mockito.verify(userDAO,Mockito.atMostOnce()).delete(userId);8.2.6.验证方法调用最多n次Mockito.verify(userDAO,Mockito.atMost(n)).delete(userId);8.2.7.验证方法调用指定n次Mockito允许按顺序进行验证方法调用,未被验证到的方法调用将不会被标记为已验证。

Mockito.verify(userDAO,Mockito.call(n)).delete(userId);8.2.8.验证对象及其方法调用1次用于验证对象及其方法调用1次,如果该对象还有别的方法被调用或者该方法调用了多次,都将导致验证方法调用失败。

Mockito.verify(userDAO,Mockito.only()).delete(userId);相当于:

Mockito.verify(userDAO).delete(userId);Mockito.verifyNoMoreInteractions(userDAO);8.3.验证方法调用并捕获参数值Mockito提供ArgumentCaptor类来捕获参数值,通过调用forClass(Classclazz)方法来构建一个ArgumentCaptor对象,然后在验证方法调用时来捕获参数,最后获取到捕获的参数值并验证。如果一个方法有多个参数都要捕获并验证,那就需要创建多个ArgumentCaptor对象。

ArgumentCaptor的主要接口方法:

在测试用例方法中,直接使用ArgumentCaptor.forClass方法定义参数捕获器。

ArgumentCaptoruserCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO).modify(userCaptor.capture());UserDOuser=userCaptor.getValue();注意:定义泛型类的参数捕获器时,存在强制类型转化,会引起编译器警告。

也可以用Mockito提供的@Captor注解,在测试用例类中定义参数捕获器。

@RunWith(PowerMockRunner.class)publicclassUserServiceTest{@CaptorprivateArgumentCaptoruserCaptor;@TestpublicvoidtestModifyUser(){...Mockito.verify(userDAO).modify(userCaptor.capture());UserDOuser=userCaptor.getValue();}}注意:定义泛型类的参数捕获器时,由于是Mockito自行初始化,不会产生编译器警告。

ArgumentCaptoruserCaptor=ArgumentCaptor.forClass(UserDO.class);Mockito.verify(userDAO,Mockito.atLeastOnce()).modify(userCaptor.capture());ListuserList=userCaptor.getAllValues();8.4.验证其它特殊方法8.4.1.验证final方法调用final方法的验证跟普通方法类似,这里不再累述。

PowerMockito提供verifyPrivate方法验证私有方法调用。

PowerMockito.verifyPrivate(myClass,times(1)).invoke("unload",any(List.class));8.4.3.验证构造方法调用PowerMockito提供verifyNew方法验证构造方法调用。

PowerMockito.verifyNew(MockClass.class).withNoArguments();PowerMockito.verifyNew(MockClass.class).withArguments(someArgs);8.4.4.验证静态方法调用PowerMockito提供verifyStatic方法验证静态方法调用。

PowerMockito.verifyStatic(StringUtils.class);StringUtils.isEmpty(string);

JUnit测试框架中Assert类就是断言工具类,主要验证单元测试中实际数据对象与期望数据对象一致。在调用被测方法时,需要对返回值和异常进行验证;在验证方法调用时,也需要对捕获的参数值进行验证。

通过JUnit提供的Assert.assertNull方法验证数据对象为空。

Assert.assertNull("用户标识必须为空",userId);9.1.2.验证数据对象非空通过JUnit提供的Assert.assertNotNull方法验证数据对象非空。

Assert.assertNotNull("用户标识不能为空",userId);9.2.验证数据对象布尔值9.2.1.验证数据对象为真通过JUnit提供的Assert.assertTrue方法验证数据对象为真。

Assert.assertTrue("返回值必须为真",NumberHelper.isPositive(1));9.2.2.验证数据对象为假通过JUnit提供的Assert.assertFalse方法验证数据对象为假。

Assert.assertFalse("返回值必须为假",NumberHelper.isPositive(-1));9.3.验证数据对象引用在单元测试用例中,对于一些参数或返回值对象,不需要验证对象具体取值,只需要验证对象引用是否一致。

JUnit提供的Assert.assertSame方法验证数据对象一致。

UserDOexpectedUser=...;Mockito.doReturn(expectedUser).when(userDAO).get(userId);UserDOactualUser=userService.getUser(userId);Assert.assertSame("用户必须一致",expectedUser,actualUser);9.3.1.验证数据对象不一致JUnit提供的Assert.assertNotSame方法验证数据对象一致。

UserDOexpectedUser=...;Mockito.doReturn(expectedUser).when(userDAO).get(userId);UserDOactualUser=userService.getUser(otherUserId);Assert.assertNotSame("用户不能一致",expectedUser,actualUser);9.4.验证数据对象值JUnit提供Assert.assertEquals、Assert.assertNotEquals、Assert.assertArrayEquals方法组,可以用来验证数据对象值是否相等。

对于简单数据对象(比如:基础类型、包装类型、实现了equals的数据类型……),可以直接通过JUnit的Assert.assertEquals和Assert.assertNotEquals方法组进行验证。

Assert.assertNotEquals("用户名称不一致","admin",userName);Assert.assertEquals("账户金额不一致",10000.0D,accountAmount,1E-6D);9.4.2.验证简单数组或集合对象对于简单数组对象(比如:基础类型、包装类型、实现了equals的数据类型……),可以直接通过JUnit的Assert.assertArrayEquals方法组进行验证。对于简单集合对象,也可以通过Assert.assertEquals方法验证。

Long[]userIds=...;Assert.assertArrayEquals("用户标识列表不一致",newLong[]{1L,2L,3L},userIds);ListuserIdList=...;Assert.assertEquals("用户标识列表不一致",Arrays.asList(1L,2L,3L),userIdList);9.4.3.验证复杂数据对象对于复杂的JavaBean数据对象,需要验证JavaBean数据对象的每一个属性字段。

UserDOuser=...;Assert.assertEquals("用户标识不一致",Long.valueOf(1L),user.getId());Assert.assertEquals("用户名称不一致","admin",user.getName());Assert.assertEquals("用户公司标识不一致",Long.valueOf(1L),user.getCompany().getId());...9.4.4.验证复杂数组或集合对象对于复杂的JavaBean数组和集合对象,需要先展开数组和集合对象中每一个JavaBean数据对象,然后验证JavaBean数据对象的每一个属性字段。

ListuserList=...;Stringtext=ResourceHelper.getResourceAsString(getClass(),"userList.json");Assert.assertEquals("用户列表不一致",text,JSON.toJSONString(userList));通常使用JSON.toJSONString方法把Map对象转化为字符串,其中key-value的顺序具有不确定性,无法用于验证两个对象是否一致。这里,JSON提供序列化选项SerializerFeature.MapSortField(映射排序字段),可以用于保证序列化后的key-value的有序性。

Map>userMap=...;Stringtext=ResourceHelper.getResourceAsString(getClass(),"userMap.json");Assert.assertEquals("用户映射不一致",text,JSON.toJSONString(userMap,SerializerFeature.MapSortField));9.4.6.验证数据对象私有属性字段有时候,单元测试用例需要对复杂对象的私有属性字段进行验证。而PowerMockito提供的Whitebox.getInternalState方法,获取轻松地获取到私有属性字段值。

MapperScannerConfigurerconfigurer=myBatisConfiguration.buildMapperScannerConfigurer();Assert.assertEquals("基础包不一致","com.alibaba.example",Whitebox.getInternalState(configurer,"basePackage"));9.5.验证异常对象内容异常作为Java语言的重要特性,是Java语言健壮性的重要体现。捕获并验证异常数据内容,也是测试用例的一种。

JUnit的注解@Test提供了一个expected属性,可以指定一个期望的异常类型,用来捕获并验证异常。但是,这种方式只能验证异常类型,并不能验证异常原因和消息。

@RuleprivateExpectedExceptionexception=ExpectedException.none();@TestpublicvoidtestGetUser(){//模拟依赖方法LonguserId=123L;Mockito.doReturn(null).when(userDAO).get(userId);//调用被测方法exception.expect(ExampleException.class);exception.expectMessage(String.format("用户(%s)不存在",userId));userService.getUser(userId);}9.5.3.通过Assert.assertThrows验证异常对象在最新版的JUnit中,提供了一个更为简洁的异常验证方式——Assert.assertThrows方法。

@TestpublicvoidtestGetUser(){//模拟依赖方法LonguserId=123L;Mockito.doReturn(null).when(userDAO).get(userId);//调用被测方法ExampleExceptionexception=Assert.assertThrows("异常类型不一致",ExampleException.class,()->userService.getUser(userId));Assert.assertEquals("异常消息不一致","处理异常",exception.getMessage());}

Mockito提供了verifyNoInteractions方法,可以验证模拟对象在被测试方法中没有任何调用。

Mockito.verifyNoInteractions(idGenerator,userDAO);10.2.验证模拟对象没有更多方法调用Mockito提供了verifyNoMoreInteractions方法,在验证模拟对象所有方法调用后使用,可以验证模拟对象所有方法调用是否都得到验证。如果模拟对象存在任何未验证的方法调用,就会抛出NoInteractionsWanted异常。

Mockito.verifyNoMoreInteractions(idGenerator,userDAO);备注:Mockito的verifyZeroInteractions方法与verifyNoMoreInteractions方法功能相同,但是目前前者已经被废弃。

在编写单元测试用例时,为了减少单元测试用例数和代码量,可以把多组参数定义在同一个单元测试用例中,然后用for循环依次执行每一组参数的被测方法调用。为了避免上一次测试的方法调用影响下一次测试的方法调用验证,最好使用Mockito提供clearInvocations方法清除上一次的方法调用。

//清除所有对象调用Mockito.clearInvocations();//清除指定对象调用Mockito.clearInvocations(idGenerator,userDAO);

这里,只收集了几个经典案例,解决了特定环境下的特定问题。

在编写单元测试用例时,或多或少会遇到一些问题,大多数是由于对测试框架特性不熟悉导致,比如:

……

在编写单元测试用例时,通常采用ArgumentCaptor进行参数捕获,然后对参数对象值进行验证。如果参数对象值没有变更,这个步骤就没有任何问题。但是,如果参数对象值在后续流程中发生变更,就会导致验证参数值失败。

原始代码:

publicvoidreadData(RecordReaderrecordReader,intbatchSize,FunctiondataParser,Predicate>dataStorage){try{//依次读取数据Recordrecord;booleanisContinue=true;ListdataList=newArrayList<>(batchSize);while(Objects.nonNull(record=recordReader.read())&&isContinue){//解析添加数据Tdata=dataParser.apply(record);if(Objects.nonNull(data)){dataList.add(data);}//批量存储数据if(dataList.size()==batchSize){isContinue=dataStorage.test(dataList);dataList.clear();}}//存储剩余数据if(CollectionUtils.isNotEmpty(dataList)){dataStorage.test(dataList);dataList.clear();}}catch(IOExceptione){Stringmessage=READ_DATA_EXCEPTION;log.warn(message,e);thrownewExampleException(message,e);}}测试用例:

执行单元测试用例失败,抛出以下异常信息:

java.lang.AssertionError:数据列表不一致expected:<[[java.lang.Object@1e3469df,java.lang.Object@79499fa],[java.lang.Object@48531d5]]>butwas:<[[],[]]>问题原因:

由于参数dataList在调用dataStorage.test方法后,都被主动调用dataList.clear方法进行清空。由于ArgumentCaptor捕获的是对象引用,所以最后捕获到了同一个空列表。

解决方案:

可以在模拟依赖方法dataStorage.test时,保存传入参数的当前值进行验证。代码如下:

@TestpublicvoidtestReadData()throwsException{//模拟依赖方法...//模拟依赖方法:dataStorage.testListdataList=newArrayList<>();Predicate>dataStorage=Mockito.mock(Predicate.class);Mockito.doAnswer(invocation->dataList.addAll((List)invocation.getArgument(0))).when(dataStorage).test(Mockito.anyList());//调用测试方法odpsService.readData(recordReader,2,dataParser,dataStorage);//验证依赖方法...//验证依赖方法:dataStorage.testMockito.verify(dataStorage,Mockito.times(2)).test(Mockito.anyList());Assert.assertEquals("数据列表不一致",Arrays.asList(object0,object1,object2),dataList);}

Lombok的@Slf4j注解,广泛地应用于Java项目中。在某些代码分支里,可能只有log记录日志的操作,为了验证这个分支逻辑被正确执行,需要在单元测试用例中对log记录日志的操作进行验证。

原始方法:

@Slf4j@ServicepublicclassExampleService{publicvoidrecordLog(intcode){if(code==1){log.info("执行分支1");return;}if(code==2){log.info("执行分支2");return;}log.info("执行默认分支");}...}测试用例:

@RunWith(PowerMockRunner.class)publicclassExampleServiceTest{@MockprivateLoggerlog;@InjectMocksprivateExampleServiceexampleService;@TestpublicvoidtestRecordLog1(){exampleService.recordLog(1);Mockito.verify(log).info("执行分支1");}}问题现象:

Wantedbutnotinvoked:logger.info("执行分支1");原因分析:

经过调式跟踪,发现ExampleService中的log对象并没有被注入。通过编译发现,Lombok的@Slf4j注解在ExampleService类中生成了一个静态常量log,而@InjectMocks注解并不支持静态常量的注入。

采用作者实现的FieldHelper.setStaticFinalField方法,可以实现对静态常量的注入模拟对象。

@RunWith(PowerMockRunner.class)publicclassExampleServiceTest{@MockprivateLoggerlog;@InjectMocksprivateExampleServiceexampleService;@BeforepublicvoidbeforeTest()throwsException{FieldHelper.setStaticFinalField(ExampleService.class,"log",log);}@TestpublicvoidtestRecordLog1(){exampleService.recordLog(1);Mockito.verify(log).info("执行分支1");}}11.4.兼容Pandora等容器问题阿里巴巴的很多中间件,都是基于Pandora容器的,在编写单元测试用例时,可能会遇到一些坑。

java.lang.RuntimeException:com.alibaba.rocketmq.client.producer.SendResultwasloadedbyorg.powermock.core.classloader.javassist.JavassistMockClassLoader@5d43661b,itshouldbeloadedbyPandoraContainer.Cannotloadthisfakesdkclass.原因分析:

基于Pandora容器的中间件,需要使用Pandora容器加载。在上面测试用例中,使用了PowerMock容器加载,从而导致抛出类加载异常。

首先,把PowerMockRunner替换为PandoraBootRunner。其次,为了使@Mock、@InjectMocks等Mockito注解生效,需要调用MockitoAnnotations.initMocks(this)方法进行初始化。

@RunWith(PandoraBootRunner.class)publicclassMetaqMessageSenderTest{...@BeforepublicvoidbeforeTest(){MockitoAnnotations.initMocks(this);}...}

在编写测试用例时,特别是泛型类型转换时,很容易产生类型转换警告。常见类型转换警告如下:

Typesafety:UncheckedcastfromObjecttoListTypesafety:UncheckedinvocationforClass(Class)ofthegenericmethodforClass(Class)oftypeArgumentCaptorTypesafety:TheexpressionoftypeArgumentCaptorneedsuncheckedconversiontoconformtoArgumentCaptor>作为一个有代码洁癖的轻微强迫症程序员,是绝对不容许这些类型转换警告产生的。于是,总结了以下方法来解决这些类型转换警告。

Mockito提供@Mock注解来模拟类实例,提供@Captor注解来初始化参数捕获器。由于这些注解实例是通过测试框架进行初始化的,所以不会产生类型转换警告。

问题代码:

MapresultMap=Mockito.mock(Map.class);ArgumentCaptor>parameterMapCaptor=ArgumentCaptor.forClass(Map.class);建议代码:

@MockprivateMapresultMap;@CaptorprivateArgumentCaptor>parameterMapCaptor;12.2.利用临时类或接口我们无法获取泛型类或接口的class实例,但是很容易获取具体类的class实例。这个解决方案的思路是——先定义继承泛型类的具体子类,然后mock、spy、forClass以及any出这个具体子类的实例,然后把具体子类实例转换为父类泛型实例。

FunctiondataParser=Mockito.mock(Function.class);AbstractDynamicValuedynamicValue=Mockito.spy(AbstractDynamicValue.class);ArgumentCaptor>requestCaptor=ArgumentCaptor.forClass(ActionRequest.class);建议代码:

/**定义临时类或接口*/privateinterfaceDataParserextendsFunction{};privatestaticabstractclassAbstractTemporaryDynamicValueextendsAbstractDynamicValue{};privatestaticclassVoidActionRequestextendsActionRequest{};/**使用临时类或接口*/FunctiondataParser=Mockito.mock(DataParser.class);AbstractDynamicValuedynamicValue=Mockito.spy(AbstractTemporaryDynamicValue.class);ArgumentCaptor>requestCaptor=ArgumentCaptor.forClass(VoidActionRequest.class);12.3.利用CastUtils.cast方法SpringData包中提供一个CastUtils.cast方法,可以用于类型的强制转换。这个解决方案的思路是——利用CastUtils.cast方法屏蔽类型转换警告。

FunctiondataParser=Mockito.mock(Function.class);ArgumentCaptor>requestCaptor=ArgumentCaptor.forClass(ActionRequest.class);MapscoreMap=(Map)method.invoke(userService);建议代码:

FunctiondataParser=CastUtils.cast(Mockito.mock(Function.class));ArgumentCaptor>requestCaptor=CastUtils.cast(ArgumentCaptor.forClass(ActionRequest.class));MapscoreMap=CastUtils.cast(method.invoke(userService));这个解决方案,不需要定义注解,也不需要定义临时类或接口,能够让测试用例代码更为精简,所以作者重点推荐使用。如果不愿意引入SpringData包,也可以自己参考实现该方法,只是该方法会产生类型转换警告。

注意:CastUtils.cast方法本质是——先转换为Object类型,再强制转换对应类型,本身不会对类型进行校验。所以,CastUtils.cast方法好用,但是不要乱用,否则就是大坑(只有执行时才能发现问题)。

在Mockito中,提供形式如下的方法——泛型类型只跟返回值有关,而跟输入参数无关。这样的方法,可以根据调用方法的参数类型自动转换,而无需手动强制类型转换。如果手动强制类型转换,反而会产生类型转换警告。

TgetArgument(intindex);publicstaticTany();publicstaticsynchronizedTinvokeMethod(Objectinstance,StringmethodToExecute,Object...arguments)throwsException;问题代码:

Mockito.doAnswer(invocation->dataList.addAll((List)invocation.getArgument(0))).when(dataStorage).test(Mockito.anyList());Mockito.doThrow(e).when(workflow).beginToPrepare((ActionRequest)Mockito.any());MapscoreMap=(Map)Whitebox.invokeMethod(userService,"getScoreMap");建议代码:

Mockito.doAnswer(invocation->dataList.addAll(invocation.getArgument(0))).when(dataStorage).test(Mockito.anyList());Mockito.doThrow(e).when(workflow).beginToPrepare(Mockito.any());MapscoreMap=Whitebox.invokeMethod(userService,"getScoreMap");其实,SpringData的CastUtils.cast方法之所以这么强悍,也是采用了类型自动转化方法。

Mockito的when-thenReturn语句需要对返回类型强制校验,而doReturn-when语句不会对返回类型强制校验。利用这个特性,可以利用doReturn-when语句代替when-thenReturn语句解决类型转换警告。

ListvalueList=Mockito.mock(List.class);Mockito.when(listOperations.range(KEY,start,end)).thenReturn(valueList);建议代码:

List<>valueList=Mockito.mock(List.class);Mockito.doReturn(valueList).when(listOperations).range(KEY,start,end);12.6.利用Whitebox.invokeMethod方法代替Method.invoke方法JDK提供的Method.invoke方法返回的是Object类型,转化为具体类型时需要强制转换,会产生类型转换警告。而PowerMock提供的Whitebox.invokeMethod方法返回类型可以自动转化,不会产生类型转换警告

Methodmethod=PowerMockito.method(UserService.class,"getScoreMap");MapscoreMap=(Map)method.invokeMethod(userService);建议代码:

MapscoreMap=Whitebox.invokeMethod(userService,"getScoreMap");12.7.利用instanceof关键字在具体类型强制转换时,建议利用instanceof关键字先判断类型,否则会产生类型转换警告。

JSONArrayjsonArray=(JSONArray)object;...建议代码:

if(objectinstanceofJSONArray){JSONArrayjsonArray=(JSONArray)object;...}12.8.利用Class.cast方法在泛型类型强制转换时,会产生类型转换警告。可以采用泛型类的cast方法转换,从而避免产生类型转换警告。

publicstaticVparseValue(Stringtext,Classclazz){if(Objects.equals(clazz,String.class)){return(V)text;}returnJSON.parseObject(text,clazz);}建议代码:

publicstaticVparseValue(Stringtext,Classclazz){if(Objects.equals(clazz,String.class)){returnclazz.cast(text);}returnJSON.parseObject(text,clazz);}12.9.避免不必要的类型转换有时候,没有必要进行类型转换,就尽量避免类型转换。比如:把Object类型转换为具体类型,但又把具体类型当Object类型使用,就没有必要进行类型转换。像这种情况,可以利用连写表达式或定义基类变量,从而避免不必要的类型转化。

BooleanisSupper=(Boolean)method.invokeMethod(userService,userId);Assert.assertEquals("期望值不为真",Boolean.TRUE,isSupper);ListuserList=(Map)method.invokeMethod(userService,companyId);Assert.assertEquals("期望值不一致",expectedJson,JSON.toJSONString(userList));建议代码:

Assert.assertEquals("期望值不为真",Boolean.TRUE,method.invokeMethod(userService,userId));ObjectuserList=method.invokeMethod(userService,companyId);Assert.assertEquals("期望值不一致",expectedJson,JSON.toJSONString(userList));

登妙峰山记

山高路远车难骑,

精疲力尽人易弃。

多少妙峰登顶者,

又练心境又练力!

骑行的人,一定要沉得住气、要吃得了苦、要耐得住寂寞、要意志坚定不移、要体力够猛够持久……恰好,这也正是技术人所要具备的精神。只要技术人做到了这些,练就了好的“心境”和“体力”,才有可能登上技术的“妙峰山”。

THE END
1.python基础——如何测试代码?代码测试可通过的测试 你需要一段时间才能习惯创建测试用例的语法,但创建测试用例之后,再添加针对函数的单元测试就很简单了。 要为函数编写测试用例,可先导入模块unittest和要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。 https://blog.csdn.net/weixin_49895216/article/details/134278533
2.白盒测试方法wx634e5f8a4276e的技术博客白盒测试又称透明盒测试、逻辑驱动测试,是测试被测单元内部如何工作的一种测试方法,根据程序内部逻辑结构及有关信息来设计和选择测试用例,对程序的逻辑结构进行测试,可覆盖全部代码、分支、条件和路径等。保证程序中所有关键路径的测试,防止由于没有执行的路径在实际投入运行后执行到发生意外的情况,衡量测试完整性,程序https://blog.51cto.com/u_15834920/5767451
3.安全测试方法目前主要安全测试方法有: ①静态的代码安全测试:主要通过对源代码进行安全扫描,根据程序中数据流、控制流、语义等信息与其特有软件安全规则库进行匹对,从中找出代码中潜在的安全漏洞。静态的源代码安全测试是非常有用的方法,它可以在编码阶段找出所有可能存在安全风险的代码,这样开发人员可以在早期解决潜在的安全问题。而https://xue.baidu.com/okam/pages/strategy/index?strategyId=141649071995450&source=natural
4.『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?2、白盒测试的测试对象 白盒测试的测试对象是基于被测试程序的源代码,而不是软件的需求规格说明书。 使用白盒测试方法时,测试人员必须全面了解程序内部逻辑结构,检查程序的内部结构,从检查程序的逻辑着手,对相关的逻辑路径进行测试,最后得出测试结果。 3、白盒测试的原则 https://www.imooc.com/article/319673
5.一种基于uppaal模型的汽车软件源代码仿真测试方法1.一种基于UPPAAL模型的汽车软件源代码仿真测试方法,其特征是,实现步骤如下 (O根据需求规格说明,构建UPPAAL模型,构建好的UPPAAL模型中的全局声明部分定义的数据变量和管道变量就是测试系统中的输入变量和输出变量,利用构建好的UPPAAL模型进行仿真和功能需求验证; (2)根据UPPAAL模型的定义或者通过UPPAAL仿真器,确定测试系统https://www.xjishu.com/zhuanli/55/201210382231.html
6.单元测试常见问题及测试方法单元测试常用方法.png 驱动代码(Driver)指调用被测函数的代码:单元测试中,驱动模块通常包括调用被测函数前的数据准备、调用被测函数及验证相关结果三个步骤。 桩代码(Stub)是用来代替真实代码的临时代码。比如,某个函数A的内部实现中 调用了一个尚未实现的函数B,为了对函数A的逻辑进行测试,那么就需要模拟一个函数Bhttps://www.jianshu.com/p/2ed4a7c203e3
7.在intellijidea中快速生成测试代码腾讯云开发者社区在intellij idea中快速生成测试代码 将鼠标放到类的任意位置,摁下Ctrl+Shift+T,然后Create a new Test即可。 JUnit4为了保证每个测试方法都是单元测试,是独立的互不影响。所以每个测试方法执行前都会重新实例化测试类。 为什么Junit没有main()方法就能运行 https://cloud.tencent.com/developer/article/1884994
8.静态代码检测工具:原理作用优点及选择指南为了确保代码的质量和安全性,在应用开发阶段使用静态代码检测工具进行代码检测越来越重要。 一、静态代码检测工具的原理 静态代码检测工具直接对源代码进行分析和检查,而不需要实际运行代码,从而发现潜在的问题和缺陷。其主要原理包括以下几个方面: 1. 语法分析:静态检测工具通过解析源代码,进行语法分析,以检查代码中的https://zhuanlan.zhihu.com/p/642168605
9.TestStars星云测试精准测试ThreadingTest穿线测试选用某平台工具(简称TT)对WIFI小车机器人的Android手机控制平台进行黑盒手工的测试,达到小车控制与代码逻辑分析。 三、静态测试与代码审查 静态测试内容里面要求采取多种的测试方法,例如‘低复杂度的强制要求’一般需要通过满足一定的度量指标来实现,度量指标包括圈复杂度、嵌套深度等等,除此之外 静态测试还要求一些其它的http://www.threadingtest.com/newstest/%E7%B2%BE%E5%87%86%E6%B5%8B%E8%AF%95%E5%9C%A8%E6%99%BA%E8%83%BD%E6%9C%BA%E5%99%A8%E4%BA%BA%E4%B8%8A%E7%9A%84%E5%BA%94%E7%94%A8.html
10.tiantian010日志测试生涯@flaky(max_runs=3, min_passes=2):第一次执行失败了,将会再次重复执行它3次,如果这3次中有2次成功了,则认为这个测试通过了。 查看(714)评论(0)收藏分享管理 Robot Framework类似功能 2019-09-10 18:02:34 查看(745)评论(0)收藏分享管理 不懂代码也能测试的接口自动化平台 http://www.51testing.com/index.php?uid-154419-action-spacelist-type-blog-itemtypeid-4546
11.面经2022年软件测试面试题大全(持续更新)附答案可测试性:每项需求都能够通过设计测试用例或其他的验证方法来进行测试; 可修改性:每项需求只应在SRS中出现一次,这样更改会容易保持一致性; 可跟踪性:在每项软件需求与它的根源与设计元素,源代码,测试用例之间建立起链接,而这种可跟踪性要求每项需求都必须以一种结构化的,粒度好的方式编写并单独标明,而不是大段https://maimai.cn/article/detail?fid=1730797197&efid=rTTgV-zsthsezl4x1LC2pw
12.《软件测验基本》期末考卷及参考谜底结构测试,静态测试,动态测试 10、软件是包括___﹑___﹑___的完整集合。 程序,数据,相关文档 11、边界值分析法属于___。 黑盒测试 12、单元测试是以___说明书为指导,测试源程序代码。 详细设计 13、集成测试以___说明书指导,测试软件结构。 概要设计 14、https://www.360docs.net/doc/ada5fd4e31687e21af45b307e87101f69e31fba2.html
13.使用测试资源管理器运行单元测试测试资源管理器还可以运行第三方和开放源代码单元测试框架,它们实现了测试资源管理器外接程序接口。 你可以通过 Visual Studio Extension Manager 和 Visual Studio 库添加其中许多框架。 有关详细信息,请参阅安装第三方单元测试框架。 你可以从代码快速生成测试项目和测试方法,或者根据你的需要手动创建测试。 当使用 Inthttps://docs.microsoft.com/zh-cn/visualstudio/test/unit-test-basics
14.医疗软件产品技术审评规范(2017版)确认是指通过提供客观证据认定软件满足用户需求和预期用途,通常是指在真实或模拟使用环境进行的用户测试。可追溯性分析是指追踪需求规范、设计规范、源代码、测试、风险管理之间的关系,分析已识别关系的正确性、一致性、完整性和准确性。 A级提供系统测试、用户测试的计划和报告摘要,描述测试的条件、工具、方法、通过准则https://yjj.beijing.gov.cn/yjj/ztzl48/ylqxjgfwzn/jsscgfzl64/yycp60/11001660/
15.科学网—欠小伙伴一个火山图,这次给你们补上扫描关注微信公众号,后台回复火山图,获取源代码和测试数据 # 读入数据 df = readFlie('./DEGs_result.txt',type = 'txt') # 绘图 fg=wn_volcano(Symbol = rownames(df),logFC=df$logFC,Pvalue=df$FDR) # 展示图片 fg # 保存图片 savePlots(path = './fg.pdf',plot = fg,type = 'pdf',widthhttps://wap.sciencenet.cn/home.php?mod=space&do=blog&id=1251072
16.代码测试工具SourceMonitorv3.5.8绿色版下载代码测试工具 SourceMonitor v3.5.8绿色版主要功能: 通过源文件快速收集,单次指标。 检测C++,C,C#,VB.NET,Java和Delphi源代码,Visual Basic(VB6)或HTML。 包括方法和功能水平指标C ++,C,C#,VB.NET,Java和Delphi的。 未经修改的复杂性度量的选择。 http://www.winwin7.com/soft/xtgj-2880.html