Nội dung
Một số Annotation cơ bản của JUnit
JUnit cung cấp một số Annotation để viết Test như sau:
@Before
Phương thực được đánh dấu với Annotation này sẽ được gọi trước mỗi khi phương thức @Test được gọi.
Nó thường được sử dụng để khởi tạo dữ liệu trước khi thực thi một phương thức @Test.
@After
Phương thực được đánh dấu với Annotation này sẽ được gọi sau mỗi khi phương thức @Test được gọi.
Nó thường được sử dụng để dọn dẹp bộ nhớ sau khi thực thi một phương thức @Test.
@BeforeClass
Phương thực được đánh dấu với Annotation này sẽ được gọi trước khi thực thi tất cả các phương thức @Test được gọi trong một Test class. Phương thức này chỉ được gọi một lần duy nhất.
Phương thức đánh dấu Annotation này phải là static.
Nó thường được sử dụng để khởi tạo dữ liệu cho việc thực thi một Test class.
@AfterClass
Tương tự như @BeforeClass, nhưng nó được gọi sau khi kết thúc thực thi các phương thức @Test. Phương thức này chỉ được gọi một lần duy nhất.
Phương thức đánh dấu Annotation này phải là static.
Nó thường được sử dụng để dọn dẹp bộ nhớ sau khi thực thi tất cả các phương thức @Test trong một Test class.
@Test
Được sử dụng để đánh dấu đây là một phương thức test.@Test(timeout=500)Được sử dụng khi cần giới hạn thời gian thực thi của một phương thức. Nếu vượt quá thời này thì phương thức sẽ fail.
@Test(expected=XxxException.class)
Được sử dụng khi cần test một ngoại lệ được throw ra từ phương thức được test. Nếu ngoại lệ không được throw thì phương thức sẽ fail.
@Ignore
Được sử dụng để đánh dấu phương thức này để được bỏ qua (ignore/ disable), không cần thực thi test.
Nó có thể sử dụng cho một phương thức test hay một class từ một test suite.
@FixMethodOrder
Annotation này cho phép user có thể chọn thứ tự thực thi các phương thức @Test trong một test class.
Một số Annotation khác
Ngoài ra còn một số Annotation khác như: @Rule, @ClassRule, @RunWith, @SuiteClasses, @Parameterized, … chúng ta sẽ cùng tìm hiểu ở một bài viết khác.
Ví dụ Lifecycle của một Test Class trong JUnit
Trong ví dụ bên dưới, chúng ta sẽ thấy được cách mà một Unit test được thi thi với JUnit.
package com.gpcoder.junit; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; public class JUnitLifeCycleTest { @BeforeClass public static void runOnceBeforeClass() { System.out.println("@BeforeClass - runOnceBeforeClass"); } @AfterClass public static void runOnceAfterClass() { System.out.println("@AfterClass - runOnceAfterClass"); } @Before public void runBeforeTestMethod() { System.out.println("@Before - runBeforeTestMethod"); } @After public void runAfterTestMethod() { System.out.println("@After - runAfterTestMethod"); } @Test public void test_method_1() { System.out.println("@Test - test_method_1"); } @Test public void test_method_2() { System.out.println("@Test - test_method_2"); } }
Output của chương trình:
@BeforeClass - runOnceBeforeClass @Before - runBeforeTestMethod @Test - test_method_1 @After - runAfterTestMethod @Before - runBeforeTestMethod @Test - test_method_2 @After - runAfterTestMethod @AfterClass - runOnceAfterClass
Ví dụ @Ignore a Test
Vì một lý do nào đó, chúng ta muốn tạm thời vô hiệu hóa test case (bỏ qua/ không chạy test case đó).
Thông thường ta sẽ xóa hoặc comment annotation @Test, như thế trình test runner sẽ bỏ qua method đó nhưng đồng thời test case đó cũng sẽ không được report, chúng ta có thể quên mất là có test case đó.
Biện pháp thay thế là sử dụng annotation @Ignore ở trước hoặc sau annotation @Test, sau khi chạy JUnit test, nó vẫn thông báo là có test case đó nhưng đang bị disable. Trong trường hợp muốn ignore tất cả các phương thức của một class test, chỉ đơn giản đặt annotation @Ingore ở mức class.
Ví dụ Ignore method:
package com.gpcoder.junit; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; public class IgnoreTest1 { @Test @Ignore("This test case will be ignored") public void testEquals() { String expected = "gpcoder.com"; Assert.assertEquals(expected, "gpcoder.com"); } @Test public void testTrue() { Assert.assertTrue(true); } @Test public void testFalse() { Assert.assertFalse(false); } }
Output chương trình:
Ví dụ Ignore class:
@Ignore("All test cases of this class will be ignored") public class IgnoreTest2 { // Some method test here. }
Ví dụ Timeout Test
Chúng ta có thể expect thời gian timeout của một test case bằng cách sử dụng thuộc tính timeout trong annoation @Test.
Giả sử chúng ta có một class như sau:
package com.gpcoder.junit.util; import java.util.concurrent.TimeUnit; public class TaskUtils { public static int doNormalTask() { try { // Do heavy task TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } return 1; } public static int doHeavyTask() { try { // Do heavy task TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return 1; } }
Bây giờ chúng ta sẽ viết Unit test với mong muốn thời gian thực thi mỗi phương thức tối đa là 3 giây. Nếu sau thời gian đó, thì xem như phương thức test bị failed. Chúng ta cần cãi tiến performance cho nó.
package com.gpcoder.junit; import org.junit.Assert; import org.junit.Test; import com.gpcoder.junit.util.TaskUtils; public class TimeoutTest1 { @Test(timeout = 3000) // 3 seconds public void testTimeout1() { int expected = 1; int actual = TaskUtils.doNormalTask(); Assert.assertEquals(expected, actual); } @Test(timeout = 3000) // 3 seconds public void testTimeout2() { int expected = 1; int actual = TaskUtils.doHeavyTask(); Assert.assertEquals(expected, actual); } }
Output của chương trình:
Như bạn thấy, tất cả các phương thức test trên đều expected thời gian timeout là 3 giây. Thay vì đặt thuộc tính này trong tất cả các phương thức test, chúng ta có thể sử dụng Timeout rule để áp dụng cho tất cả các phương thức trong class test.
package com.gpcoder.junit; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import com.gpcoder.junit.util.TaskUtils; public class TimeoutTest2 { // Set timeout max 3 seconds per each method tested @Rule public Timeout globalTimeout = Timeout.seconds(3); // 3 seconds @Test public void testTimeout1() { int expected = 1; int actual = TaskUtils.doNormalTask(); Assert.assertEquals(expected, actual); } @Test public void testTimeout2() { int expected = 1; int actual = TaskUtils.doHeavyTask(); Assert.assertEquals(expected, actual); } }
Ví dụ Expected Exceptions Test
Giả sử chúng ta có một class cần test như sau:
package com.gpcoder.junit.util; public class MathUtil { private MathUtil() { throw new UnsupportedOperationException("Cannot call constructor directly!"); } public static int divide(int dividend, int divisor) { if (divisor == 0) { throw new IllegalArgumentException("Cannot divide by zero (0)."); } return dividend / divisor; } }
Sử dụng expect exception
Trong một số trường hợp chúng ta cần viết unit test ứng với trường hợp xảy ra exception thì chúng ta expect kết quả là test case đó sẽ xảy ra một Exception chứ không phải một giá trị cụ thể.
package com.gpcoder.junit; import org.junit.Test; import com.gpcoder.junit.util.MathUtil; public class ExceptionTest1 { @Test(expected = IllegalArgumentException.class) public void testDivideByZero() throws Exception { MathUtil.divide(1, 0); } }
Sử dụng try/ catch
Việc sử dụng thuộc tính expected trong annotation @Test có nhược điểm là ta không thể kiểm tra được message của exception hay trạng thái của object sau khi exception được ném ra. Để khắc phục điều đó ta sử dụng try/ catch.
package com.gpcoder.junit; import org.junit.Assert; import org.junit.Test; import com.gpcoder.junit.util.MathUtil; public class ExceptionTest2 { @Test public void testDivideByZero() throws Exception { try { MathUtil.divide(1, 0); Assert.fail("Not throw an exception"); } catch (Exception e) { Assert.assertTrue(e instanceof IllegalArgumentException); Assert.assertEquals("Cannot divide by zero (0).", e.getMessage()); } } }
Lưu ý: phương thức fail() nếu được chạy thì tức là test case bị failed. Chúng ta nên sử dụng phương thức này để test exception với try/ catch để đảm bảo rằng phương thức được test phải throw exception hoặc là bị failed.
Sử dụng ExpectedException Rule
Thay vì sử dụng try/ catch exception, chúng ta có thể sử dụng ExpectedException, nó cũng giúp chúng ta test loại và message exception.
package com.gpcoder.junit; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import com.gpcoder.junit.util.MathUtil; public class ExceptionTest3 { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldTestExceptionMessage() throws Exception { // Keep this ordering: expect -> call the method which throw an exception thrown.expect(IllegalArgumentException.class); thrown.expectMessage("Cannot divide by zero (0)."); MathUtil.divide(1, 0); } }
Ví dụ @FixMethodOrder Test
Các phương thức test trong một class nên được viết một cách độc lập, không phụ thuộc lẫn nhau nên thứ tự thực thi một lớp không quan trọng. Tuy nhiên, chúng ta có thể xác định thứ tự thực thi của các method trong class test bằng cách dùng annotation @FixMethodOrder ở mức class. Có 3 kiểu sắp xếp là:
- @FixMethodOrder(MethodSorters.DEFAULT): Đây là kiểu sắp xếp mặc định nếu không khai báo @FixMethodOrder, tuy nhiên với kiểu này thì sẽ không thể xác định chính xác method nào sẽ được thực thi trước.
- @FixMethodOrder(MethodSorters.JVM): Thứ tự các method test dựa theo JVM. Tuy nhiên thứ tự này có thể bị thay đổi khi thực thi.
- @FixMethodOrder(MethodSorters.NAME_ASCENDING): Thứ tự các method được thực thi dựa theo tên method. Thông thường, nếu cần sắp xếp thì kiểu này được chọn bởi nó giữ đúng thứ tự theo tên phương thức, không bị thay đổi như 2 kiểu trên.
Ví dụ:
package com.gpcoder.junit; import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class FixMethodOrderTest { @Test public void test_11() { Assert.assertTrue(true); } @Test public void test_1() { Assert.assertTrue(true); } @Test public void test_10() { Assert.assertTrue(true); } @Test public void test_2() { Assert.assertTrue(true); } }
Output của chương trình:
Trên đây là một số Annotation cơ bản và thường được sử dụng trong JUnit test. Có một số phương thức có thể gây khó hiểu cho các bạn như Assert.assertTrue(), Assert.assertFalse(), … Đây là các API của JUnit để hỗ trợ kiểm tra kết quả mong muốn. Chúng ta sẽ cùng tìm hiểu về các API này trong bài viết tiếp theo.