赞
踩
JUnit是一个Java测试框架,使编写可靠和高效的测试变得容易。它可以用于大多数语言制作的应用程序,但特别适合于测试Java应用程序。JUnit也可以用来创建自动测试。
JUnit框架是最流行的Java测试框架之一。它提供了一些功能,使编写测试变得容易,包括支持多个测试用例、断言和报告。JUnit也是多功能的,允许用各种语言编写测试。
它允许你有效地创建和运行测试,并已成为最流行的Java测试框架之一。xUnit框架激发了Smalltalk和C++的JUnit。由于JUnit是xUnit测试框架家族的成员,它被设计为支持不同的测试,包括单元、功能和集成测试。
JUnit主要用于单元测试,但它也可用于其他测试,如功能和集成测试。功能测试是测试系统的功能。它们与单元测试不同,因为它们测试的是整个系统而不是单个单元。集成测试测试两个或多个系统的集成。它们与单元测试不同,因为它们测试的是系统的各个组成部分如何协同工作,而不是单独工作。
@Test
注解的方法定义了一个测试,为了运行这个方法,JUnit会创建一个包含类的新实例,然后再调用这个被注释的方法。@RunWith
注解指定。<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
expected
:指定一个期望的异常timeout
:指定一个超时时间,单位毫秒附着在静态方法
必须执行一次并在类的所有测试之前,这种情况一般用于测试计算、共享配制方法(如数据库连接)。每个测试类只运行一次。静态方法
。每个测试类只运行一次。public class JunitTest { /** * 只执行一次 */ @BeforeClass public static void beforeClass() { System.out.println("before class"); } /** * 只执行一次 */ @AfterClass public static void afterClass() { System.out.println("after class"); } /** * 有多少个@Test就执行几次 */ @Before public void before() { System.out.println("before"); } /** * 有多少个@Test就执行几次 */ @After public void after() { System.out.println("after"); } @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } }
输出:
before class
before
test1
after
before
test2
after
after class
JUnit的Assert类提供了一系列断言方法。
断言一个条件为真
:
断言一个条件为假
:
断言测试失败,抛出AssertionError
:
断言两个值相等
:值类型可能是int,short,long,byte,char,Object,第一个参数是一个可选字符串消息
断言两个值不相等
:
断言预期数组和结果数组相等
:数组类型可能是int,short,long,byte,char,Object
断言一个对象不为空(null)
断言一个对象为空(null)
断言两个对象引用相同的对象
断言两个对象不是引用同一个对象
配合Hamcrest匹配器断言
import org.hamcrest.CoreMatchers; import org.junit.Test; import static org.junit.Assert.*; /** * @Author: acton_zhang * @Date: 2024/1/3 9:37 下午 * @Version 1.0 */ public class AssertionsTest { @Test public void testAssert() { //断言一个条件为真 assertTrue(2 > 1); //断言一个条件为假 assertFalse(1 > 2); //断言两个值相等,equals assertEquals(1, 1); assertEquals(2.2, 2.2, 0); assertEquals("hello", "hello"); //断言两个值不相等 assertNotEquals(1, 2); assertNotEquals(2.2, 1.1); assertNotEquals("hello", "world"); //断言两个数组相等 assertArrayEquals(new int[]{1, 2, 3}, new int[]{1, 2, 3}); assertArrayEquals(new String[]{"a", "b", "c"}, new String[]{"a", "b", "c"}); //断言一个对象不为空 assertNotNull(new Object()); //断言一个对象为空 assertNull(null); //断言两个对象引用一致 String a = "hello"; String b = "hello"; assertSame(a, b); //断言两个对象引用不一致 Object aa = new Object(); Object bb = new Object(); assertNotSame(aa, bb); //Hamcrest匹配器 assertThat(5.5, CoreMatchers.equalTo(5.5)); } }
Hamcrest是一个库,包含了大量有用的匹配器对象(也称为约束或者谓语),它可以被植入到开发语言中(Java、C++等)。
目前,JUnit提供的匹配符,定义在两个类里面:
JUnitMatchers提供的匹配符有两个:
CoreMathers提供的匹配符如下:
一般匹配符
:
逻辑匹配符
:用于组合多个匹配符
&&
||
字符串匹配符
:
集合相关匹配符
:
其他匹配符
:
import org.junit.Test; import java.io.Serializable; import java.util.Arrays; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; /** * @Author: acton_zhang * @Date: 2024/1/3 11:58 下午 * @Version 1.0 */ public class HamcrestTest { @Test public void hamcrestTest() { /** * 一般匹配符 */ //anything,总是匹配 assertThat(1, anything()); //equalTo,Object.equals assertThat("hello", equalTo("hello")); //instanceOf,测试类型 assertThat("hello", instanceOf(String.class)); //简写 assertThat("hello", isA(String.class)); //nullValue,是否为null assertThat(null, nullValue()); //notNullValue,不为null assertThat(new Object(), notNullValue()); //sameInstance,是否为相同引用 assertThat("123", sameInstance("123")); //theInstance,是否为相同引用 assertThat("123", theInstance("123")); /** * 逻辑匹配符 */ //allOf,相当于&& assertThat("hello", allOf(notNullValue(), isA(String.class), startsWith("h"))); //anyOf,相当于|| assertThat("hello", anyOf(nullValue(), isA(String.class))); //both,两个条件都匹配 assertThat("hello", both(containsString("h")).and(containsString("o"))); //either,两个条件匹配一个即可 assertThat("fan", either(containsString("a")).or(containsString("b"))); //not,取反 assertThat(null, not(notNullValue())); assertThat(null, not(1)); /** * 字符串匹配符 */ //containsString,包含 assertThat("hello", containsString("ll")); //startsWith,以...开头 assertThat("hello", startsWith("he")); //endsWith,以...结尾 assertThat("hello", endsWith("lo")); /** * 集合相关匹配器 */ //everyItem,集合每一项都匹配 assertThat(Arrays.asList("ab", "ac", "ad", "ae"), everyItem(containsString("a"))); //hasItem,集合包含某一项,或集合中的某一项能匹配时,该匹配器匹配 assertThat(Arrays.asList("ab", "ac", "ad", "ae"), hasItem("ab")); assertThat(Arrays.asList("ab", "ac", "ad", "ae"), hasItem(equalTo("ab"))); //hasItems,集合包含某几个项,或集合中的某几个项能匹配时,该匹配器匹配 assertThat(Arrays.asList("ab", "ac", "ad", "ae"), hasItems("ab", "ac")); assertThat(Arrays.asList("ab", "ac", "ad", "ae"), hasItems(equalTo("ab"), startsWith("ad"))); /** * 其他匹配符 */ //describedAs,包装现有的匹配器,用指定的描述覆盖其描述 assertThat(null, describedAs("is null", nullValue())); //is,改进可读性的装饰匹配符 assertThat(1, is(1)); assertThat(1, is(equalTo(1))); assertThat(1, is(instanceOf(Number.class))); //isA,改进可读性的装饰匹配符 assertThat(1, isA(Number.class)); assertThat(1, isA(Serializable.class)); } }
public class IsNotANumber extends TypeSafeMatcher<Double> {
protected boolean matchesSafely(Double number) {
return number.isNaN();
}
public void describeTo(Description description) {
description.appendText("not a number");
}
@Factory
public static <T> Matcher<Double> notANumber() {
return new IsNotANumber();
}
}
@Test
public void numberTest() {
assertThat(Math.sqrt(-1), is(IsNotANumber.notANumber()));
}
JUnit可以向后兼容3.8.x版本。所以JUnit4不仅要运行JUnit4的测试还要运行JUnit3.x版本的测试。所以JUnit提供了不同的运行器,分别用来运行JUnit3.x和JUnit4的测试以及其他不同的测试集。
可以通过@RunWith
注解指定测试的运行器,例如SpringBoot提供的运行器集成了spring的一些功能。
@RunWith(SpringJUnit4ClassRunner.class)
另外为了统一运行器,JUnit提供了一个facade门面,即org.junit.runner.JUnitCore
,它可以运行任何运行器,并收集测试结果与统计信息。
Parameterized(参数化)的测试运行器允许使用不同的参数多次运行同一个测试。
要使用参数化的测试运行器来运行一个测试类,那就需要满足一下要求:
@RunWith
注解,并且要将Parameterized.class
作为它的参数org.junit.runners.Parameterized.Parameters
修饰的,返回值为 java.util.Collection
的公共静态方法
,并在此方法中初始化所有需要测试的参数对。带有参数的公共构造函数
,并在其中为第二个环节中声明的几个变量赋值被测试的方法:
public class Caculator {
public double add(double a, double b) {
return a + b;
}
}
测试类:
import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import static org.junit.Assert.*; import java.util.Arrays; import java.util.Collection; //指定参数化运行器 @RunWith(value = Parameterized.class) public class ParameterizedTest { //声明所需的实例变量 private double expected;//期望的值 private double valueA;//测试方法参数A private double valueB;//测试方法参数B //带参数的构造,为实例变量赋值 public ParameterizedTest(double expected, double valueA, double valueB) { this.expected = expected; this.valueA = valueA; this.valueB = valueB; } //Parameters修饰的静态方法,返回Collection,内部是所需的参数数组 @Parameterized.Parameters public static Collection<Double[]> getTestParameters() { return Arrays.asList(new Double[][]{ {2.2, 1.1, 1.1}, {5.5, 2.2, 3.3}, {9.9, 3.3, 6.6} }); } //测试方法 @Test public void testAdd() { Caculator caculator = new Caculator(); //浮点数计算,精度0.0000000001即可 assertEquals(expected, caculator.add(valueA, valueB), 0.0000000001); } }
Suite(套件)测试可以组合多个测试类一次运行。Suite是一个容器,用来把几个测试归在一起,并把它们作为一个集合一起运行。
如果没有使用@SuiteClasses指定一个Suite,那么运行器会自动创建一个Suite。默认的Suite会扫描测试类,找出所有以@Test注释的方法。默认的Suite会在内部为每个@Test方法创建一个测试类的实例。然后JUnit就会独立地执行每个@Test方法,以避免潜在的负面影响。
Suite对象其实及时一个Runner,可以执行测试类中所有@Test注释的方法。
将多个测试类组成一个套件:
public class TestA {
@Test
public void test1() {
System.out.println("testA.test1 run...");
}
@Test
public void test2() {
System.out.println("testA.test2 run...");
}
}
public class TestB {
@Test
public void test1() {
System.out.println("TestB.test1 run...");
}
@Test
public void test2() {
System.out.println("TestB.test2 run...");
}
}
//指定套件测试运行器
@RunWith(Suite.class)
@Suite.SuiteClasses({
//这里组合测试类,影响程序运行的顺序
TestA.class,
TestB.class
})
public class SuiteTest {
}
输出:
testA.test1 run...
testA.test2 run...
TestB.test1 run...
TestB.test2 run...
将多个测试套件组合成测试集:
public class TestC {
@Test
public void test1() {
System.out.println("testC.test1 run...");
}
@Test
public void test2() {
System.out.println("testC.test2 run...");
}
}
public class TestD {
@Test
public void test1() {
System.out.println("testD.test1 run...");
}
@Test
public void test2() {
System.out.println("testD.test2 run...");
}
}
//指定套件测试运行器
@RunWith(Suite.class)
@Suite.SuiteClasses({
//这里组合测试类,影响程序运行的顺序
TestC.class,
TestD.class
})
public class SuiteTest2 {
}
//指定套件测试运行器
@RunWith(Suite.class)
@Suite.SuiteClasses({
//这里组合测试类,影响程序运行的顺序
SuiteTest.class,
SuiteTest2.class
})
public class SuiteCollectionTest {
}
输出:
testA.test1 run...
testA.test2 run...
TestB.test1 run...
TestB.test2 run...
testC.test1 run...
testC.test2 run...
testD.test1 run...
testD.test2 run...
public class TestB {
//忽略该方法
@Ignore
@Test
public void test1() {
System.out.println("TestB.test1 run...");
}
@Test
public void test2() {
System.out.println("TestB.test2 run...");
}
}
//忽略该类所有方法
@Ignore
public class TestC {
@Test
public void test1() {
System.out.println("testC.test1 run...");
}
@Test
public void test2() {
System.out.println("testC.test2 run...");
}
}
再次运行测试集,输出:
testA.test1 run...
testA.test2 run...
Test ignored.
TestB.test2 run...
testD.test1 run...
testD.test2 run...
Test ignored.
Junit 4 提供了 @Test注解的timeout参数来测试任意特定方法的执行时间。如果测试方法的执行时间大于指定的超时参数,测试方法将抛出异常,测试结果为失败。指定的超时参数是以毫秒
记。
public class TimeoutTest {
//指定超时时间,单位毫秒
@Test(timeout = 2000)
public void test() throws InterruptedException {
System.out.println("start...");
Thread.sleep(3000);
System.out.println("end...");
}
}
输出:
start...
org.junit.runners.model.TestTimedOutException: test timed out after 2000 milliseconds
Junit4 提供了@Test注解的expected参数来指定期望方法抛出的异常。如果测试方法没有抛出异常或异常不匹配,测试结果为失败。
public class ExceptionTest {
@Test(expected = NullPointerException.class)
public void test() {
Object o = new Object();
o = null;
o.hashCode();
}
}
JUnit是通过@FixMethodOrder
注解来控制测试方法的执行顺序的。@FixMethodOrder注解的参数是org.junit.runners.MethodSorters
对象,在枚举类org.junit.runners.MethodSorters
中定义了如下三种顺序类型:
//方法名字典顺序执行 @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class MethodSortTest { @Test public void bbb() { System.out.println("bbb"); } @Test public void ccc() { System.out.println("ccc"); } @Test public void aaa() { System.out.println("aaa"); } }
输出:
aaa
bbb
ccc
一个 Junit Rule(规则) 就是一个实现了org.junit.rules.TestRule
接口的类,这些类的作用类似于@Before @After注解,通过在类的执行前后运行指定的代码逻辑来增强测试,此外,Junit Rule还能做一些@Before @After注解实现不了的功能,如动态获取测试类、测试方法的信息
假设我们的测试用例需要在运行的时候连接外部资源(如数据库),在测试结束的时候释放连接,如果我们想在多个测试类中使用该数据库,那么最终将在每个测试类中重复该代码。通过使用 Rule,我们可以将重复的逻辑隔离在一个地方,并方便的在多个测试类中重用该代码。
Rule使用遵循以下步骤:
public
的字段,该字段类型需是org.junit.rules.TestRule
接口的实现类@Rule
注解标记该字段测试的时候,我们经常需要创建一个临时文件/文件夹,但是在测试类中管理这些文件/文件夹有时候会很麻烦,这时使用TemporaryFolderRule就能很好的帮我们管理测试过程中的文件的创建与删除:
public class TemporaryFolderRuleTest { //定义TemporaryFolder规则 @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Test public void test() throws IOException { //创建一个临时文件a.txt File file = temporaryFolder.newFile("a.txt"); //断言 assertTrue(file.isFile()); assertEquals(temporaryFolder.getRoot(), file.getParentFile()); //测试完成之后,TemporaryFolder会自动删除临时文件夹和文件(但不校验删除是否成功) } }
其它常用方法:
ExpectedException可以用来校验代码即将发生的异常:
public class ExpectedExceptionRuleTest { //定义ExpectedException规则 @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void test() { //接下来代码会抛出IllegalArgumentException thrown.expect(IllegalArgumentException.class); //且case是NullPointerException thrown.expectCause(isA(NullPointerException.class)); //且message是this is a exception thrown.expectMessage("this is a exception"); //抛出异常 throw new IllegalArgumentException("this is a exception", new NullPointerException()); } }
TestName可以很方便的让我们获取当前执行的测试方法的名称:
public class TestNameRuleTest {
//定义TestName规则
@Rule
public TestName name = new TestName();
@Test
public void test() {
String methodName = name.getMethodName();
assertEquals("test", methodName);
}
}
对于添加了TimeoutRule 的测试类,当测试类中的测试方法执行超过TimeoutRule 配置的时间时,测试方法执行就会被标记为失败:
public class TimeoutRuleTest {
//定义Timeout规则
@Rule
public Timeout timeout = Timeout.seconds(5);
@Test
public void test() throws InterruptedException {
TimeUnit.SECONDS.sleep(10);
}
}
顾名思义,ErrorCollector Rule就是用来收集Error的,正常情况下,当测试方法抛异常的时候,方法执行会中断。使用ErrorCollector可以把异常收集起来,让测试用例继续执行,待执行用例跑完之后再将异常信息报告出来。
public class ErrorCollectorRuleTest {
//定义ErrorCollector规则
@Rule
public final ErrorCollector errorCollector = new ErrorCollector();
@Test
public void test() {
errorCollector.addError(new Throwable("First thing went wrong!"));
errorCollector.addError(new Throwable("Second thing went wrong!"));
errorCollector.checkThat("Hello World", not(containsString("ERROR!")));
System.out.println("run finished");
}
}
输出:
run finished
java.lang.Throwable: First thing went wrong!
...
java.lang.Throwable: Second thing went wrong!
...
Process finished with exit code 255
从执行结果可以看出,程序先打印run finished再输出错误信息。说明Errorcollector暂时先讲错误信息给收集起来,待测试用例执行完再将报错信息抛出,整个测试用例的执行结果为 fail。
Verifier是一个抽象类,当我们需要在测试用例中验证一些额外的行为的时候,可以使用这个类并重写该类的verify方法。事实上,有些Rule就是通过继承Verifier类实现的。
public class VerifierRuleTest { private List messageLog = new ArrayList(); @Rule public Verifier verifier = new Verifier() { @Override protected void verify() throws Throwable { //在每个测试用例执行完的时候额外验证一下messageLog是否为空 assertFalse("Message Log is not Empty!", messageLog.isEmpty()); } }; @Test public void successTest() { //该用例成功 messageLog.add("I love java"); } @Test public void failTest() { //该用例失败 System.out.println("I don't like java"); } }
有时候我们希望在 debug 的时候 disable 掉某些 Rule,比如我们的测试代码中定义了一个Timeout Rule,但 debug 的时候往往会超出 Timeout Rule 设定的超时时间,这时候就可以使用 DisableOnDebug Rule 将 Timeout Rule 给 disable 掉,这样当我们通过 debug 模式执行测试用例时,Timeout Rule 就不会生效。
public class DisableOnDebugRuleTest {
//定义DisableOnDebug规则,屏蔽掉超时规则
@Rule
public DisableOnDebug disableTimeout = new DisableOnDebug(Timeout.seconds(5));
//debug模式可以通过测试
//直接run会失败
@Test
public void test() throws InterruptedException {
TimeUnit.SECONDS.sleep(10);
}
}
当做集成测试的时候,有可能需要在执行前准备一些数据(可以是一个文件/数据库连接),并在执行完将准备的数据进行删除。这时候就可以用 ExternalResource Rule 实现,前面所讲的 TemporaryFolder Rule 正式通过集成 ExternalResource 实现的,详情可以参考 TemporaryFolder 源码。
如果想让 Rule 作用于 class 级别,可以使用@ClassRule注解(类似于@Before、@After跟@BeforeClass、@AfterClass的区别)。
public class UsesExternalResource { //ClassRule必须标注在public static 字段上 @ClassRule public static ExternalResource resource = new ExternalResource() { //所有测试运行之前运行 @Override protected void before() { System.out.println("before"); } //所有测试运行之后运行 @Override protected void after() { System.out.println("after"); } }; @Test public void test1() { System.out.println("test1"); } @Test public void test2() { System.out.println("test2"); } }
输出:
before
test1
test2
after
除了使用junit提供的默认Rule外,还可以自定义我们自己的Rule, 自定义Rule需要继承TestRule接口,并实现其apply方法。
public class CustomerRuleTest { @Rule public TestMethodNameLogger testMethodNameLogger = new TestMethodNameLogger(); @Test public void test() { System.out.println("run case"); } static class TestMethodNameLogger implements TestRule { /** * * @param statement 表示测试方法执行过程中的 runtime * @param description 可以获取到当前正在执行的测试用例的一些信息(类名,方法名等 * @return */ @Override public Statement apply(Statement statement, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("before test,class name is " + description.getClassName()); //执行测试方法 statement.evaluate(); System.out.println("after test,class name is " + description.getClassName()); } }; } } }
输出:
before test,class name is pers.zhang.rules.CustomerRuleTest
run case
after test,class name is pers.zhang.rules.CustomerRuleTest
有多个Rule的时候,可以通过RuleChain来控制多个Rule的执行顺序,类似于过滤器链(FilterChain)。
public class RuleChainsTest { @Rule public RuleChain ruleChain = RuleChain .outerRule(new SimpleMsgLogger("First Rule")) .around(new SimpleMsgLogger("Second Rule")) .around(new SimpleMsgLogger("Third Rule")); @Test public void test() { System.out.println("run case"); } static class SimpleMsgLogger implements TestRule { private String log; public SimpleMsgLogger(String log) { this.log = log; } @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { System.out.println("Starting:" + log); base.evaluate(); System.out.println("Finishing:" + log); } }; } } }
输出:
Starting:First Rule
Starting:Second Rule
Starting:Third Rule
run case
Finishing:Third Rule
Finishing:Second Rule
Finishing:First Rule
写过 spring 项目的都知道,当我们想要在测试类中使用 spring 容器,需要在测试类上添加 @RunWith(SpringRunner.class)
注解,但此时如果你又想用 junit 的参数化测试的话,就会有冲突产生,因为 junit 参数化测试需要在测试类上添加 @RunWith(Parameterized.class)
注解
我们知道,一个测试类不能同时添加两个 @RunWith 注解,对于这种情况,就可以使用 junit Rule 解决。
可以使用 @SpringClassRule @SpringMethodRule
这两个注解来替代 @RunWith(SpringRunner.class)
启动 spring 容器。
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = WiremockApplication.class)
public class RuleTest {
@ClassRule
public static SpringClassRule springClassRule = new SpringClassRule();
@Rule
public SpringMethodRule springMethodRule = new SpringMethodRule();
@Autowired
public SampleController sampleController;
@Test
public void test() {
sampleController.hello();
}
}
IDEA 一般默认安装了插件 JUnit,如下图所示:可在 settings 中的 Plugins 选项卡中的 Installed 一栏中搜索 JUnit 查看。
首先,在项目根目录下新建 Test 文件夹(或其他任意你喜欢的名字),并在 Project Structure 中标记为 Test(默认为 Source),用以保存生成的测试类。标记后,自动生成的测试类都会自动保存在此文件夹下。
在需要测试的类上右键 -> Go To -> Test,选择 “create new test…”,打开生成界面。
在生成界面中选择 Testing Library 为 JUnit 4(默认为 Arquillian JUnit 4),同时勾选需要测试的函数,然后点击 OK 即可生成。(可以自行指定生成的包,@Before,@After方法等)
之后我们可以看到 Test 文件夹下已经有了生成好的测试类。
package pers.zhang.junit;
import org.junit.Test;
import static org.junit.Assert.*;
public class CaculatorTest {
@Test
public void add() {
}
}
待测试方法:
public class Caculator {
public double add(double a, double b) {
return a + b;
}
}
测试类:
public class CaculatorTest {
@Test
public void add() {
Caculator caculator = new Caculator();
assertEquals(2.2, caculator.add(1.1, 1.1), 0);
}
}
选择 Edit Configuration 以设置 Code Coverage 检查。
在弹出的窗口中左侧选择 JUnit 对应的运行模块,然后选择 Code Coverage 选项卡,之后选择下方的 Tracing(默认为 Sampling),接着点击右侧的添加期望检查的类。
之后选择 Run “测试类名” with Coverage 即可查看测试覆盖率了。左侧的绿色条代表方法中的每条语句都测试到了,红色代表完全没有测试,黄色代表部分语句测试到。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。