- vừa được xem lúc

[Investigation] Test double? Mock? Stub? Spy? Fake?

0 0 24

Người đăng: Nguyen Ngoc Man

Theo Viblo Asia

Lời mở đầu

Một vấn đề mà khá nhiều lập trình viên mắc phải đó là sử dụng những thứ mà mình chưa hiểu rõ về nó. Mình cũng vậy 😅😂😂Dù đã viết khá nhiều UT cho các dự án khác nhau nhưng đến bây giờ mình vẫn cảm thấy khá là nhập nhằng giữa các khác niệm được nhắc đến ở tiêu đề bài viết này. Vậy là mình ngồi đây, tìm hiểu và cùng xem lại các khái niệm về UT này là gì và có những sự khác biệt gì giữa chúng. Ok. Let's go!!!

Khái niệm

Chắc các bạn đã từng nghe trên một lần các khái niệm như test double, mock, stub, spy, fake trong một buổi sharing nào đấy. Hoặc thoáng qua trong một bài viết về chuyên môn. Nhưng tại sao chúng ta lại cần sử dụng chúng? Đầu tiên ta nhắc lại kiến thức cũ một chút. Unit test là gì? Unit test là một phương pháp kiểm thử phần mềm trong đó các đoạn mã (code) được kiểm tra độc lập và cụ thể, từng phần riêng lẻ của chương trình (như hàm, phương thức, lớp) được kiểm thử để đảm bảo chức năng của nó hoạt động đúng như mong đợi.

Vậy nên Test double được sinh ra để sử dụng trong kiểm thử phần mềm để chỉ các đối tượng giả định được sử dụng để thay thế cho các thành phần phụ thuộc của một đơn vị thử nghiệm. Test double được sử dụng để cô lập đơn vị thử nghiệm khỏi các thành phần phụ thuộc mà không cần thực sự sử dụng các thành phần đó.

Có nhiều loại test double, trong đó "Test double" là thuật ngữ tổng quát để chỉ đến các loại sau: Mock, Stub, Spy, Fake.

Mock

Là một đối tượng giả định được sử dụng để kiểm tra xem một phương thức đã được gọi hay chưa, và nếu được gọi, phương thức đó được gọi với các tham số chính xác hay không.

