GP Coder

Trang chia sẻ kiến thức lập trình Java

  • Java Core
    • Basic Java
    • OOP
    • Exception Handling
    • Multi-Thread
    • Java I/O
    • Networking
    • Reflection
    • Collection
    • Java 8
  • Design pattern
    • Creational Pattern
    • Structuaral Pattern
    • Behavior Pattern
  • Web Service
    • SOAP
    • REST
  • JPA
  • Java library
    • Report
    • Json
    • Unit Test
  • Message Queue
    • ActiveMQ
    • RabbitMQ
  • All
Trang chủ Java library Unit Test Đơn giản hóa Unit Test với JUnit Rule

Đơn giản hóa Unit Test với JUnit Rule

Đăng vào 11/03/2019 . Được đăng bởi GP Coder . 5324 Lượt xem . Toàn màn hình

Nội dung

  • 1 JUnit Rule là gì?
  • 2 Ví dụ sử dụng JUnit Rule

JUnit Rule là gì?

Rule (quy tắc) trong JUnit 4 là một thành phần cho phép chúng ta viết code để thực hiện một số công việc trước và sau khi phương thức test thực thi. Do đó, tránh duplicate code trong các lớp test khác nhau. Chúng rất hữu ích để thêm nhiều chức năng hơn cho tất cả các phương thức test trong một class test. Chúng ta có thể mở rộng hoặc sử dụng lại các rule được cung cấp hoặc viết các rule mới theo mục đích sử dụng riêng.

Tất cả các lớp Rule của JUnit 4 phải implement một interface org.junit.rules.TestRule. Một số Rule có sẵn trong JUnit 4:

  • TemporaryFolder Rule
  • Timeout Rule
  • ExternalResource Rules và ClassRule
  • ErrorCollector Rule
  • Verifier Rule :
  • TestWatchman/TestWatcher Rules
  • TestName Rule
  • ExpectedException Rules
  • RuleChain
  • Custom Rules

Ví dụ sử dụng JUnit Rule

TemporaryFolder Rule

TemporaryFolder Rule cho phép chúng ta tạo các file và folder. Các file và folder này sẽ bị xóa cho dù phương thức test thành công hay thất bại ngay sau khi phương thức test kết thúc. Theo mặc định, không có ngoại lệ được ném nếu tài nguyên không thể bị xóa. Vì vậy, nếu cần chạy test một file hoặc folder tạm thời thì có thể sử dụng quy tắc này.

Ví dụ:


package com.gpcoder.junit.rule;

import static org.junit.Assert.assertTrue;

import java.io.File;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class TemporaryFolderRuleTest {

	/**
	 * Annotates fields that reference rules or methods that return a rule.
	 * 
	 * A field must be public, not static, and a sub-type of
	 * org.junit.rules.TestRule (preferred) or org.junit.rules.MethodRule.
	 * 
	 * A method must be public, not static, and must return a sub-type of
	 * org.junit.rules.TestRule (preferred) or org.junit.rules.MethodRule.
	 */
	@Rule
	public TemporaryFolder tempFolder = new TemporaryFolder();

	@Test
	public void testFile() throws Exception {
		File testFolder = tempFolder.newFolder("TestFolder");
		File testFile = tempFolder.newFile("test.txt");
		assertTrue(testFolder.exists());
		assertTrue(testFile.exists());
	}
}

Timeout Rule

ExternalResource Rules áp dụng cùng thời gian chờ (timeout) cho tất cả các phương thức test trong một class.


package com.gpcoder.junit.rule;

import java.util.concurrent.TimeUnit;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;

public class TimeoutRuleTest {

	/**
	 * Creates a Timeout that will timeout a test after the given duration, in
	 * milliseconds.
	 */
	@Rule
	public Timeout timeout = Timeout.millis(3000);

