浅谈junit4单元测试高级用法

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。

一、Suit------它可以一次生执行全面在多个类中的测试用例,例如:

@RunWith(Suite.class)
@SuiteClasses({Person.class, People.class})
public class TestSuitMain{
 //虽然这个类是空的,但依然可以运行Junit测试,运行时,它会将Person.class和//People.class中的所有测试用命都执行一遍!
}

二、Parameterized------在普通的单元测试中被@Test注解标注的测试方法只能是public void的,且不能有任何输入参数。而这时常会给我们造成困扰,因为有时候我们需要为测试方法输入参数,甚至是批量指定多个待测参数。这时Parameterized这个Runner就能满足我们的要求,用法如下:

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

三、Category------继承自Suit,更强大,它可以让我们对测试类中被测试的方法进行分类执行,例如Person对象具有一些属性,这些属性拥有get/set方法,同时还有一些普通方法。我们可以将获取属性的get方法和普通方法进行分类测试。如:

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具有一样的效果了。
}

四、Theories------虽意为原理或推测的意思,但我在这里以更直观的方式表述它:提供一组参数的排列组合值作为待没方法的输入参数。同时注意到在使用Theories这个Runner的时候,我们的待测方法可以拥有输入参数,而这在其它的Runner中的测试方法是不成的。下面是一个例子:

@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”、10、20四个参数以类型合法的排列组合传给待没方法。因此输出的结果必然也有2x2=4种:

  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:判断两个数组是否相等。

总结

以上就是本文关于junit4单元测试高级用法的全部内容,希望对大家有所帮助。感兴趣的朋友可以参阅:Java 非阻塞I/O使用方法  Java中map遍历方式的选择问题详解  Java实现四则混合运算代码示例等。希望大家对本站多多支持!

(0)

