Professional Documents
Culture Documents
Junit5 Mockito
Junit5 Mockito
JUNIT VÀ MOCKITO
4.3.1. Giới thiệu JUnit
JUnit là một framework viết unit test cho ngôn ngữ Java, có vai
trò quan trọng trong phát triển các ứng dụng test-driven. Trong
tài liệu này trình bày JUnit 5, nó tương thích các phiên bản Java
8 hoặc mới hơn. JUnit 5 bao gồm ba thành phần quan trọng sau:
155
thuộc gói org.junit.jupiter.api (Bảng 4.7).
Bảng 4.7. Các annotation để viết test case trong jUnit5.
@BeforeEach
Chạy trước mỗi phương thức test case.
@AfterEach
Chạy sau mỗi phương thức test case.
@BeforeAll
Chạy trước tất cả các phương thức test case, phương thức có
annotation phải là static.
@AfterAll
Chạy sau tất cả các phương thức test case, phương thức có
annotation phải là static.
@Test
Phương thức đóng vai trò test case.
@DisplayName
Cung cấp tên hiển thị cho phương thức test case hoặc test class.
@Disable
Bỏ qua một phương thức test case hoặc test class.
@Nested
Dùng tạo các lớp test case lồng nhau.
@Tag
Gán nhãn (tag) cho các phương thức test case hoặc test class
để dễ tìm kiếm và lọc test case.
@TestFactory
Đánh dấu phương thức là test factory cho kiểm thử động.
Ví dụ ta có lớp test như sau:
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
157
assertFalse()
Kiểm tra một biểu thức điều kiện là false.
assertTrue()
Kiểm tra một biểu thức điều kiện là true.
assertNotNull()
Kiểm tra một đối tượng khác null.
assertNull()
Kiểm tra một đối tượng là null.
assertSame()
Kiểm tra hai biến tham chiếu tham chiếu đến cùng đối tượng.
assertNotSame()
Kiểm tra 2 biến tham chiếu không tham chiếu cùng đối tượng.
assertThrows()
Kiểm tra chương trình ném ra ngoại lệ mong muốn hay không.
assertArrayEquals()
Kiểm tra hai mảng có bằng nhau không.
assertIterableEquals()
Kiểm tra 2 iterable hoàn toàn bằng nhau, trong đó các phần tử
cùng vị trí phải bằng nhau, và hai iterable không bắt buộc cùng
kiểu dữ liệu, chẳng hạn kiểm tra hai iterable khác kiểu dữ liệu
là LinkedList và ArrayList nếu có các phần tử bằng nhau tại
các vị trí thì xem là bằng nhau theo phương thức này.
assertLinesMatch()
Kiểm tra hai danh sách chuỗi khớp với nhau. Quá trình so
khớp cặp chuỗi (expected, actual) trong hai danh sách tương
ứng được thực hiện như sau:
- Kiểm tra nếu expected.equals(actual) là đúng thì
so sánh cặp tiếp theo.
- Ngược lại, expected được xem như biểu thức chính quy
và kiểm tra bằng phương thức matches(). Nếu khớp thì
so sánh cặp tiếp theo.
- Ngược lại, kiểm tra expected là chuỗi fast-forward (là
chuỗi bắt đầu và kết thúc bằng >> và chứa ít nhất 4 ký tự)
assertTimeout() và assertTimeoutPreemptively()
Kiểm tra nhiệm vụ trong test case được thực thi trong khoảng
thời gian (duration) cho phép.
158
Ví dụ ta có lớp phân số như sau cần viết các test case cho hai
phương thức rút gọn và so sánh bằng hai phân số.
public class PhanSo {
private int tuSo;
private int mauSo;
public PhanSo(int t, int m) {
if (m == 0)
throw new ArithmeticException("Mẫu#0");
this.tuSo = t;
this.mauSo = m;
}
public void rutGon() {
int u = ucln(this.tuSo, this.mauSo);
this.tuSo = this.tuSo / u;
this.mauSo = this.mauSo / u;
}
return a;
}
@Override
public boolean equals(Object obj) {
PhanSo p = (PhanSo) obj;
int t1 = this.tuSo * p.mauSo;
int t2 = this.mauSo * p.tuSo;
return t1 - t2 == 0;
}
@Override
public int hashCode() {
int hash = 7;
hash = 97 * hash + this.tuSo;
hash = 97 * hash + this.mauSo;
return hash;
}
159
}
Thiết kế các test case kiểm tra phương thức tìm ước chung lớn
nhất hai số nguyên.
package com.dht.test1;
Hình 4.16. Kết quả thực thi test case mẫu trên NetBeans IDE 11.2.
Thiết kế một số test case kiểm tra phương thức rút gọn phân số
và so sánh hai phân số.
- Phương thức test1() kiểm tra ném ngoại lệ khi truyền
mẫu số là 0.
- Phương thức test2() kiểm tra rút gọn phân số với tử số và
mẫu số là số dương.
160
- Phương thức test3() và test4() kiểm tra 2 hai phân số
bằng nhau.
- Phương thức test5() kiểm tra 2 mảng phân số bằng nhau.
- Phương thức test6() kiểm tra thực hiện rút gọn phân số
trong tối đa 2 giây.
package com.dht.test2;
@BeforeEach
public void setUp() {
}
@AfterEach
public void tearDown() {
}
@Test
@DisplayName("Kiểm tra ném ngoại lệ khi mẫu = 0")
public void test1() {
assertThrows(ArithmeticException.class, ()->{
new PhanSo(2, 0);
});
}
@Test
@Tag("important")
@DisplayName("Kiểm tra rút gọn phân số")
public void test2() {
PhanSo p = new PhanSo(8, 36);
p.rutGon();
assertEquals(p.getTuSo(), 2);
assertEquals(p.getMauSo(), 9);
}
@Test
161
@Tag("important")
@DisplayName("Kiểm tra hai phân số bằng nhau")
public void test3() {
PhanSo p1 = new PhanSo(-1, 2);
PhanSo p2 = new PhanSo(4, -8);
assertTrue(p1.equals(p2));
}
@Test
@DisplayName("Kiểm tra 2 phân số khác nhau")
public void test4() {
PhanSo p1 = new PhanSo(-1, 2);
PhanSo p2 = new PhanSo(-4, -8);
assertFalse(p1.equals(p2));
}
@Test
@DisplayName("Kiểm tra hai mảng phân số")
public void test5() {
PhanSo[] p1 = {new PhanSo(2, -4),
new PhanSo(8, 28)};
PhanSo[] p2 = {new PhanSo(-1, 2),
new PhanSo(-2, -7)};
assertArrayEquals(p1, p2);
}
@Test
@Tag("important")
@DisplayName("Lặp vô hạn khi tử hoặc mẫu âm")
public void test6() {
assertTimeoutPreemptively(
Duration.ofSeconds(2), () -> {
PhanSo p1 = new PhanSo(-8, 36);
p1.rutGon();
});
}
}
162
Hình 4.17. Kết quả thực thi test case PhanSo.
Assumptions cung cấp các phương thức tĩnh giúp thực thi kiểm
tra biểu thức điều kiện nào đó, khi assumptions trả về kết quả
kiểm tra thất bại thì ngoại lệ TestAbortedException sẽ được ném
ra và phương thức test sẽ dừng lại.
4.3.4. JUnit Test Suite
Test Suite cho phép thực thi test case thuộc nhiều test class và
nhiều package khác nhau. JUnit 5 cung cấp các annotation hỗ trợ
thực thi test suite.
Bảng 4.9. Các annotation hỗ trợ thực thi test suite.
@SelectPackages
Chỉ định tên các gói được chọn chạy trong test suite thông qua
@RunWith(JUnitPlatform.class).
@SelectClasses
Chỉ định tên các lớp được chọn chạy trong test suite thông qua
@RunWith(JUnitPlatform.class).
@IncludePackages và @ExcludePackages
Annotation @SelectPackages sẽ tìm test class trong tất cả các
gói chỉ định và các gói con của nó, nếu
- Nếu chỉ muốn sử dụng vài gói con trong gói chỉ định sử
dụng @IncludePackages.
- Nếu muốn bỏ qua vài gói con trong gói chỉ định sử dụng
@ExcludePackages.
@IncludeClassNamePatterns và
@ExcludeClassNamePatterns
Chỉ định các lớp được sử dụng hoặc bỏ qua khi thực thi test
suite bằng mẫu biểu thức chính quy.
@IncludeTags và @ExcludeTags
Chỉ định các phương thức test case được sử dụng hoặc bỏ qua
163
khi thực thi test suite sử dụng tag.
Ví dụ
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.SelectPackages;
import org.junit.runner.RunWith;
@RunWith(JUnitPlatform.class)
@SelectPackages({"com.dht.test2", "com.dht.test3"})
@SelectClasses(com.dht.test1.TestCase1.class)
public class TestCase {
}
4.3.5. Parameterized Test
Paramaterized Test cung cấp cơ chế thực thi một phương thức
test nhiều lần với các tham số khác nhau.
Các cách thức truyền đối số cho phương thức test.
- @ValueSource: chỉ định mảng giá trị là danh sách các đối
số lần lượt được truyền phương thức kiểm thử.
- @MethodSource: chỉ định các phương thức của lớp kiểm
thử hoặc từ lớp ngoài, phương thức này phải là phương thức
tĩnh (static).
- @CsvSource: chỉ định danh sách các phần tử, mỗi phần tử
là chuỗi gồm nhiều giá trị cách nhau bằng dấu phẩy tương
ứng là các đối số truyền vào phương thức kiểm thử.
- @CsvFileSource: đọc các đối số từ tập tin CSV, mỗi cột
của từng dòng tương ứng là danh sách các đối số truyền vào
phương thức kiểm thử.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import static
org.junit.jupiter.params.provider.Arguments.arguments;
import
org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
164
import org.junit.jupiter.params.provider.ValueSource;
@ParameterizedTest
@ValueSource(ints = {1, 4, 6, 10, 15})
public void testKhongNguyenTo(int n) {
assertFalse(Tester.ktNguyenTo(n));
}
@ParameterizedTest
@CsvSource({"2,true", "4,false", "7,true"})
public void testCSV(int n, boolean expected) {
assertEquals(Tester.ktNguyenTo(n),
expected);
}
@ParameterizedTest
@CsvFileSource(resources = "/data/data.csv",
numLinesToSkip = 1)
public void testCSVFile(int n,
boolean expected) {
assertEquals(Tester.ktNguyenTo(n),
expected);
}
@ParameterizedTest
@MethodSource(value = "primeData")
public void testMethod(int n,
boolean expected) {
assertEquals(Tester.ktNguyenTo(n),
expected);
}
165
Trong đó tập tin data.csv nằm trong thư mục test/resources/data
(xem Hình 4.9).
Hình 4.19. Minh hoạ cấu trúc project kiểm thử với junit.
4.3.6. Mockito
Unit test thực hiện việc kiểm thử các chức năng độc lập. Tuy
nhiên thường một đơn vị (lớp, phương thức) kiểm thử sẽ phụ
166
thuộc một số lớp khác, vấn đề này có thể hạn chế bằng cách sử
dụng các thành phần giả lập trong quá trình kiểm thử, điều này
giúp tập trung kiểm thử chức năng đang thực hiện.
Mocking là cách thức kiểm thử các chức năng của lớp riêng biệt,
các đối tượng mock (mock object) là các đối tượng giả lập bắt
chước các hành vi của đối tượng thật, các đối tượng này sẽ trả về
một dữ liệu giả (dummy data) tương ứng với dữ liệu vào.
Mokito là một mocking framework của Java, Mockito sử dụng
các mock interface, các chức năng giả (dummy function) sẽ được
thêm vào interface để thực hiện unit test.
Để sử dụng Mockito, ta thêm dependency sau:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.5.7</version>
<scope>test</scope>
</dependency>
@BeforeEach
public void setUp() {
this.service = mock(ICalculatorService.class);
this.app.setService(service);
}
@Test
public void testAdd() {
when(service.add(5, 20)).thenReturn(25);
Assertions.assertEquals(app.add(5, 20), 25);
}
@Test
public void testSubstract() {
when(service.substract(20, 15)).thenReturn(5);
Assertions.assertEquals(
app.substract(20, 15), 5);
}
}
Trong ví dụ này, ta tạo một đối tượng giả (mock object) cho thể
hiện của interface ICalculatorService và gắn nó vào đối tượng
cần kiểm thử là app.
Mockito cung cấp phương thức verify() để kiểm tra một
phương thức của mock object với các đối số bắt buộc có được gọi
168
không, chẳng hạn test case:
@Test
public void testAdd() {
when(service.add(5, 20)).thenReturn(25);
Assertions.assertEquals(app.add(5, 20), 25);
verify(service).add(5, 21);
}
Mockito cũng cung cấp phương thức spy() để gắn kết mock
object với đối tượng thực sự. Giả sử ta đã có một lớp hiện thực
interface ICalculatorService:
class Cal implements ICalculatorService {
@Override
public int add(int a, int b) {
return a + b;
}
@Override
public int substract(int a, int b) {
return a - b;
}
}
@BeforeEach
public void init() {
this.service = spy(new Cal());
this.app.setService(service);
169
}
@Test
public void testAdd() {
Assertions.assertEquals(app.add(5, 20), 25);
}
}
@BeforeEach
public void init() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testAdd() {
when(service.add(5, 20)).thenReturn(25);
Assertions.assertEquals(app.add(5, 20), 25);
}
@Test
public void testSubstract() {
when(service.substract(20, 15)).thenReturn(5);
Assertions.assertEquals(
app.substract(20, 15), 5);
}
}
171