01、前世你好,我是JUnit,一个开源的Java单元测试框架。在您了解我之前,让我们了解什么是单元测试。单元测试就是为最小的功能单元编写测试代码。在Java中,最小的功能单元是方法,因此Java程序员的单元测试实际上是在测试Java方法。为什么需要单元测试?因为单元测试可以保证你写的代码符合软件需求,遵循开发规范。单元测试是所有测试中最低级的测试类型。它是第一个环节,也是最重要的环节。它是唯一可以实现100%代码覆盖率的测试。它是整个软件测试过程的基础和前提。可以说单元测试的性价比是最好的。微软之前有这样一个统计:一个bug在单元测试阶段被发现的平均时间为3.25小时,如果在系统测试中漏掉,则需要11.5小时。说了这么多,大家应该已经很清楚单元测试的重要性了。你在第一次写测试代码的时候经常这样做吗?像下面这样。publicclassFactorial{publicstaticlongfact(longn){longr=1;for(longi=1;i<=n;i++){r=r*i;}returnr;}publicstaticvoidmain(String[]args){if(fact(3)==6){System.out.println("Pass");}else{System.out.println("Failed");}}}为了测试fact()方法的正确性,你在main()中写method一段测试代码。如果你这样做了,我只能说你太天真了!使用main()方法进行测试有很多缺点,例如:1)测试代码没有和源代码分离。2)不够灵活,无法编写一组通用的测试代码。3)预期结果和实际结果无法自动打印出来,无法比较。但是如果你学会了使用me-JUnit,你就不会再有这种烦恼了。我可以非常简单的组织测试代码,随时运行,并给出准确的测试报告,让你在最短的时间内找出自己写的代码哪里出了问题。02.入门指南好了,既然知道我这么厉害了,还等什么,赶快开始吧!我的最新版本是JUnit5,已经集成在IntellijIDEA中,所以你可以直接在IDEA测试用例中编写和运行我的测试用例。第一步,在当前代码编辑器窗口直接按下Command+N键(Mac版),在弹出的菜单中选择“Test...”。勾选方法fact()编写测试用例,点击“确定”。此时IDEA会在当前类所在的包下自动生成一个类名为Test(常规)的测试类。如下所示。如果你是第一次使用我,IDEA会提示你导入我的依赖包。建议您选择最新的JUnit5.4。导入完成后,可以打开pom.xml文件,确认我的依赖多了。org.junit.jupiterjunit-jupiterRELEASEcompile第二步,在向测试方法添加一组断言,如下所示。@Testvoidfact(){assertEquals(1,Factorial.fact(1));assertEquals(2,Factorial.fact(2));assertEquals(6,Factorial.fact(3));assertEquals(100,Factorial.fact(5))));}@Test注释是我需要的,我会将带有@Test的方法识别为测试方法。在测试方法内部,您可以使用assertEquals()将预期值与实际值进行比较。第三步,可以在邮件菜单中选择“RunFactorialTest”来运行测试用例,结果如下。测试失败是因为第20行的预期结果与实际结果不符,预期的100实际上是120。此时,要么修复实现代码,要么修复测试代码,直到测试通过。是不是很难?单元测试可以确保单个方法按预期运行。如果修改某个方法的代码,只需要保证相应的单元测试通过,就可以认为修改没有问题。03.向前看和向后看在一个测试用例中,可能会测试多个方法。测试前需要准备一些条件,比如创建对象;测试完成后,需要销毁这些对象来释放资源。在多种测试方法中重复此样板代码会非常冗长。这个时候,我该怎么办?我为您提供了setUp()和tearDown()。作为一个文化人,我称之为“向前看,向后看”。让我们看一下要测试的代码。publicclassCalculator{publicintsub(inta,intb){returna-b;}publicintadd(inta,intb){returna+b;}}记得在创建新测试用例时检查setUp和tearDown。生成的代码如下。classCalculatorTest{Calculatorcalculator;@BeforeEachvoidsetUp(){calculator=newCalculator();}@AfterEachvoidtearDown(){calculator=null;}@Testvoidsub(){assertEquals(0,calculator.sub(1,1));}@Testvoidadd(){assertEquals(2,calculator.add(1,1));}}@BeforeEach的setUp()方法会在运行每个@Test方法之前运行;@AfterEach的tearDown()方法将运行之后运行的每个@Test方法。与之对应的是@BeforeAll和@AfterAll。与@BeforeEach和@AfterEach不同,All通常用于初始化和销毁??静态变量。publicclassDatabaseTest{staticDatabasedb;@BeforeAllpublicstaticvoidinit(){db=createDb(...);}@AfterAllpublicstaticvoiddrop(...);}@AfterAllpublicstaticvoiddrop(){...}}03.异常测试对于Java程序来说,异常处理也很重要。测试可能抛出的异常本身就是测试的一个重要部分。还拿前面的Factorial类来说明。在fact()方法的开始,检查参数n,如果它小于0,则抛出IllegalArgumentException。publicclassFactorial{publicstaticlongfact(longn){if(n<0){thrownewIllegalArgumentException("参数不能小于0");}longr=1;for(longi=1;i<=n;i++){r=r*i;}returnr;}}添加一个测试方法factIllegalArgument()到FactorialTest。@TestvoidfactIllegalArgument(){assertThrows(IllegalArgumentException.class,newExecutable(){@Overridepublicvoidexecute()throwsThrowable{Factorial.fact(-2);}});}我给你提供了一个assertThrows()方法,第一个参数是异常类型。第二个参数Exec??utable可以封装产生异常的代码。如果觉得匿名内部类写起来比较复杂,可以使用Lambda表达式。@TestvoidfactIllegalArgumentLambda(){assertThrows(IllegalArgumentException.class,()->{Factorial.fact(-2);});}04.忽略测试有时,由于某些原因,某些方法有错误,需要一段时间才能修复,在修复之前,该方法对应的测试用例一直以失败告终。为了避免这种情况,我为您提供了@Disabled注解。classDisabledTestsDemo{@Disabled("此测试用例将不再执行,直到编号为43的bug被修复")@TestvoidtestWillBeSkipped(){}@TestvoidtestWillBeExecuted(){}}@Disabled注释也可以解释,但我建议你还是提供简要解释为什么应忽略此测试方法。在上面的例子中,如果团队的其他人阅读了说明,他们就会明白当bug#43被修复时,测试方法将被重新启用。哪怕是提醒自己,也是很有必要的,因为时间长了,你可能会忘记当初为什么忽略了这个测试方法。05.条件测试有时候,你可能需要在某些条件下运行测试方法,而在某些条件下不运行测试方法。针对这个使用场景,我给大家提供一个条件测试。1)不同的操作系统可能需要不同的测试用例。比如Linux和Windows的路径名是不一样的。通过@EnabledOnOs注解可以针对不同的操作系统启用不同的测试用例。@Test@EnabledOnOs(MAC)voidonLinuxOrMac(){//...}@TestOnMacvoidtestOnMac(){//...}@Test@EnabledOnOs({LINUX,MAC})voidonLinuxOrMac(){//...}@Test@DisabledOnOs(WINDOWS)voidnotOnWindows(){//...}2)不同的Java运行环境可能需要不同的测试用例。@EnabledOnJre和@EnabledForJreRange注解可以满足这个要求。@Test@EnabledOnJre(JAVA_8)voidonlyOnJava8(){//...}@Test@EnabledOnJre({JAVA_9,JAVA_10})voidonJava9Or10(){//...}@Test@EnabledForJreRange(min=JAVA_9,max=JAVA_11)voidfromJava9to11(){//...}06.在最后,说出我的心声三句话。在写单元测试的时候,最好是这样:1)单元测试本身的代码一定要非常清晰明了,一眼就能看懂,绝对不能为了测试代码而写测试代码。2)每个单元测试应该相互独立,不依赖于运行时的顺序。3)测试时要特别注意边界条件,如0、null、空串""等。希望我能尽快为您找出代码中的错误。毕竟,越早找到它们,造成的伤害就越小。再见!本文转载自微信公众号“沉默王二”,可通过以下二维码关注。转载本文请联系沉默王二公众号。