相关推荐

  • 详解Java单元测试之JUnit篇

    单元测试是编写测试代码,应该准确.快速地保证程序基本模块的正确性. JUnit是Java单元测试框架,已经在Eclipse中默认安装. JUnit4 JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @BeforeClass 全局只会执行一次,而且是第一个运行 @Before 在测试方法运行之前运行 @Test 测试方法 @After 在测试方法运行之后允许 @AfterClass 全局只会执行一次,而且是最后一个运行 @Ignore 忽略此方法 下面基于Eclipse介绍JUn

  • java编程之单元测试(Junit)实例分析(附实例源码)

    本文实例讲述了java编程之单元测试.分享给大家供大家参考,具体如下: 完整实例代码代码点击此处本站下载. 在有些时候,我们需要对我们自己编写的代码进行单元测试(好处是,减少后期维护的精力和费用),这是一些最基本的模块测试.当然,在进行单元测试的同时也必然得清楚我们测试的代码的内部逻辑实现,这样在测试的时候才能清楚地将我们希望代码逻辑实现得到的结果和测试实际得到的结果进行验证对比. 废话少说,上代码: 首先创建一个java工程,在工程中创建一个被单元测试的Student数据类,如下: packa

  • 详解Java单元测试Junit框架实例

    问题: 1.目前测试存在的问题 2.Junit注意的细节 3.Junit使用规范 4.断言 5.案例 junit(单元测试框架) 1.目前存在的问题 1.目前的测试方法如果需要测试,都需要在main方法上调用 2.目前的结果都需要我们人工比对 2.Junit 注意的细节 1.如果使用junit测试一个方法的时候,在junit窗口上显示绿色那么表示测试正确,如果显示了红色,则代表该方法测试出现了异常不通过 2.如果点击方法名.类名.包名.工程名运行junit分别测试的是对于的方法,类.包中的所有类

  • Java中的异常测试框架JUnit使用上手指南

    JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework).Junit测试是程序员测试,即白盒测试.该项目主页:http://www.junit.org/ 使用JUnit时,主要都是通过继承TestCase类别来撰写测试用例,使用testXXX()名称来撰写单元测试. 用JUnit写测试真正所需要的就三件事: 1.  一个import语句引入所有junit.framework.*下的类. 2.  一个exte

  • 浅谈junit4单元测试高级用法

    Junit单元测试框架是Java程序开发必备的测试利器,现在最常用的就是Junit4了,在Junit4中所有的测试用例都使用了注解的形式,这比Junit3更加灵活与方便.之前在公司的关于单元测试的培训课程中,讲师仅仅讲述了Junit4的基本的与生命周期相关的注解的使用,主要包括@BeforeClass.@Before.@Test.@After.@AfterClass这些注解,这些在应付普通简单的单元测试已经足够,然而有很多更加复杂且也会经常遇到的测试需求依靠这些生命周期注解并不能完成!因此这篇分

  • 浅谈maven单元测试设置代理

    背景 环境需要设置代理才能够访问外部网络,如果只是运行java程序来访问网络,我们可以通过java -jar test.jar -DproxyHost=proxy_ip -DproxyPort=proxy_port,但如果是java的maven项目中,单元测试需要访问网络,只执行mvn test则会导致单元测试的代码无法访问网络. 解决 Maven单元测试,使用的是Surefire Maven插件.当Surefire插件fork JVM时,并不会继承所有的系统属性.因此我们可以通过命令行来如下设

  • 浅谈Android单元测试的作用以及简单示例

    前提概要 受人嫌弃的单元测试 对于单元测试这个知识点,其实很多开发者是不太接触的,包括笔者,在实习之前也并未实用过单元测试,或者说并没感受到单元测试的好处. 对于bug的调试,笔者之前更倾向于使用log和断点调试,可以说会了这两个,大部分的逻辑bug都能自己解决了.这两个与看似臃肿的单元测试代码相比更受大家的喜爱. 但是,使用log和断点调试的前提是开发人员较少,甚至是单人开发的情况.如果我自己开发,我完全可以每次都使用集成测试,我知道每一个功能会涉及哪些模块的代码,然后根据逻辑设置log或者断

  • 浅谈Go Channel 高级实践

    channel 是 golang 里相当有趣的一个功能,在我使用 golang 编码的经验里,大部分事件都会是在享受 channel 和 goroutine 配合的乐趣.所以本文主要介绍 channel 的一些有趣的用法. 这里有 Oling Cat 翻译的Go编程语言规范里关于 channel(信道)的描述: 信道提供了一种机制,它在两个并发执行的函数之间进行同步,并通过传递(与该信道元素类型相符的)值来进行通信. 这个个描述又乏味.又枯燥.在我第一次阅读的时候,完全不明白这到底是个什么玩意.

  • 浅谈Go Slice 高级实践

    Go 语言切片是对数组的抽象. Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大. 定义切片 你可以声明一个未指定大小的数组来定义切片: var identifier []type 切片不需要说明长度. 或使用make()函数来创建切片: var slice1 []type = make([]type, len) 也可以简写为 slice1

  • 浅谈Vuejs Prop基本用法

    这两天学习了Vuejs Prop感觉这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记. 一.使用Prop传递数据 组件实例的作用域是孤立的.这意味着不能并且不应该在子组件的模板内直接引用父组件的数据.可以使用props把数据传给子组件. prop是父组件用来传递数据的一个自定义属性.子组件需要显示的地用props选项声明"prop" Vue.component('child',{ props:['message'], template:'<span>{{ mess

  • 浅谈php命令行用法

    Php是一个非常流行的web服务端脚本语言.其实,php不仅仅可以在web服务器中充当重要角色.在命令行一样可以执行. 本文中,笔者为各位介绍下php在命令行中的使用方法. 1.  查看php的版本.配置 在命令行中输入php –v 即可查看当前php的版本. 其他的选项有: –m.-i.笔者在这里就不给出列子了. -m 会显示当前php加载的有效模块. -i 则输出无html格式的phpinfo. 使用 –ini 选项可以输出当前php加载ini配置文件的数量.路径信息. 2.  在命令行中运

  • Android自定义view Path 的高级用法之搜索按钮动画

    关于Path之前写的也很多了,例如path绘制线,path绘制一阶,二阶和三阶贝塞尔路径,这些都是path的基本用法.今天我要带大家看的是Path 的高级用法,先上图,再吹. 效果大致是这样的.看着是不是挺好.话不多说,切入正题: 既然今天要谈Path的高级用法,那就先来讲一讲(Path -- 中文 )就是"路径"既然是路径,从我们面向对象的想法的话,我们就容易想到 路径 的长度,路径的某一点等.想到这里我们就引出今天 的主要 类--------PathMeasure,字面意思很容易理

  • 浅谈Flutter 中渐变的高级用法(3种)

    Flutter 中渐变有三种: LinearGradient:线性渐变 RadialGradient:放射状渐变 SweepGradient:扇形渐变 看下原图,下面的渐变都是在此图基础上完成. LinearGradient 给一张图片添加从上到下的线性渐变: ShaderMask( shaderCallback: (Rect bounds) { return LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCen

  • 浅谈MyBatis循环Map(高级用法)

    今天遇到一个比较特殊的业务,需要对传入的Map数据在映射文件中进行遍历,在之前的学习中,我们也知道MyBatis有默认对集合的操作list和array,但是没有默认的map,所有不能直接写collection="map",如果这么处理,它会当成是根据map.get("map")获取传递value只,==大部分情况下是一个map中是不会有"map"这个key的,于是就是报错==.如果你想用map标识来获取参数map,就需要保证传入的Map参数有@P

随机推荐