0


关于JAVA后端的单元测试

1. 单元测试核心原则

  • 自动化:单元测试应该是全自动执行的,并且非交互式的。利用断言Assert进行结果验证。
  • 独立性:保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。 单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
  • 可重复:单元测试是可以重复执行的,不能受到外界环境的影响。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
  • 全面性:除了正确的输入得到预期的结果,还需要强制错误信息输入得到预期的结果,为了系统的鲁棒性,应加入边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
  • 细粒度:保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。

2. 测试框架

JUnit和TestNG是最受欢迎的两个单元测试框架

JUnit

默认情况下,JUnit的运行顺序无规律

JUnit3:

//JUnit3的测试类必须继承TestCasepublicclassJUnit3TestextendsTestCase{//JUnit3的测试方法必须test开头publicvoidtestName(){}}

JUnit4;

//JUnit使用注解来运行,无需继承TestCase,也不需要测试驱动以test开头publicclassJUnit4Test{@TestpublicvoidtestName(){}}

Test设置

  • JUnit:**@BeforeClass@AfterClass的方法需要加static**常用:- @BeforeClass:在所有测试方法前执行一次,一般在其中写上整体初始化的代码。- @AfterClass:在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码。- @Before:在每个测试方法前执行,有多少个测试方法就会运行多少次。- @After:在每个测试方法后执行,有多少个测试方法就会运行多少次。- @Test:具体的测试方法- @Ignore:执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类。- @Rule:定义一些规则,如超时时间,异常捕获不常用:- **@Test(timeout = 1000)**:指明要被测试的方法(测试方法执行超过1000毫秒后算超时,测试将失败)。- **@Test(expected = Exception.class)**:测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败。- @FixMethodOrder:可以指定按方法名的升序或是降序排序- @RunWith:指定用什么方式策略去运行这些测试集、类、方法。- **@ActiveProfiles(“xxx”)**:在测试的时候启用某些Profile的Bean。
  • TestNG:- 方法级别:@BeforeMethod:测试方法运行前执行的方法注解​ @AfterMethod:测试方法运行后执行的方法注解- 类级别:@BeforeClass​ @AfterClass- 套件:@BeforeSuite、@AfterSuite- 组级别:@BeforeGroup、@AfterGroup- @Test- alwaysRun : 如果=true,表示即使该测试方法所依赖的前置测试有失败的情况,也要执行- dataProvider : 选定传入参数的构造器。(@DataProvider注解将在后面章节介绍)- dataProviderClass : 确定参数构造器的Class类。(参数构造器首先会在当前测试类里面查找,如果参数构造器不在当前测试类定义,那么必须使用该属性来执行它所在的Class类)- dependsOnGroups : 确定依赖的前置测试组别。- dependsOnMethods : 确定依赖的前置测试方法。- description : 测试方法描述信息。(建议为每个测试方法添加有意义的描述信息,这将会在最后的报告中展示出来)- enabled : 默认为true,如果指定为false,表示不执行该测试方法。- expectedExceptions : 指定期待测试方法抛出的异常,多个异常以逗号(,)隔开。- groups : 指定该测试方法所属的组,可以指定多个组,以逗号隔开。组测试的用法将在后面文章单独介绍。- invocationCount : 指定测试方法需要被调用的次数。- invocationTimeOut: 每一次调用的超时时间,如果invocationCount没有指定,该参数会被忽略。应用场景可以为测试获取数据库连接,超时就认定为失败。单位是毫秒。- priority : 指定测试方法的优先级,数值越低,优先级越高,将会优先与其他数值高的测试方法被调用。(注意是针对一个测试类的优先级)- timeout : 指定整个测试方法的超时时间。单位是毫秒- 多线程:threadPoolSize = 3,invocationCount = 6,timeOut = 1000

停用测试

  • JUnit:@Ignore
  • TestNG:@Test后面加入enable=false @Test(enable=false)

套件/分组测试

  • JUnit利用@RunWith、@SelectPackages、@SelectClasses注解来组合测试用例@RunWith(Suite.class)@Suite.SuiteClasses({JUnit3Test.class,JUnit4Test.class})publicclassJUnitTest{}
  • TestNG@Test(groups = {"group2","group1"})``````<test name="test-2"> <groups> <run> <include name="group2"/> <include name="group1"/> </run> </groups> <packages> <package name="com.cxl.testng"/> </packages> </test>

参数化测试

  • JUnit@RunWith(value =Parameterized.class)publicclassJUnitTest{privateint number;publicJUnitTest06(int number){this.number = number;}@Parameterized.ParameterspublicstaticCollection<Object[]>data(){Object[][] data =newObject[][]{{1},{2},{3},{4}};returnArrays.asList(data);}@TestpublicvoidpushTest(){System.out.println("Parameterized Number is : "+ number);}}
  • TestNG@Parameters({"param1"})@TestpublicvoidparamterTest(String param1){System.out.println("\n---------------"+param1);}``````<parametername="param1"value="http://127.0.0.1:4723/wd/hub"/><testname="testDemo1"><classes><classname="com.cxl.testng.TestNG01"></class></classes></test>

@DataProvider 注解做参数化测试

  • TestNG:@Test(dataProvider ="userData")publicvoidtest(Class clazz,String[] str){System.out.println(clazz +"-------------"+ str[0]);System.out.println(clazz +"-------------"+ str[1]);}@DataProvider(name ="userData")publicObject[][]data(){Object[][] objects =newObject[][]{{Vector.class,newString[]{"java.util.Arrays","java.util.List"}},{String.class,newString[]{"this is my str","this is my pp"}},{Integer.class,newString[]{"123","345"}},{Float.class,newString[]{"12.45f","33.11f"}}};return objects;}

