JUnit 单元测试入门

JUnit

JUnit 是一个编写可重复测试的简单框架。它是单元测试框架的XUnit体系结构的一个实例。写好单元测试,也是程序员的一项重要开发技能和对项目开发的负责。

JUnit 4 : https://junit.org/junit4/
JUnit 5: https://junit.org/junit5/

集成

Maven

除了JUnit 的基本包,还可以引入一些第三方 jar 包,如匹配器 hamcrest-library 、模拟测试 mockito-core 等来有效编写单元测试。

	<dependency>
	  <groupId>junit</groupId>
	  <artifactId>junit</artifactId>
	  <version>4.12</version>
	  <scope>test</scope>
	</dependency> 

使用方法

断言

JUnit 为所有的基本数据类型、对象和数组提供重载断言方法。第一个可选的参数为当断言失败时输出的提示信息。断言参数值顺序依次为期望值,实际值。

Assert 类中常用的方法:

public class AssertTests {

    @Test
    public void testAssertEquals() {
        Assert.assertEquals("failure - string are not equals", "hello", "hello");
        Assert.assertNotEquals("failure - string are equals", "hello", "world");
    }

    @Test
    public void testAssertArrayEquals() {
        byte[] expected = "hello".getBytes();
        byte[] actual = "hello".getBytes();

        Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
    }

    @Test
    public void testAssertBoolean() {
        Assert.assertTrue("failure - should  be true", true);
        Assert.assertFalse("failure - should  be false", false);
    }

    @Test
    public void testAssertNull() {
        Assert.assertNull("should be null ", null);
        Assert.assertNotNull("should not be null", new Object());
    }

    @Test
    public void testAssertSame() {
        Integer expected = Integer.valueOf("666");
        int actual = 666;

        // assertSame 会将所有参数转为Object 对象,比较的是对象的地址,而不是数值
        Assert.assertSame("should be same", expected, expected);
        Assert.assertNotSame("should not be same", expected, actual);
    }

    @Test
    public void testAssertThatBothContainsString() {
        Assert.assertThat("hello JUnit",
                CoreMatchers.both(CoreMatchers.containsString("hello")).and(CoreMatchers.containsString("J")));
    }

    @Test
    public void testAssertThatHasItems() {
        Assert.assertThat(Arrays.asList("one", "two", "three"), CoreMatchers.hasItem("one"));
    }

}

Suite

当你需要运行多个测试类时,可以使用 Junit 提供的 suite 来包含多个测试类,并指定多个测试类的执行顺序。执行顺序以 @Suite.SuiteClasses 中引入的测试类顺序为准。

	import org.junit.runner.RunWith;
	import org.junit.runners.Suite;
	
	@RunWith(Suite.class)
	@Suite.SuiteClasses({
	  TestFeatureLogin.class,
	  TestFeatureLogout.class,
	  TestFeatureNavigate.class,
	  TestFeatureUpdate.class
	})
	
	public class FeatureTestSuite {
	  // the class remains empty,
	  // used only as a holder for the above annotations
	}	

指定测试方法执行顺序

JUnit设计之初,没有指定测试方法调用的执行顺序。到目前为止,这些方法只是按照反射API返回的顺序被调用。然而,使用JVM命令是不明智的,因为Java平台没有指定任何特定的顺序,并且事实上JDK 7返回或多或少的随机顺序。当然,编写良好的测试代码不会有任何顺序,但有些会有顺序,可预测的失败比某些平台上的随机失败要好。

从4.11版开始,JUnit 可以使用 @FixMethodOrder 指定确定性但不可预测的顺序:

  • @FixMethodOrder(MethodSorters.DEFAULT):默认家执行顺序
  • @FixMethodOrder(MethodSorters.JVM):按照JVM返回的顺序执行测试方法。此顺序可能因运行而异。
  • @FixMethodOrder(MethodSorters.NAME_ASCENDING):按方法名的字典顺序对测试方法进行排序。

使用方法:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestMethodOrder {

    @Test
    public void testA() {
        System.out.println("first");
    }
    @Test
    public void testB() {
        System.out.println("second");
    } 
}

异常测试

预期异常的处理

当我们需要验证代码是否能否按照预期抛出特定的异常时,可以在@Test() 中指定预期的异常类型。当无此异常发生时,则测试不通过。

@Test(expected = IndexOutOfBoundsException.class) 
public void empty() { 
     new ArrayList<Object>().get(0); 
}

更深一步的异常测试

上述的方法只适合用于一些简单的异常测试,但他有一些限制,比如不能指定异常中测试消息的值,也不能在引发异常后测试域对象的状态。我们有两种方式来捕捉异常,并指定异常消息的值:

  1. 使用 try…catch… 捕捉异常
  2. 使用 ExpectedException 指定规则
	// 1. 使用 try...catch... 捕捉异常
    @Test
    public void testExceptionMessage() {
        try {
            new ArrayList<Object>().get(0);
        } catch (IndexOutOfBoundsException anIndexOutOfBoundsException) {
            Assert.assertThat(anIndexOutOfBoundsException.getMessage(), CoreMatchers.is("Index: 0, Size: 0"));
        }
    }

	// 2. 使用 ExpectedException 指定规则
	public ExpectedException thrown = ExpectedException.none();	
	@Test
	public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
	    List<Object> list = new ArrayList<Object>();
	 
	    thrown.expect(IndexOutOfBoundsException.class);
	    thrown.expectMessage("Index: 0, Size: 0");
	    list.get(0); // execution will never get past this line
	}