	@Test
	public void testA() throws Exception {
		try {
			// Do normal task
			TimeUnit.MILLISECONDS.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	@Test
	public void testB() throws Exception {
		try {
			// Do heavy task
			TimeUnit.SECONDS.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

Kết quả test:

ExternalResource Rules và ClassRule

ExternalResource là lớp cơ sở cho Rule tương tự như TemporaryFolder, nó cho phép thiết lập tài nguyên bên ngoài trước khi thực thi phương thức test (file, server, database connection, 3rd party serivce,…) và đảm bảo tài nguyên được giải phóng sau đó (teardown). Rule này thích hợp cho Integration Test (kiểm tra tích hợp).

Với annotation ClassRule, chúng ta có thể mở rộng hoạt động của InternalResource sang nhiều lớp. Nó rất hữu ích khi chúng ta cần lặp lại test setup/ teardown cho nhiều lớp. Ví dụ: nếu chúng ta đang thực hiện  Integration Test và chúng ta phải kết nối database trước khi kiểm tra và đóng kết nối sau kiểm tra, chúng ta nên sử dụng annotation ClassRule.

Annotation ClassRule phải được đánh dấu với public static field.

Ví dụ:

Giả sử chúng ta có class kết nối đến cơ sở dữ liệu và thực hiện một số thao tác cơ bản như thêm (insert), cập nhật (update), xóa (delete).

DatabaseConnection.java


package com.gpcoder.junit.rule.ExternalResource;

public class DatabaseConnection {

	private static final DatabaseConnection CONNECTION = new DatabaseConnection();

	private DatabaseConnection() {
	}

	public static DatabaseConnection getConnection() {
		return CONNECTION;
	}

	public void open() {
		System.out.println("Opened connection to database");
	}

	public void close() {
		System.out.println("Closed connection to database");
	}

	public boolean insert() {
		System.out.println("Saved");
		return true;
	}

	public boolean update() {
		System.out.println("Updated");
		return true;
	}

	public boolean delete() {
		System.out.println("Deleted");
		return true;
	}
}

Tiếp theo, chúng ta tạo một class kế thừa từ ExternalResource, class này sẽ mở kết nối/ đóng kết nối đến cơ sở dữ liệu một cách tự động trước khi mỗi class test được thực thi.

DatabaseExternalResource.java


package com.gpcoder.junit.rule.ExternalResource;

import org.junit.rules.ExternalResource;

/**
 * A base class for Rules (like TemporaryFolder) that set up an external
 * resource before a test (a file, socket, server, database connection, etc.),
 * and guarantee to tear it down afterward
 */
public class DatabaseExternalResource extends ExternalResource {

	private static final DatabaseConnection connection = DatabaseConnection.getConnection();

	@Override
	protected void before() throws Throwable {
		connection.open();
	}

	@Override
	protected void after() {
		connection.close();
	}

	public DatabaseConnection getConnection() {
		return connection;
	}
}

Để sử dụng ExternalResource, chúng ta sẽ thêm vào mỗi test class một field được đánh dấu annotation @ClassRule như sau:

UserDaoTest.java


package com.gpcoder.junit.rule.ExternalResource;

import static org.junit.Assert.assertEquals;

import org.junit.ClassRule;
import org.junit.Test;

public class UserDaoTest {

	/**
	 * When we use @ClassRule, our rule instance should be static, 
	 * just like @BeforeClass and @AfterClass methods.
	 */
	@ClassRule
	public static DatabaseExternalResource db = new DatabaseExternalResource();

	@Test
	public void testInsert() {
		assertEquals(true, db.getConnection().insert());
	}

	@Test
	public void testUpdate() {
		assertEquals(true, db.getConnection().update());
	}
}

RoleDaoTest.java


package com.gpcoder.junit.rule.ExternalResource;

import static org.junit.Assert.assertEquals;

import org.junit.ClassRule;
import org.junit.Test;

public class RoleDaoTest {

	/**
	 * When we use @ClassRule, our rule instance should be static, 
	 * just like @BeforeClass and @AfterClass methods.
	 */
	@ClassRule
	public static DatabaseExternalResource db = new DatabaseExternalResource();

	@Test
	public void testInsert() {
		assertEquals(true, db.getConnection().insert());
	}

	@Test
	public void testDelete() {
		assertEquals(true, db.getConnection().delete());
	}
}

Tiếp theo chúng ta sẽ tạo một Test Suite để thực thi 2 class test trên cùng lúc.

DaoIntegerationTest.java


package com.gpcoder.junit.rule.ExternalResource;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({ UserDaoTest.class, RoleDaoTest.class })
public class DaoIntegerationTest {
	// the class remains empty, used only as a holder for the above annotations
}

Chạy test cho class trên, chúng ta có kết quả như sau:


Opened connection to database
Saved
Updated
Closed connection to database
Opened connection to database
Deleted
Saved
Closed connection to database

Kết quả test:

ErrorCollector Rule

ErrorCollector Rule cho phép tiếp tục thực hiện test sau khi tìm thấy sự cố đầu tiên (exception). Nó thu thập tất cả các lỗi và báo cáo tất cả chúng cùng một lúc.


package com.gpcoder.junit.rule;

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;

public class ErrorCollectorRuleTest {

	/**
	 * The ErrorCollector rule allows execution of a test to continue after the
	 * first problem is found (for example, to collect _all_ the incorrect rows in a
	 * table, and report them all at once)
	 */
	@Rule
	public ErrorCollector collector = new ErrorCollector();

	@Test
	public void fails_first_assert_mismatch() {
		assertEquals(1, 2); // Failed and stop at first error
		assertEquals(2, 2); // Not running
		assertEquals(2, 3); // Not running
	}

	@Test
	public void fails_after_execution_1() {
		/**
		 * Adds a failure to the table if matcher does not match value.Execution
		 * continues, but the test will fail at the end if the match fails.
		 */
		collector.checkThat(5, is(8)); // First Error
		collector.checkThat(5, is(not(8))); // Passed
		collector.checkThat(5, is(equalTo(9))); // Second Error
	}

	@Test
	public void fails_after_execution_2() {
		/**
		 * Adds a Throwable to the table. Execution continues, but the test will fail at
		 * the end.
		 */
		collector.addError(new Throwable("first thing went wrong"));
		collector.checkThat(1, is(1));
		collector.checkThat(1, is(2));
	}
}

Kết quả test:

Verifier Rule

Verifier Rule thực hiện kiểm tra verfify và nếu thất bại thì phương thức test sẽ kết thúc với kết quả không thành công (fail). Chúng ta có thể viết logic để verfify riêng của mình với Verifier Rule.

Ví dụ:


import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Verifier;

public class VerifierRuleTest {

	private List<String> errorLog = new ArrayList<String>();

	/**
	 * Verifier is a base class for Rules like ErrorCollector, which can turn
	 * otherwise passing test methods into failing tests if a verification check is
	 * failed
	 */
	@Rule
	public Verifier verifier = new Verifier() {
		// After each method perform this check
		@Override
		public void verify() {
			assertTrue("Error Log is not Empty!", errorLog.isEmpty());
		}
	};

	@Test
	public void test1() {
		// Do something with an error and write to log
		errorLog.add("There is an error!");
	}

	@Test
	public void test2() {
		// Do something with an error and write to log
		errorLog.add("There is an error!");
	}

	@Test
	public void test3() {
		// Success
	}
}

Kết quả test:

TestWatchman/TestWatcher Rules

TestWatcher (TestWatchman không dùng nữa) là các lớp cho Rules và nó cho phép theo dõi (watch) các phương thức test và ghi log cho mỗi phương thức test pass hoặc fail.

Ví dụ:


package com.gpcoder.junit.rule;

import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runners.MethodSorters;
import org.junit.runners.model.Statement;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestWatcherRuleTest {
    
	private static StringBuilder watchedLog = new StringBuilder();
 
    @Rule
    public TestRule watchman = new TestWatcher() {
        @Override
        public Statement apply(Statement base, Description description) {
            return super.apply(base, description);
        }
 
        @Override
        protected void succeeded(Description description) {
            watchedLog.append(description.getDisplayName() + " " + "success!\n");
            System.out.println("Succeed! Watchlog:\n" + watchedLog);
        }
 
        @Override
        protected void failed(Throwable e, Description description) {
        	watchedLog.append(description.getDisplayName() + " " + e.getClass().getSimpleName() + "\n");
            System.out.println("Failed! Watchlog:\n" + watchedLog);
        }
 
        @Override
        protected void starting(Description description) {
            super.starting(description);
            System.out.println("Starting test! Watchlog:\n" + watchedLog);
        }
 
        @Override
        protected void finished(Description description) {
            super.finished(description);
			System.out.println("Test finished! Watchlog:\n" + watchedLog + "\n---\n");
        }
    };

	@Test
	public void T1_succeeds() {
		Assert.assertEquals(5, 5);
	}

	@Test
	public void T2_succeeds2() {
		Assert.assertEquals(2, 2);
	}

	@Test
	public void T3_fails() {
		Assert.assertEquals(3, 5);
	}
}

Chạy class test trên, chúng ta có kết quả như sau:


Starting test! Watchlog:

Succeed! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

Test finished! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

---

Starting test! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

Succeed! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T2_succeeds2(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

Test finished! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T2_succeeds2(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

---

Starting test! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T2_succeeds2(com.gpcoder.junit.rule.TestWatcherRuleTest) success!

Failed! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T2_succeeds2(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T3_fails(com.gpcoder.junit.rule.TestWatcherRuleTest) AssertionError

Test finished! Watchlog:
T1_succeeds(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T2_succeeds2(com.gpcoder.junit.rule.TestWatcherRuleTest) success!
T3_fails(com.gpcoder.junit.rule.TestWatcherRuleTest) AssertionError

---

TestName Rule

TestName Rule cho phép chúng ta có lấy được tên của phương thức test hiện tại bên trong phương thức test.

Ví dụ:


package com.gpcoder.junit.rule;

import static org.junit.Assert.assertEquals;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;

public class NameRuleTest {
	@Rule
	public TestName name = new TestName();

	@Test
	public void test1() {
		assertEquals("test1", name.getMethodName());
	}

	@Test
	public void test2() {
		assertEquals("test2", name.getMethodName());
	}
}

ExpectedException Rules

ExpectedException Rules cho phép test các loại ngoại lệ và thông báo ngoại lệ mong muốn bên trong phương thức test.

Ví dụ:


package com.gpcoder.junit.rule;

import static org.junit.Assert.assertTrue;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class ExpectedExceptionRuleTest {

	@Rule
	public final ExpectedException thrown = ExpectedException.none();

	@Test
	public void throwsNothing() {
		assertTrue(true);
	}

	@Test
	public void testThrowsNullPointerExceptionWithMessage() {
		// Verify that your code throws an exception that is an instance of specific type. 
		thrown.expect(NullPointerException.class);
		
		// Verify that your code throws an exception whose message contains a specific text.
		thrown.expectMessage("The given value cannot be null");
		
		// Do something to throw an NullPointerException
		throw new NullPointerException("The given value cannot be null");
	}
}

Custom Rules

Để viết một Custom Rules, chúng ta cần implement một interface TestRule. Interface này chỉ có phương thức apply(Statement, Description) trả về một thể hiện của Statement. Câu lệnh Statement.evaluate() sẽ được Junit runtime gọi khi class test thực thi.

Ví dụ chúng ta sẽ tạo một custom Rule để chụp lại màn hình ngay sau khi có một test case bị throw exception.

ScreenshotRule.java


package com.gpcoder.junit.rule.custom;

import java.io.IOException;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class ScreenshotRule implements TestRule {

	public Statement apply(final Statement base, final Description description) {
		return new Statement() {
			@Override
			public void evaluate() throws Throwable {
				System.out.println("Before test");
				try {
					base.evaluate();
				} catch (Throwable t) {
					takeScreenshot();
					throw t; // Report failure to JUnit
				} finally {
					System.out.println("After test");
				}
			}

			private void takeScreenshot() throws IOException {
				System.out.println("Take a screen shot and save to image file");
			}
		};
	}
}

ScreenshotRuleTest.java


package com.gpcoder.junit.rule.custom;

import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;

public class ScreenshotRuleTest {

	@Rule
	public ScreenshotRule screenshotRule = new ScreenshotRule();

	@Test
	public void testScreenShot() throws IOException {
		throw new IOException("Application is crashed");
	}
}

Kết quả test:


Before test
Take a screen shot and save to image file
After test

RuleChain

Một class có thể có rất nhiều Rule. Tuy nhiên, các Rule có thể được thực thi theo bất kỳ thứ tự nào, chúng ta không thể dựa vào thứ tự khai báo làm lệnh thực thi.Thay vào đó, chúng ta có thể tạo sử dụng RuleChain để sắp xếp thứ tự thực thi các Rule như chúng ta cần.

Ví dụ:


package com.gpcoder.junit.rule.chain;

import static org.junit.Assert.assertTrue;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;

public class RuleChainTest {

	@Rule
	public TestRule chain = RuleChain
			// Returns a RuleChain with a single TestRule. This method is the usual starting
			// point of a RuleChain.
			.outerRule(new LoggingRule("outer rule"))
			// Create a new RuleChain, which encloses the nextRule with the rules of the
			// current RuleChain.
			.around(new LoggingRule("middle rule"))
			.around(new LoggingRule("inner rule"));

	@Test
	public void test() {
		assertTrue(true);
	}
}

Kết quả test:


Starting: outer rule
Starting: middle rule
Starting: inner rule
Finished: inner rule
Finished: middle rule
Finished: outer rule

Tài liệu tham khảo:

  • https://github.com/junit-team/junit4/wiki/Rules
5.0
03
Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé! Và Donate tác giả

Shares

Chuyên mục: Unit Test Được gắn thẻ: JUnit

JUnit – Parameterized Test
JUnit – Hamcrest Matchers

Có thể bạn muốn xem:

  • Làm thế nào để lắng nghe các sự kiện mỗi khi một test được thực thi trong JUnit? (23/03/2019)
  • JUnit – Custom Hamcrest Matchers (18/03/2019)
  • PowerMockito – Suppressing Unwanted Behavior (10/04/2019)
  • Giới thiệu Powermock (08/04/2019)
  • JUnit – HTML Report với Surefire maven plugin (25/03/2019)

Bình luận

bình luận

Tìm kiếm

Bài viết mới

  • Clean code 13/01/2024
  • Giới thiệu CloudAMQP – Một RabbitMQ server trên Cloud 02/10/2020
  • Kết nối RabbitMQ sử dụng Web STOMP Plugin 19/06/2020
  • Sử dụng publisher confirm trong RabbitMQ 16/06/2020
  • Sử dụng Dead Letter Exchange trong RabbitMQ 13/06/2020

Xem nhiều

  • Hướng dẫn Java Design Pattern – Factory Method (97351 lượt xem)
  • Hướng dẫn Java Design Pattern – Singleton (96987 lượt xem)
  • Giới thiệu Design Patterns (86649 lượt xem)
  • Lập trình đa luồng trong Java (Java Multi-threading) (85486 lượt xem)
  • Giới thiệu về Stream API trong Java 8 (83025 lượt xem)

Nội dung bài viết

  • 1 JUnit Rule là gì?
  • 2 Ví dụ sử dụng JUnit Rule

Lưu trữ

Thẻ đánh dấu

Annotation Authentication Basic Java Behavior Pattern Collection Creational Design Pattern Cấu trúc điều khiển Database Dependency Injection Design pattern Eclipse Exception Executor Service Google Guice Gson Hibernate How to Interceptor IO Jackson Java 8 Java Core JDBC JDK Jersey JMS JPA json JUnit JWT Message Queue Mockito Multithreading OOP PowerMockito RabbitMQ Reflection Report REST SOAP Structuaral Pattern Swagger Thread Pool Unit Test Webservice

Liên kết

  • Clean Code
  • JavaTpoint
  • Refactoring Guru
  • Source Making
  • TutorialsPoint
  • W3Schools Online Web Tutorials

Giới thiệu

GP Coder là trang web cá nhân, được thành lập với mục đích lưu trữ, chia sẽ kiến thức đã học và làm việc của tôi. Các bài viết trên trang này chủ yếu về ngôn ngữ Java và các công nghệ có liên quan đến Java như: Spring, JSF, Web Services, Unit Test, Hibernate, SQL, ...
Hi vọng góp được chút ít công sức cho sự phát triển cộng đồng Coder Việt.

Donate tác giả

Tìm kiếm các bài viết của GP Coder với Google Search

Liên hệ

Các bạn có thể liên hệ với tôi thông qua:
  • Trang liên hệ
  • Linkedin: gpcoder
  • Email: contact@gpcoder.com
  • Skype: ptgiang56it

Follow me

Copyright 2025 © GP Coder · All Rights Reserved · Giới thiệu · Chính sách · Điều khoản · Liên hệ ·

Share

Blogger
Delicious
Digg
Email
Facebook
Facebook messenger
Flipboard
Google
Hacker News
Line
LinkedIn
Mastodon
Mix
Odnoklassniki
PDF
Pinterest
Pocket
Print
Reddit
Renren
Short link
SMS
Skype
Telegram
Tumblr
Twitter
VKontakte
wechat
Weibo
WhatsApp
X
Xing
Yahoo! Mail

Copy short link

Copy link