依赖测试

JUnit4 框架主要聚焦于测试的隔离,暂时还不支持这个特性。

TestNG使用dependOnMethods、dependsOnGroups 来实现了依赖测试的功能。

@Testpublicvoidmethod1(){System.out.println("This is method 1");}@Test(dependsOnMethods={"method1"})publicvoidmethod2(){System.out.println("This is method 2");}

如果method1()成功执行,那么method2()也将被执行,否则method2()将会被忽略。

@Test(groups ={"init.1"})publicvoidtest1(){}@Test(groups ={"init.2"})publicvoidtest2(){}@Test(dependsOnGroups ={"init.*"})publicvoidtest3(){}

并发测试

Junit单元测试不支持多线程测试,TestNg使用threadPoolSize用来指明线程池的大小。

publicclassTestNgThreadPoolSize{@Test(threadPoolSize =3,invocationCount =5)publicvoidthreadPool(){System.out.println("Thread ----------"+Thread.currentThread().getName());}}

总结:

建议使用TestNG作为Java项目的核心单元测试框架,因为TestNG在参数化测试,依赖测试和套件测试(分组概念)方面更加突出。 TestNG用于高级测试和复杂集成测试。 它的灵活性对于大型测试套件尤其有用。 此外,TestNG还涵盖了整个核心的JUnit4功能。

3. Mock

在单元测试中模拟一个被调用方法,进而隔离测试环境,其实现原理是利用接口的多态性。

通常意义的mock指的就是mock server,模拟服务端返回的接口数据,用于前端开发

  • 为什么要用mock? 1. 前后端开发速度不一致,前端开发速度快于后端,需要一个假的接口用于模拟后端返回。2. 项目需要用到第三方接口,如果第三方接口未开发好,或者第三方接口没有测试环境,为了保证进度,所以需要模拟接口用于调试。
@MockprivateIExecuteSqlManage iExecuteSqlManage;@BeforeMethodpublicvoidsetUp()throwsException{MockitoAnnotations.initMocks(this);ReflectionTestUtils.setField(AopTargetUtils.getTarget(espJfxxInfoFileToDb),"iExecuteSqlManage", iExecuteSqlManage);}@AfterMethodpublicvoidtearDown()throwsException{ReflectionTestUtils.setField(AopTargetUtils.getTarget(espJfxxInfoFileToDb),"iExecuteSqlManage",null);}

4.PowerMock

1、类上加注解

@PowerMockIgnore("javax.management.*,org.apache.log4j.*")@PrepareForTest({AcctFileProcessUtil.class,FileTool.class,GlobalConf.class})//需要Mock的静态类@PowerMockIgnore({"javax.xml.*","org.xml.sax.*","org.w3c.dom.*","org.springframework.context.*","org.apache.log4j.*"})

2、初始化

@ObjectFactorypublicITestObjectFactorygetObjectFactory(){returnnewPowerMockObjectFactory();}

3、自定义返回值

PowerMockito.mockStatic(GlobalConf.class);PowerMockito.when(GlobalConf.getString(anyString(),any())).thenReturn("path_in");

4、检验次数

PowerMockito.verifyStatic(times(1));

5.断言

验证结果

@TestvoidgetPeopleById(){People people = peopleService.getPeopleById(1);//System.out.println(people);Assert.assertNotNull(people);Assert.assertEquals(people.getAge(),11);Assert.assertEquals(people.getName(),"cxl");}
try{
     agetAgentSeqImpl.getAgetAgentSeq(req);}catch(Exception e){Assert.assertEquals(e.getMessage(),"未取到代理商充值流水");}
agetAgentSeqRes = agetAgentSeqImpl.getAgetAgentSeq(req);Assert.assertEquals(agetAgentSeqRes.getXtableAgentSeq(),"null~0~-1~null~-1~1~0~-1~null~-1~0~20170301000000~null~0~0~0~-1~0~1~;");

验证方法是否被调用

@TestpublicvoidtestVerify(){// 创建并配置 mock 对象MyClass test =Mockito.mock(MyClass.class);when(test.getUniqueId()).thenReturn(43);// 调用mock对象里面的方法并传入参数为12
    test.testing(12);
    test.getUniqueId();
    test.getUniqueId();// 查看在传入参数为12的时候方法是否被调用verify(test).testing(Matchers.eq(12));// 方法是否被调用两次verify(test,times(2)).getUniqueId();// 其他用来验证函数是否被调用的方法verify(mock,never()).someMethod("never called");verify(mock,atLeastOnce()).someMethod("called at least once");verify(mock,atLeast(2)).someMethod("called at least twice");verify(mock,times(5)).someMethod("called five times");verify(mock,atMost(3)).someMethod("called at most 3 times");}

断言错误

@Test(expectedExceptions =com.newland.common.NLException.class)publicvoidqueryUserUnionPayUserIdMinusOne()throwsException{try{
            qryUserUnionPayImpl.qryUserUnionPay(getReq("-1"));}catch(NLException e){//断言结果是否与预期相同Assert.assertEquals(e.getResCode(),CloudErrorCode.ERR_PAKERR);Assert.assertEquals(e.getMessage(),"帐户userid节点没找到或值不对");throw e;}}

本文转载自: https://blog.csdn.net/qq_44323281/article/details/123740082
版权归原作者 Atopos_Void 所有, 如有侵权,请联系我们删除。

“关于JAVA后端的单元测试”的评论:

还没有评论