Android & Unit testing : Unit Testing là gì? (phần 1)
Bài đăng này đã không được cập nhật trong 5 năm
1. Unit Testing là gì ?
Unit Testing là gì ? Tại sao chúng ta lại cần nó trong quá trình phát triển ứng dụng.
Một Unit là một thành phần trong code nhỏ nhất mà ta có thể kiểm tra được như các hàm (Function), thủ tục (Procedure), lớp (Class), hoặc các phương thức (Method). Vì Unit được chọn để kiểm tra thường có kích thước nhỏ và chức năng hoạt động đơn giản, chúng ta không khó khăn gì trong việc tổ chức, kiểm tra, ghi nhận và phân tích kết quả kiểm tra nên việc phát hiện lỗi sẽ >dễ dàng xác định nguyên nhân và khắc phục cũng tương đối dễ dàng vì chỉ khoanh vùng trong một Unit đang kiểm tra.
Nghe có vẻ cũng khá loằng ngoằng nhưng túm cái váy lại hiểu theo cách đơn giản là chúng ta viết code để test cho những đoạn code mà ta đã viết .
Một số đặc điểm của Unit Test
- Code Unit test cần phải ngắn gọi, dễ hiểu, dễ đọc
- Mỗi Unit là 1 đơn vị riêng biệt, độc lập, không phụ thuộc vào Unit khác
- Mỗi Unit test là 1 method trong test class, tên method cũng là tên của UnitTest. Do đó cần đặt tên hàm rõ ràng ( test_validate_with_username) có thể tên method sẽ rất dài nhưng không sao, cái cần ở đây là nó phải được rõ ràng
- UnitTest cần phải nhanh, vì nó được chạy để kiểm tra mỗi lần build. Do đó trong unit test hạn chế các task tốn nhiều thời gian gọi như I/O, database, network...
- UnitTest nên test từng đối tượng riêng biệt, tránh sự phụ thuộc lẫn nhau của các class . Ví dụ : Khi bạn cần test cho function shouldBringUmbrella thì chỉ cần test cho chính nó, tránh móc nối đến các class như Weather để xem thời tiết ...
Nhưng có một số bạn sẽ gặp một vài thắc mắc "Ủa thế không lấy dữ liệu trong class Weather thì làm sao có thể biết nắng mưa mà mang ô" hay "code của tôi rối lắm nó gọi lẫn nhau lung tung, làm sao để có thể test từng function một cách riêng biệt"
- Để viết Unit Test, các class của bạn phải có quan hệ “lõng lẽo” với nhau (loose coupling), nếu không viết được Unit test nghĩa là các class dính với nhau quá chặt, sẽ khó thay đổi sau này. Hãy áp dụng các nguyên lý SOLID vào code, viết code với tư tưởng “viết sao để unit test được” sẽ là code của bạn uyển chuyển, dễ test hơn.
- Về vấn đề function shouldBringUmbrella và Weather Access, các class không nên gọi trực tiếp lẫn nhau mà gọi thông qua interface. Khi Unit test, ta sẽ thay các hiện thực interface này bằng các class giả (Mock class), thay thế cho class weather thật ( Các bạn cũng có thể tìm hiểu về dependency injection và dagger 2 trong Android)
Một số lợi ích của Unit Test:
- Nếu viết Unit Test một cách cẩn thận, code của bạn sẽ ít lỗi hơi, vì Unit Test sẽ phát hiện lỗi cho bạn
- Phát hiện những hàm chạy chậm và không hiệu quả thông qua thời gian chạy của Unit Test.
- Tăng sự tự tin khi code, vì đã có Unit Test phát hiện lỗi.
- Khi refactor code, sửa code hay thêm chức năng mới, Unit Test đảm bảo chương trình chạy đúng, phát hiện những lỗi tiềm tàng mà ta bỏ lỡ.
Unit Testing trong Android với Mockito
Bản chất của Unit test là kiểm thử đơn vị chỉ mình nó làm sao chạy được không phụ thuộc thằng module hay đơn vị khác khi run test. Khi xây dựng ứng dụng, khi một func của class A được nhét vào một func class B, cuối cùng class B phụ thuộc class A. Trong nguyên lý SOLID, chữ cái D cuối cùng có một chút nội dung:
//Dependency Inversion Principle
Các module cấp cao không nên phụ thuộc vào các module cấp thấp. Cả 2 nên phụ thuộc vào abstraction.
Interface (abstraction) không nên phụ thuộc vào chi tiết, mà ngược lại. (Các class giao tiếp với nhau thông qua interface, không phải thông qua implementation.)
Vậy khó kiểm tra như thế nào ?
Đơn giản: Khi một phụ thuộc có tác dụng phụ. Ý của câu này, một chức năng đang gọi cái gì đấy bên ngoài, chẳng hạn như call API, truy vấn database, kiểm tra trạng thái status... sẽ gây ra những kết quả không như mong đợi.
=> Điều đó sinh ra kiểm tra nhân đôi (Test double)
Test double là gì?
Đơn giản bạn hãy nghĩ tới trường hợp diễn viên đóng thế: người chuyên gia đóng những cảnh quay nguy hiểm thay thế cho nhân vật chính của chúng ta.
- Test double: chúng là các đối tượng thay thể thử nghiệm sự phụ thuộc.
- Tách rời sự phụ thuộc kiểm tra dễ dàng hơn.
Thông thường Test double được sử dụng trong các Unit Test và cả Integration Test
Trong Test Double này sẽ có nhiều loại khác nhau: Spies, Stubs, Mocks
Stub
Stub là một đối tượng chứa dữ liệu được xác định trước và sử dụng nó để thay thế các đối tượng phụ thuộc được gọi. Stub kiểm soát quy định đầu vào của dữ liệu, bắt buộc function trả về kết quả mà ta mong muốn. Một ví dụ có thể là một đối tượng cần lấy một số dữ liệu từ cơ sở dữ liệu để cho một function sử dụng. Thay vì đối tượng thực sự, chúng ta tạo ra một stub và xác định dữ liệu nào sẽ được trả về.
public class GradesService {
private final Gradebook gradebook;
public GradesService(Gradebook gradebook) {
this.gradebook = gradebook;
}
Double averageGrades(Student student) {
return average(gradebook.gradesFor(student));
}
}
Thay vì gọi xuống cơ sở dữ liệu từ class Gradebook để lấy điểm học sinh thực sự, chúng ta định cấu hình Stub với các grades sẽ được trả về. Chúng ta sử dụng stub kiểm tra thuật toán tính toán trung bình. Quy định đầu vào và biết trước kết quả trả về.
public class GradesServiceTest {
private Student student;
private Gradebook gradebook;
@Before
public void setUp() throws Exception {
gradebook = mock(Gradebook.class);
student = new Student();
}
@Test
public void calculates_grades_average_for_student() {
when(gradebook.gradesFor(student)).thenReturn(grades(8, 6, 10)); //stubbing gradebook
double averageGrades = new GradesService(gradebook).averageGrades(student);
assertThat(averageGrades).isEqualTo(8.0);
}
}
Mock
Mock object là một đối tượng ảo mô phỏng các tính chất và hành vi giống hệt như đối tượng thực được truyền vào bên trong khối mã đang vận hành nhằm kiểm tra tính đúng đắn của các hoạt động bên trong Khác với Stub, Mock tập trung vào hành vi của function, chỉ ra phương thức nào được gọi, cách thức thực hiện giá trị trả về.
Chúng ta sử dụng Mock khi chúng tôi không muốn sử dụng những đối tượng thực tế hoặc khi không có cách nào dễ dàng để xác minh, code dự định đó đã được thực thi. Không có giá trị trả về và không có cách dễ dàng để kiểm tra thay đổi trạng thái hệ thống. Một ví dụ có thể là một chức năng gọi dịch vụ gửi e-mail. Chúng ta không muốn gửi email mỗi khi chúng tôi chạy thử. Hơn nữa, không dễ để xác minh trong các bài kiểm tra mà một email đúng đã được gửi. Điều duy nhất chúng ta có thể làm là xác minh các đầu ra của chức năng được thực hiện trong thử nghiệm.
public class SecurityCentral {
private final Window window;
private final Door door;
public SecurityCentral(Window window, Door door) {
this.window = window;
this.door = door;
}
void securityOn() {
window.close();
door.close();
}
}
Chúng tôi không muốn sử dụng window và door thực tế để kiểm tra phương pháp bảo mật đang hoạt động, phải không? Thay vào đó, chúng ta đặt các đối tượng mock của door và window trong mã test
public class SecurityCentralTest {
Window windowMock = mock(Window.class);
Door doorMock = mock(Door.class);
@Test
public void enabling_security_locks_windows_and_doors() {
SecurityCentral securityCentral = new SecurityCentral(windowMock, doorMock);
securityCentral.securityOn();
verify(doorMock).close();
verify(windowMock).close();
}
}
Sau khi thực hiện phương thức securityOn, các Mock của Door và Window đã ghi lại tất cả các tương tác. Điều này cho phép chúng ta xác minh rằng các bước đã được thực hiện, điều đó có đúng với logic mà ta mong muốn không. Đó là tất cả những gì chúng ta cần kiểm tra từ quan điểm của SecurityCental. Nhưng bạn có thể hỏi làm thế nào chúng ta có thể biết nếu chúng ta sử dụng chúng trong thực tế liệu có gì khác so với mock? Câu trả lời là chúng ta có thể. Nhưng chúng ta không quan tâm đến nó trong phạm vi của Mock. Chúng ta của thể sử dụng Spies để đảm bảo trong thực tế có thể chạy tốt như bài test.
Tổng kết
Unit Testing đang là một chủ để nóng bỏng trong phạm vi Sun*, bài viết trên là một số tìm hiểu của mình về Unit Testing hi vọng nó có thể giúp các bạn có một cái nhìn phần nào về Unit Test. Trong bài sau mình sẽ giới thiệu đến các bạn Spies , tại sao nó có thể đảm bảo object mock có khả năng như object thực tế khi ứng dụng release và framework Mockito mang đến cho bạn những gì trong kiểm thử cho Android.
Tài liệu tham khảo: https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da
All rights reserved