Kotlin: Best practice cho Unit test (phần 2)
Bài đăng này đã không được cập nhật trong 6 năm
4. Use Backticks and @Nested Inner Classes
-
Đặt tên của method test trong backticks. Điều này cho phép các "spaces" trong tên phương thức cải thiện khả năng đọc. Bằng cách này, chúng ta không cần thêm annotation @DisplayName.
-
@Nested của JUnit5 rất hữu ích để nhóm các method test. Các nhóm có thể là một số type test nhất định (như InputIsXY, ErrorCases) hoặc một nhóm cho từng method được test (GetDesign và UpdateDesign).
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class TagClientTest {
@Test
fun `basic tag list`() {}
@Test
fun `empty tag list`() {}
@Test
fun `empty tag translations`() {}
@Nested
inner class ErrorCases {
@Test
fun `server sends empty body`() {}
@Test
fun `server sends invalid json`() {}
@Test
fun `server sends 500`() {}
@Test
fun `timeout - server response takes too long`() {}
@Test
fun `not available at all - wrong url`() {}
}
}
5. Mockito-Kotlin
Mockito-Kotlin được khuyến khích sử dụng vì nó cung cấp API thuận tiện và idiomatic. Ví dụ Ví dụ, nó tạo điều kiện cho các type reified của Kotlin. Vì vậy, type có thể được infer và chúng ta không phải chỉ định rõ ràng.
//plain Mockito
val service = mock(TagService::class.java)
setClient(mock(Client::class.java))
//Mockito-Kotlin
val service: TagService = mock()
setClient(mock())
6. Tạo các mock
Việc tạo lại các mock trước mỗi test rất chậm và yêu cầu việc sử dụng lateinit.
/Don't
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignControllerTest_RecreatingMocks {
private lateinit var dao: DesignDAO
private lateinit var mapper: DesignMapper
private lateinit var controller: DesignController
@BeforeEach
fun init() {
dao = mock()
mapper = mock()
controller = DesignController(dao, mapper)
}
// takes 1,5 s!
@RepeatedTest(300)
fun foo() {
controller.doSomething()
}
}
Thay vào đó, hãy tạo mock instance một lần và đặt lại chúng trước hoặc sau mỗi test. Nó nhanh hơn đáng kể (1,5 giây so với 220 ms trong ví dụ) và cho phép sử dụng các trường không thay đổi với val.
// Do:
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignControllerTest {
private val dao: DesignDAO = mock()
private val mapper: DesignMapper = mock()
private val controller = DesignController(dao, mapper)
@BeforeEach
fun init() {
reset(dao, mapper)
}
// takes 210 ms
@RepeatedTest(300)
fun foo() {
controller.doSomething()
}
}
** 7. Xử lý các Class với State**
Phương pháp tạo một lần đã được trình bày cho test fixture chỉ hoạt động nếu chúng không có bất kỳ trạng thái nào hoặc có thể được đặt lại dễ dàng (như mocks). Trong các trường hợp khác, việc tạo lại trước mỗi test là không thể tránh khỏi.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DesignViewTest {
private val dao: DesignDAO = mock()
// the class under test has state
private lateinit var view: DesignView
@BeforeEach
fun init() {
reset(dao)
view = DesignView(dao)
}
@Test
fun changeButton() {
assertThat(view.button.caption).isEqualTo("Hi")
view.changeButton()
assertThat(view.button.caption).isEqualTo("Guten Tag")
}
}
Nguồn: https://blog.philipphauer.de/best-practices-unit-testing-kotlin/
All rights reserved