public class Calculator { private final AdditionService additionService; public Calculator(AdditionService additionService) { this.additionService = additionService; } public int add(int x, int y) { return additionService.add(x, y); }
} public interface AdditionService { int add(int x, int y);
} public class AdditionServiceImpl implements AdditionService { public int add(int x, int y) { return x + y; }
} public class CalculatorTest { private AdditionService mockAdditionService; private Calculator calculator; @Before public void setUp() { mockAdditionService = mock(AdditionService.class); calculator = new Calculator(mockAdditionService); } @Test public void testAddition() { // Giả định AdditionService trả về 5 khi được gọi với các tham số (2, 3) when(mockAdditionService.add(2, 3)).thenReturn(5); // Kiểm tra kết quả trả về của phương thức add trong lớp Calculator assertEquals(5, calculator.add(2, 3)); // Kiểm tra xem phương thức add của AdditionService đã được gọi với đúng các tham số hay chưa verify(mockAdditionService).add(2, 3); }
}

Trong ví dụ này, chúng ta đang kiểm thử lớp Calculator bằng cách sử dụng một test double cho AdditionService, được thực hiện bằng cách sử dụng Mockito để tạo ra một đối tượng mock cho AdditionService. Chúng ta giả định rằng AdditionService sẽ trả về giá trị 5 khi được gọi với các tham số là 2 và 3, sau đó kiểm tra kết quả trả về của phương thức add trong lớp Calculator. Chúng ta cũng kiểm tra xem phương thức add của AdditionService đã được gọi với đúng các tham số hay chưa bằng cách sử dụng phương thức verify của Mockito.

Stub

Là một đối tượng giả định được sử dụng để trả về giá trị cứng định trước cho một cuộc gọi phương thức cụ thể.

public class Order { private final PaymentGateway paymentGateway; public Order(PaymentGateway paymentGateway) { this.paymentGateway = paymentGateway; } public void checkout() { // Thực hiện việc thanh toán bằng cách sử dụng PaymentGateway boolean success = paymentGateway.pay(100.0); if (success) { System.out.println("Order successful"); } else { System.out.println("Order failed"); } }
} public interface PaymentGateway { boolean pay(double amount);
} public class PaymentGatewayStub implements PaymentGateway { @Override public boolean pay(double amount) { // Luôn trả về giá trị true return true; }
} public class OrderTest { @Test public void testCheckout() { PaymentGatewayStub paymentGatewayStub = new PaymentGatewayStub(); Order order = new Order(paymentGatewayStub); order.checkout(); // Kiểm tra xem Order đã in ra đúng thông báo khi thanh toán thành công hay không assertEquals("Order successful", systemOutRule.getLog().trim()); }
}

Trong ví dụ này, chúng ta đã sử dụng PaymentGatewayStub để cung cấp giá trị mặc định cho phương thức pay của PaymentGateway, để kiểm tra xem lớp Order đã in ra đúng thông báo khi thanh toán thành công hay không.

Spy

Là một đối tượng giả định được sử dụng để ghi lại thông tin về các cuộc gọi phương thức cụ thể, ví dụ như số lần gọi, các tham số được truyền vào và giá trị trả về.

public class Calculator { public int add(int x, int y) { return x + y; }
} public class CalculatorSpy extends Calculator { private int addCount = 0; @Override public int add(int x, int y) { addCount++; return super.add(x, y); } public int getAddCount() { return addCount; }
} public class CalculatorTest { @Test public void testAdd() { CalculatorSpy calculatorSpy = new CalculatorSpy(); int result = calculatorSpy.add(2, 3); assertEquals(5, result); assertEquals(1, calculatorSpy.getAddCount()); }
}

Trong ví dụ này, chúng ta đã tạo ra một CalculatorSpy để giám sát việc gọi phương thức add của Calculator. Chúng ta đã ghi lại số lần phương thức này được gọi bằng cách sử dụng một biến đếm và kiểm tra giá trị trả về của phương thức add, cũng như số lần phương thức được gọi đến.

Fake

Các phương thức giả lập như stub, mock và spy đôi khi không đủ để kiểm tra các trường hợp phức tạp hoặc yêu cầu sử dụng một đối tượng thực sự để thực hiện các hành động phức tạp. Trong trường hợp này, bạn có thể sử dụng một đối tượng giả định là một đối tượng giả (fake object).

Là một đối tượng giả định được sử dụng để cung cấp một triển khai thay thế cho một thành phần phụ thuộc thực sự, giúp giảm thiểu các phụ thuộc đến các thành phần bên ngoài hoặc giảm thiểu sự phức tạp của việc thiết kế và triển khai.

public interface UserDao { User getUser(int userId);
} public class UserDaoImpl implements UserDao { @Override public User getUser(int userId) { // Code để lấy User từ cơ sở dữ liệu // ... return user; }
} @Service
public class UserService { private UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } public boolean isUserValid(int userId) { User user = userDao.getUser(userId); return user != null && user.getStatus() == UserStatus.ACTIVE; }
} public class UserDaoFake implements UserDao { private Map<Integer, User> users = new HashMap<>(); public void addUser(User user) { users.put(user.getId(), user); } @Override public User getUser(int userId) { return users.get(userId); }
} public class UserServiceTest { private UserDaoFake userDaoFake; private UserService userService; @Before public void setUp() { userDaoFake = new UserDaoFake(); userService = new UserService(userDaoFake); } @Test public void testIsValidUser() { // Tạo một đối tượng User và thêm vào UserDaoFake User user = new User(1, "John", "john@example.com", UserStatus.ACTIVE); userDaoFake.addUser(user); // Gọi phương thức cần kiểm tra boolean result = userService.isUserValid(1); // Kiểm tra kết quả trả về assertTrue(result); }
}

Ở đây, chúng ta sử dụng một đối tượng giả UserDaoFake để thay thế cho đối tượng thực UserDaoImpl. Đối tượng UserDaoFake này chỉ đơn giản là lưu trữ các đối tượng User trong một bản đồ để có thể trả về một đối tượng User cụ thể khi phương thức getUser được gọi. Khi chúng ta kiểm tra phương thức isValidUser của lớp UserService, chúng ta sử dụng đối tượng UserDaoFake này để thực hiện việc lấy dữ liệu và kiểm tra kết quả trả về.

Tạm kết

Hi vọng qua bài viết này bạn đọc đã có cái nhìn rõ ràng hơn về các khái niệm đã nhắc đến trong tiêu đề. Chúc vui 😄

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 302

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 277

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 50

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 194

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 71

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 139