匹配器和断言

待研究:https://github.com/junit-team/junit4/wiki/Matchers-and-assertthat

忽略测试

某些情况下,由于某个测试方法失败而影响整个测试进程时,想暂时忽略跳过此方法时,可以使用@Ignore() 方法。

	@Ignore("忽略时的提示消息")
    @Test
    public void testAssertEquals() {
        Assert.assertEquals("failure - string are not equals", "hello", "hello2");
    }

超时设置

在测试有些比较耗时或者需要在规定时间内执行完毕的方法时,可以指定方法执行的超时时间。当超过时间限制时,则会引发超时异常使得测试失败,有助于我们明确哪些需要更进一步优化运行速度的代码。有以下2中方式设置超时时间:

  1. @Test(timeout= …),作用范围仅为该测试方法
  2. 使用@Rule指定Timeout 的规则,作用范围为整个测试类,注意也会覆盖 @Test 中设置的 timeout
	// 1、@Test(timeout= ...)
	@Test(timeout = 5000)
    public void testTimeOut() {
    }
    
    // 2、使用@Rule指定Timeout 的规则
    @Rule
    public Timeout timeout = Timeout.seconds(5);
    private final CountDownLatch latch = new CountDownLatch(1);

    @Test
    public void testSleepForTooLong() throws Exception {
        TimeUnit.SECONDS.sleep(100); // sleep for 100 seconds
    }

    @Test
    public void testBlockForever() throws Exception {
        latch.await(); // will block
    }

参数化测试

待研究:https://github.com/junit-team/junit4/wiki/Parameterized-tests

假设

假设即当如果不满足某个条件时,则不要运行此测试,它的作用与 Assert 类似,只不过不会被当成一个失败的测试。

	import static org.junit.Assume.*;
	
	 @Test
    public void testAssumeTrue() {
        assumeTrue("assumeTrue is false", true);
        assumeFalse("assumeFalse is true", false);
    }

    @Test
    public void testAssumeThat() {
        assumeThat("assumeThat is false", "hello world", CoreMatchers.containsString("hello"));
    }

    @Test
    public void testN() {
        assumeNotNull("assumeNotNull is false", new Object());
    }

规则

规则允许非常灵活地添加或重新定义测试类中每个测试方法的行为。测试人员可以重用或扩展 JUnit 提供的规则之一,或者编写自己的规则。

JUnit 提供的规则比较多,详细使用方法可以件官方文档例子。

https://github.com/junit-team/junit4/wiki/Rules

理论

待研究:https://github.com/junit-team/junit4/wiki/Theories

Test fixtures

测试夹具的目的是确保有一个众所周知且固定的环境,在该环境中运行测试,以便结果可重复。例如:

  • 准备输入数据和设置/创建假对象或模拟对象
  • 使用特定的已知数据集加载数据库
  • 复制一组特定的已知文件创建一个测试夹具将创建一组初始化为特定状态的对象。

JUnit 提供了一些注解以在测试类或测试方法执行之前执行某些特定的方法。

  • @BeforeClass 在测试类中的所有测试方法执行之前执行(该方法必须是静态)
  • @AfterClass 在测试类中的所有测试方法执行之后执行(该方法必须是静态)
  • @Before 在每一个测试方法之前运行
  • @After.每一个测试方法之后运行

例子:

    @BeforeClass
    public static void testBeforeClass() {
        System.out.println("@BeforeClass");
    }

    @AfterClass
    public static void testAfterClass() {
        System.out.println("@AfterClass");
    }

    @Before
    public void testBefore() {
        System.out.println("@Before");
    }

    @After
    public void testAftee() {
        System.out.println("@After");
    }

    @Test
    public void test1() {
        System.out.println("JUnit test1");
    }

    @Test
    public void test2() {
        System.out.println("JUnit test2");
    }

输出结果:

@BeforeClass
@Before
JUnit test1
@After
@Before
JUnit test2
@After
@AfterClass

类别

从一组给定的测试类中,Categories 运行程序仅运行用于 @Includecategory 注释给定的类或该类别的子类型进行注释的类和方法。@Includecategory(Superclass.class),将运行使用 @Category(Dubclass.class)注解的测试类或方法。也可以使用@excludecategory 注解排除测试类。
例子:

	public interface FastTests { /* category marker */ }
	public interface SlowTests { /* category marker */ }
	
	public class A {
	  @Test
	  public void a() {
	    fail();
	  }
	
	  @Category(SlowTests.class)
	  @Test
	  public void b() {
	  }
	}
	
	@Category({SlowTests.class, FastTests.class})
	public class B {
	  @Test
	  public void c() {
	
	  }
	}
	
	@RunWith(Categories.class)
	@IncludeCategory(SlowTests.class)
	@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
	public class SlowTestSuite {
	  // 只会运行 A.b、 B.c,但不会执行 A.a
	}
	
	@RunWith(Categories.class)
	@IncludeCategory(SlowTests.class)
	@ExcludeCategory(FastTests.class)
	@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
	public class SlowTestSuite {
	  // 只会运行 A.b,但不会执行  A.a 、B.c
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值