Junit单元测试框架是Java程序开发必备的测试利器,如今最经常使用的就是Junit4了,在Junit4中全部的测试用例都使用了注解的形式,这比Junit3更加灵活与方便。以前在公司的关于单元测试的培训课程中,讲师仅仅讲述了Junit4的基本的与生命周期相关的注解的使用,主要包括@BeforeClass、@Before、@Test、@After、@AfterClass这些注解,这些在应付普通简单的单元测试已经足够,然而有不少更加复杂且也会常常遇到的测试需求依靠这些生命周期注解并不能完成!所以这篇分享将为您呈现Junit4的另外一片新大陆,且看陈述… 数组
其实,在单元测试培训课程中,讲师并无讲到Junit4的核心,例如为何Junit没有main()方法就能运行(由于咱们知道不管是什么程序都必须得有一个程序入口,而它一般是main);在例如Junit的核心组成部分是什么?如何更改Junit在运行单元测试时获取数据和执行测试的行为?更具体一点,若是我要为一个须要两个参数的方法进行测试,如何使用我所提供的参数的全部排列组合对方法进行测试?若是我须要在茫茫的测试用例中只测试与特定类相关的用例该怎么作……. 框架
在这以前,先纠正一点------Junit4能够直接运行咱们的某个方法,没有main入口函数是断然不行的。正如我以前给咱们组的一个妹子讲Spring的时候告诉她,在测试方法中,对测试方法所在的类添加Spring的 (Compent注解或者为该类的成员变量添加)Resource注解并无什么卵用,即Spring根本不会来扫描这个测试类,更不会为这个类注入属性值。为何这么说呢?由于Spring是在测试类中由被@Before标注的方法所启动的,这时候,JVM已经将此测试类实例化了,而这并非由Spring实例化的,Spring晚了一步,因此在Spring的容器中并无此类的实例。那么Junit4真的有main方法吗?没错,既然它能直接运行咱们的方法,那它必然本身为JVM提供了程序入口。其实在org.junit.runner包下,有个JUnitCore.class,其中就有一个 标准的main方法,这就是JUnit入口函数。如此看来,它其实和咱们直接在本身的main方法中跑咱们要测试的方法在本质上是同样的。 函数
接下来,我要说的就是Junit测试框架的新大陆,或者说是其核心组件,也正是讲师所没有讲到但却十分有用的东西------Runner,即Junit的运行器! 工具
Runner只是一个抽象类,表示用于运行Junit测试用例的工具,经过它能够运行测试并通知Notifier运行的结果。一般咱们能够在待测方法所在的类之上使用@RunWith注解来为这个测试类指定一个特定的Runner。不过在不少状况下,咱们并无这么作,那是由于,咱们使用了Junit的默认Runnner------BlockJunit4ClassRunner。当咱们不为测试类添加@RunWith注解的时候,其实使用的就是这个Runner,它做为默认Runner只为咱们提供了基本的基于Junit生命周期的测试注解。而有更多很是规的测试需求,则须要咱们为测试类添加@RunWith注解并指定特定的Runner来完成!下面列出一些比较有用的Runner。 单元测试
1、Suit------它能够一次生执行全面在多个类中的测试用例,例如: 测试
@RunWith(Suite.class) @SuiteClasses({Person.class, People.class}) public class TestSuitMain{ //虽然这个类是空的,但依然能够运行Junit测试,运行时,它会将Person.class和//People.class中的全部测试用命都执行一遍! }
2、Parameterized------在普通的单元测试中被@Test注解标注的测试方法只能是public void的,且不能有任何输入参数。而这时常会给咱们形成困扰,由于有时候咱们须要为测试方法输入参数,甚至是批量指定多个待测参数。这时Parameterized这个Runner就能知足咱们的要求,用法以下: ui
@RunWith(Parameterized.class) public class TestGenerateParams{ private String greeting; public TestGenerateParams(String greeting){ super(); this.greeting = greeting; } @Test public void testParams(){ System.out.println(greeting); } /** * 这里的返回的应该是一个可迭代数组,且方法必须是public static * @return */ @Parameters public static List getParams(){ return Arrays.asList(new String[][]{{"hello"},{"hi"},{"good morning"},{"how are you"}}); } }
3、Category------继承自Suit,更强大,它可让咱们对测试类中被测试的方法进行分类执行,例如Person对象具备一些属性,这些属性拥有get/set方法,同时还有一些普通方法。咱们能够将获取属性的get方法和普通方法进行分类测试。如: this
public class PersonTest{ @Category(AttributeFun.class) @Test public void testGetAge(){ int age = person.getAge(); assertEquals(3, age); } @Category(AttributeFun.class) @Test public void testGetName(){ String name = person.getName(); assertEquals("Willard", name); } @Category(BehaviorFun.class) @Test public void testTalk(){ String message = person.talkTo("Jimy"); assertNotNull(message); } @Category(BehaviorFun.class) @Test(timeout=200) public void testWalk(){ person.walk(); } } //对应的测试执行代码以下: @RunWith(Categories.class) @SuiteClasses(PersonTest.class) @IncludeCategory(AttributeFun.class) public class CategoryTest{ //注意,若是不加@IncludeCategory注解,那么就和使用Suit具备同样的效果了。 }
4、Theories------虽意为原理或推测的意思,但我在这里以更直观的方式表述它:提供一组参数的排列组合值做为待没方法的输入参数。同时注意到在使用Theories这个Runner的时候,咱们的待测方法能够拥有输入参数,而这在其它的Runner中的测试方法是不成的。下面是一个例子: spa
@RunWith(Theories.class)public class TheoriesTest{ @DataPoint public static String nameValue1 = "Tony"; @DataPoint public static String nameValue2 = "Jim"; @DataPoint public static int ageValue1 = 10; @DataPoint public static int ageValue2 = 20; @Theory public void testMethod(String name, int age){ System.out.println(String.format("%s's age is %s", name, age)); } }
上面的代码的意思是,将”Tony”、”Jim”、十、20四个参数以类型合法的排列组合传给待没方法。所以输出的结果必然也有2x2=4种: orm
Tony's age is 10
Tony's age is 20
Jim's age is 10
Jim's age is 20
不过,为了简单,咱们除了可使用@DataPoint注解来提供参数以外,还能够经过@DataPoints注解来提供参数,参照上述代码,只须要将@DataPoint注解标注的四个字段参数替换为以下的两个便可:
@DataPoints public static String[] names = {"Tony", "Jim"}; @DataPoints public static int[] ageValue1 = {10, 20};
上展现了四个Junit运行器的使用示例,有这个四个运行器的支持,基本上大部分的测试需求得能够获得解决。固然Junit提供的功能远不止这些。除此以外,咱们还可使用Junit4提供的Rule/Assume/Assert等。
其中使用Rule能够为单元测试指定测试规则,下面展现了这些可用的Rule:
Verifier: 验证测试执行结果的正确性。
ErrorCollector: 收集测试方法中出现的错误信息,测试不会中断,若是有错误发生测试结束后会标记失败。
ExpectedException: 提供灵活的异常验证功能。
Timeout: 用于测试超时的Rule。
ExternalResource: 外部资源管理。
TemporaryFolder: 在JUnit的测试执行先后,建立和删除新的临时目录。
TestWatcher: 监视测试方法生命周期的各个阶段。
TestName: 在测试方法执行过程当中提供获取测试名字的能力。
此外,Assume表示假设,但它实际是对待没方法的参数进行合法性校验的,若是校验不合格则直接抛异常,而不执行测试。这和Guava中的Predictions相似。Assume提供的校验规则以下:
assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoException
例如:(经过下述代码也能够看到,要使用参数,则应使用@Theory注解)
@Theory public void printAge(String name, int age){ Assume.assumeTrue(age > 0);//若是参数age<=0,会抛AssumptionViolatedException异常 System.out.println(String.format("%s's Name is %s.", name, age)); }
Assert是Junit提供的断言,与Assume不一样,Assert是对测试结果的校验,它提供的检验规则以下:
AssertTrue、AssertFalse:结果的true、false。
AssertThat:使用Matcher作自定义的校验。
AssertEquals、AssertNotEquals:判断两个对象是否相等。
AssertNull、AssertNotNull:判断对象是否为空。
AssertSame:判断两个对象是否为同一个,不一样于equals这里是使用“==”判断。
AssertArrayEquals:判断两个数组是否相等。