CodeGym /Blog Java /Ngẫu nhiên /Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành...
John Squirrels
Mức độ
San Francisco

Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành

Xuất bản trong nhóm
Ngày nay, bạn sẽ không tìm thấy một ứng dụng nào không có các bài kiểm tra, vì vậy chủ đề này sẽ phù hợp hơn bao giờ hết đối với các nhà phát triển mới làm quen: bạn không thể thành công nếu không có các bài kiểm tra. Về nguyên tắc, hãy xem xét những loại thử nghiệm nào được sử dụng, sau đó chúng ta sẽ nghiên cứu chi tiết mọi thứ cần biết về thử nghiệm đơn vị. Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 1

Các loại thử nghiệm

một bài kiểm tra là gì? Theo Wikipedia: "Kiểm thử phần mềm liên quan đến việc thực thi một thành phần phần mềm hoặc thành phần hệ thống để đánh giá một hoặc nhiều thuộc tính quan tâm." Nói cách khác, đó là kiểm tra tính chính xác của hệ thống của chúng tôi trong các tình huống nhất định. Chà, hãy xem nói chung có những loại thử nghiệm nào:
  • Kiểm thử đơn vị — Kiểm thử với mục đích kiểm tra từng mô-đun của hệ thống một cách riêng biệt. Các thử nghiệm này nên áp dụng cho các bộ phận nguyên tử nhỏ nhất của hệ thống, ví dụ như các mô-đun.
  • Thử nghiệm hệ thống — Thử nghiệm cấp cao để kiểm tra hoạt động của một phần lớn hơn của ứng dụng hoặc toàn bộ hệ thống.
  • Kiểm tra hồi quy — Kiểm tra được sử dụng để kiểm tra xem các tính năng mới hoặc bản sửa lỗi có ảnh hưởng đến chức năng hiện có của ứng dụng hoặc giới thiệu các lỗi cũ hay không.
  • Kiểm tra chức năng — Kiểm tra xem một phần của ứng dụng có đáp ứng các yêu cầu được nêu trong thông số kỹ thuật, câu chuyện của người dùng, v.v. hay không.

    Các loại kiểm thử chức năng:

    • Kiểm thử hộp trắng — Kiểm tra xem một phần của ứng dụng có đáp ứng các yêu cầu trong khi biết cách triển khai bên trong của hệ thống hay không;
    • Kiểm thử hộp đen — Kiểm tra xem một phần của ứng dụng có đáp ứng các yêu cầu hay không mà không cần biết về triển khai nội bộ của hệ thống.

  • Kiểm tra hiệu năng — Các bài kiểm tra được viết để xác định hệ thống hoặc một phần của hệ thống hoạt động như thế nào dưới một mức tải nhất định.
  • Thử nghiệm tải — Các thử nghiệm được thiết kế để kiểm tra tính ổn định của hệ thống dưới mức tải tiêu chuẩn và để tìm mức tải tối đa mà ứng dụng vẫn hoạt động bình thường.
  • Stress testing — Thử nghiệm được thiết kế để kiểm tra hiệu suất của ứng dụng dưới tải không chuẩn và để xác định tải tối đa trước khi lỗi hệ thống.
  • Kiểm tra bảo mật — Các kiểm tra được sử dụng để kiểm tra tính bảo mật của hệ thống (khỏi tin tặc, vi rút, truy cập trái phép vào dữ liệu bí mật và các cuộc tấn công hấp dẫn khác).
  • Kiểm tra bản địa hóa — Kiểm tra bản địa hóa của ứng dụng.
  • Kiểm tra khả năng sử dụng — Kiểm tra nhằm mục đích kiểm tra khả năng sử dụng, tính dễ hiểu, tính hấp dẫn và khả năng học hỏi.
Tất cả điều này nghe có vẻ tốt, nhưng nó hoạt động như thế nào trong thực tế? Đơn giản! Chúng tôi sử dụng kim tự tháp thử nghiệm của Mike Cohn: Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 2Đây là phiên bản đơn giản hóa của kim tự tháp: giờ đây nó được chia thành các phần thậm chí còn nhỏ hơn. Nhưng hôm nay chúng ta sẽ không quá phức tạp. Chúng tôi sẽ xem xét phiên bản đơn giản nhất.
  1. Đơn vị — Phần này đề cập đến các bài kiểm tra đơn vị, được áp dụng trong các lớp khác nhau của ứng dụng. Họ kiểm tra đơn vị logic ứng dụng có thể chia nhỏ nhất. Ví dụ, các lớp, nhưng thường là các phương thức. Các bài kiểm tra này thường cố gắng hết sức có thể để tách biệt những gì được kiểm tra khỏi bất kỳ logic bên ngoài nào. Tức là họ cố tạo ảo giác rằng phần còn lại của ứng dụng đang chạy như mong đợi.

    Luôn luôn có nhiều bài kiểm tra này (nhiều hơn bất kỳ loại nào khác), vì chúng kiểm tra các phần nhỏ và rất nhẹ, không tiêu tốn nhiều tài nguyên (có nghĩa là RAM và thời gian).

  2. Tích hợp — Phần này đề cập đến kiểm thử tích hợp. Thử nghiệm này kiểm tra các phần lớn hơn của hệ thống. Nghĩa là, nó kết hợp một số phần logic (một số phương thức hoặc lớp) hoặc nó kiểm tra tính đúng đắn của tương tác với một thành phần bên ngoài. Các bài kiểm tra này thường nhỏ hơn bài kiểm tra đơn vị vì chúng nặng hơn.

    Một ví dụ về thử nghiệm tích hợp có thể là kết nối với cơ sở dữ liệu và kiểm tra tính chính xác của hoạt động của các phương thức làm việc với nó.

  3. Giao diện người dùng — Phần này đề cập đến các thử nghiệm kiểm tra hoạt động của giao diện người dùng. Chúng liên quan đến logic ở tất cả các cấp của ứng dụng, đó là lý do tại sao chúng còn được gọi là thử nghiệm đầu cuối. Theo quy định, có rất ít trong số chúng, vì chúng cồng kềnh nhất và phải kiểm tra các đường dẫn (được sử dụng) cần thiết nhất.

    Trong hình trên, chúng ta thấy rằng các phần khác nhau của tam giác có kích thước khác nhau: tỷ lệ xấp xỉ giống nhau tồn tại trong số lượng các loại thử nghiệm khác nhau trong công việc thực tế.

    Hôm nay chúng ta sẽ xem xét kỹ hơn các bài kiểm tra phổ biến nhất, bài kiểm tra đơn vị, vì tất cả các nhà phát triển Java tự trọng đều có thể sử dụng chúng ở mức cơ bản.

Các khái niệm chính trong kiểm thử đơn vị

Phạm vi kiểm tra (độ bao phủ của mã) là một trong những biện pháp chính để đánh giá ứng dụng được kiểm tra tốt như thế nào. Đây là tỷ lệ phần trăm mã được kiểm tra (0-100%). Trong thực tế, nhiều người theo đuổi tỷ lệ phần trăm này như là mục tiêu của họ. Đó là điều tôi không đồng ý, vì nó có nghĩa là các bài kiểm tra bắt đầu được áp dụng ở những nơi không cần thiết. Ví dụ: giả sử chúng tôi có các thao tác CRUD (tạo/lấy/cập nhật/xóa) tiêu chuẩn trong dịch vụ của mình mà không cần logic bổ sung. Các phương thức này hoàn toàn là các trung gian ủy thác công việc cho lớp làm việc với kho lưu trữ. Trong tình huống này, chúng tôi không có gì để kiểm tra, ngoại trừ có lẽ phương thức đã cho có gọi là phương thức DAO hay không, nhưng đó là một trò đùa. Các công cụ bổ sung thường được sử dụng để đánh giá phạm vi kiểm thử: JaCoCo, Cobertura, Clover, Emma, ​​v.v. Để nghiên cứu chi tiết hơn về chủ đề này, TDD là viết tắt của phát triển dựa trên thử nghiệm. Theo cách tiếp cận này, trước khi làm bất cứ điều gì khác, bạn viết một bài kiểm tra để kiểm tra mã cụ thể. Điều này hóa ra là thử nghiệm hộp đen: chúng tôi biết đầu vào là gì và chúng tôi biết đầu ra sẽ là gì. Điều này giúp tránh trùng lặp mã. Phát triển dựa trên thử nghiệm bắt đầu bằng việc thiết kế và phát triển các thử nghiệm cho từng phần chức năng trong ứng dụng của bạn. Trong cách tiếp cận TDD, trước tiên chúng tôi tạo một thử nghiệm xác định và kiểm tra hành vi của mã. Mục tiêu chính của TDD là làm cho mã của bạn dễ hiểu hơn, đơn giản hơn và không có lỗi. Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 3Cách tiếp cận bao gồm những điều sau đây:
  • Chúng tôi viết bài kiểm tra của chúng tôi.
  • Chúng tôi chạy thử nghiệm. Không có gì ngạc nhiên khi nó không thành công vì chúng tôi chưa thực hiện logic cần thiết.
  • Thêm mã khiến bài kiểm tra vượt qua (chúng tôi chạy lại bài kiểm tra).
  • Chúng tôi cấu trúc lại mã.
TDD dựa trên các bài kiểm tra đơn vị, vì chúng là các khối xây dựng nhỏ nhất trong kim tự tháp tự động hóa kiểm tra. Với các bài kiểm tra đơn vị, chúng ta có thể kiểm tra logic nghiệp vụ của bất kỳ lớp nào. BDD là viết tắt của phát triển dựa trên hành vi. Cách tiếp cận này dựa trên TDD. Cụ thể hơn, nó sử dụng các ví dụ bằng ngôn ngữ đơn giản để giải thích hành vi của hệ thống cho mọi người tham gia phát triển. Chúng tôi sẽ không đi sâu vào thuật ngữ này, vì nó chủ yếu ảnh hưởng đến người thử nghiệm và nhà phân tích kinh doanh. Trường hợp kiểm thử là một kịch bản mô tả các bước, điều kiện cụ thể và tham số cần thiết để kiểm tra mã đang được kiểm thử. Lịch thi thử là mã thiết lập môi trường thử nghiệm để có trạng thái cần thiết để phương thức được thử nghiệm chạy thành công. Đó là một tập hợp các đối tượng được xác định trước và hành vi của chúng trong các điều kiện được chỉ định.

Các giai đoạn thử nghiệm

Một bài kiểm tra bao gồm ba giai đoạn:
  • Chỉ định dữ liệu thử nghiệm (đồ đạc).
  • Thực hiện mã đang kiểm tra (gọi phương thức đã kiểm tra).
  • Kiểm chứng kết quả và so sánh với kết quả dự kiến.
Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 4Để đảm bảo tính mô-đun thử nghiệm, bạn cần cách ly khỏi các lớp khác của ứng dụng. Điều này có thể được thực hiện bằng cách sử dụng sơ khai, giả và gián điệp. Mô hình là các đối tượng có thể được tùy chỉnh (ví dụ: được điều chỉnh cho từng bài kiểm tra). Chúng cho phép chúng tôi chỉ định những gì chúng tôi mong đợi từ các cuộc gọi phương thức, tức là các phản hồi mong đợi. Chúng tôi sử dụng các đối tượng giả để xác minh rằng chúng tôi nhận được những gì chúng tôi mong đợi. Sơ khai cung cấp phản hồi được mã hóa cứng cho các cuộc gọi trong quá trình thử nghiệm. Họ cũng có thể lưu trữ thông tin về cuộc gọi (ví dụ: tham số hoặc số lượng cuộc gọi). Đôi khi chúng được gọi là gián điệp. Đôi khi mọi người nhầm lẫn giữa thuật ngữ sơ khai và giả định: sự khác biệt là sơ khai không kiểm tra bất cứ điều gì — nó chỉ mô phỏng một trạng thái nhất định. Một giả là một đối tượng có kỳ vọng. Ví dụ, một phương thức nhất định phải được gọi một số lần nhất định. Nói cách khác,

môi trường thử nghiệm

Vì vậy, bây giờ đến điểm. Có một số môi trường thử nghiệm (khung) có sẵn cho Java. Phổ biến nhất trong số này là JUnit và TestNG. Để xem xét ở đây, chúng tôi sử dụng: Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 5Kiểm tra JUnit là một phương thức trong một lớp chỉ được sử dụng để kiểm tra. Lớp thường được đặt tên giống như lớp mà nó kiểm tra, với "Test" được thêm vào cuối. Ví dụ: CarService -> CarServiceTest. Hệ thống xây dựng Maven tự động bao gồm các lớp như vậy trong phạm vi thử nghiệm. Trên thực tế, lớp này được gọi là lớp kiểm tra. Hãy xem qua các chú thích cơ bản:

  • @Test chỉ ra rằng phương thức này là một bài kiểm tra (về cơ bản, một phương thức được đánh dấu bằng chú thích này là một bài kiểm tra đơn vị).
  • @ Before biểu thị một phương thức sẽ được thực thi trước mỗi lần kiểm tra. Ví dụ: để điền vào một lớp dữ liệu thử nghiệm, đọc dữ liệu đầu vào, v.v.
  • @after được sử dụng để đánh dấu một phương thức sẽ được gọi sau mỗi lần kiểm tra (ví dụ: để xóa dữ liệu hoặc khôi phục giá trị mặc định).
  • @B BeforeClass được đặt phía trên một phương thức, tương tự như @B Before. Nhưng một phương thức như vậy chỉ được gọi một lần trước tất cả các bài kiểm tra cho lớp đã cho và do đó phải là phương thức tĩnh. Nó được sử dụng để thực hiện các hoạt động sử dụng nhiều tài nguyên hơn, chẳng hạn như quay vòng cơ sở dữ liệu thử nghiệm.
  • @afterClass ngược lại với @B BeforeClass: nó được thực hiện một lần cho lớp nhất định, nhưng chỉ sau tất cả các bài kiểm tra. Ví dụ, nó được sử dụng để xóa các tài nguyên liên tục hoặc ngắt kết nối khỏi cơ sở dữ liệu.
  • @Ignore biểu thị rằng một phương thức bị vô hiệu hóa và sẽ bị bỏ qua trong quá trình chạy thử nghiệm tổng thể. Điều này được sử dụng trong các tình huống khác nhau, ví dụ: nếu phương pháp cơ sở đã được thay đổi và thử nghiệm chưa được thực hiện lại để phù hợp với các thay đổi. Trong những trường hợp như vậy, bạn cũng nên thêm mô tả, ví dụ @Ignore("Some description").
  • @Test(expected = Exception.class) được sử dụng cho các bài kiểm tra âm tính. Đây là những bài kiểm tra xác minh cách thức hoạt động của phương thức trong trường hợp có lỗi, nghĩa là bài kiểm tra mong đợi phương thức đưa ra một số loại ngoại lệ. Một phương pháp như vậy được biểu thị bằng chú thích @Test, nhưng kèm theo dấu hiệu cho biết lỗi nào cần phát hiện.
  • @Test(timeout = 100) kiểm tra xem phương thức có được thực thi trong không quá 100 mili giây hay không.
  • @Mock được sử dụng phía trên một trường để gán một đối tượng giả (đây không phải là chú thích JUnit, mà thay vào đó đến từ Mockito). Khi cần, chúng tôi đặt hành vi của mô phỏng cho một tình huống cụ thể trực tiếp trong phương thức thử nghiệm.
  • @RunWith(MockitoJUnitRunner.class) được đặt phía trên một lớp. Chú thích này yêu cầu JUnit gọi các bài kiểm tra trong lớp. Có nhiều trình chạy khác nhau, bao gồm: MockitoJUnitRunner, JUnitPlatform và SpringRunner. Trong JUnit 5, chú thích @RunWith đã được thay thế bằng chú thích @ExtendWith mạnh mẽ hơn.
Chúng ta hãy xem xét một số phương pháp được sử dụng để so sánh kết quả:

  • assertEquals(Đối tượng mong đợi, Đối tượng thực tế) — kiểm tra xem các đối tượng được truyền có bằng nhau hay không.
  • assertTrue(boolean flag) — kiểm tra xem giá trị đã truyền có đúng không.
  • assertFalse(boolean flag) — kiểm tra xem giá trị đã truyền có sai hay không.
  • assertNull(Object object) — kiểm tra xem đối tượng đã truyền có phải là null hay không.
  • assertSame(Object firstObject, Object secondObject) — kiểm tra xem các giá trị được truyền có tham chiếu đến cùng một đối tượng hay không.
  • khẳng địnhĐó(T t, Matcher đối sánh) — Kiểm tra xem t có thỏa mãn điều kiện được chỉ định trong trình so khớp hay không.
AssertJ cũng cung cấp một phương pháp so sánh hữu ích: assertThat(firstObject).isEqualTo(secondObject) . Ở đây tôi đã đề cập đến các phương pháp cơ bản — những phương pháp khác là biến thể của các phương pháp trên.

Thử nghiệm trong thực tế

Bây giờ hãy xem tài liệu trên trong một ví dụ cụ thể. Chúng tôi sẽ kiểm tra phương pháp cập nhật của dịch vụ. Chúng tôi sẽ không xem xét lớp DAO vì chúng tôi đang sử dụng mặc định. Hãy thêm một phần khởi động cho các bài kiểm tra:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <version>2.2.2.RELEASE</version>
   <scope>test</scope>
</dependency>
Và ở đây chúng ta có lớp dịch vụ:

@Service
@RequiredArgsConstructor
public class RobotServiceImpl implements RobotService {
   private final RobotDAO robotDAO;

   @Override
   public Robot update(Long id, Robot robot) {
       Robot found = robotDAO.findById(id);
       return robotDAO.update(Robot.builder()
               .id(id)
               .name(robot.getName() != null ? robot.getName() : found.getName())
               .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
               .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
               .build());
   }
}
Dòng 8 — lấy đối tượng được cập nhật từ cơ sở dữ liệu. Dòng 9-14 — tạo đối tượng thông qua trình tạo. Nếu đối tượng đến có một trường, hãy đặt nó. Nếu không, chúng tôi sẽ để lại những gì có trong cơ sở dữ liệu. Bây giờ hãy nhìn vào thử nghiệm của chúng tôi:

@RunWith(MockitoJUnitRunner.class)
public class RobotServiceImplTest {
   @Mock
   private RobotDAO robotDAO;

   private RobotServiceImpl robotService;

   private static Robot testRobot;

   @BeforeClass
   public static void prepareTestData() {
       testRobot = Robot
               .builder()
               .id(123L)
               .name("testRobotMolly")
               .cpu("Intel Core i7-9700K")
               .producer("China")
               .build();
   }

   @Before
   public void init() {
       robotService = new RobotServiceImpl(robotDAO);
   }
Dòng 1 - Người chạy của chúng tôi. Dòng 4 — chúng tôi cách ly dịch vụ khỏi lớp DAO bằng cách thay thế một bản mô phỏng. Dòng 11 - chúng tôi đặt một thực thể thử nghiệm (thực thể mà chúng tôi sẽ sử dụng làm vật thí nghiệm) cho lớp. Dòng 22 - chúng tôi đặt đối tượng dịch vụ, đây là đối tượng chúng tôi sẽ kiểm tra.

@Test
public void updateTest() {
   when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
   when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
   Robot robotForUpdate = Robot
           .builder()
           .name("Vally")
           .cpu("AMD Ryzen 7 2700X")
           .build();

   Robot resultRobot = robotService.update(123L, robotForUpdate);

   assertNotNull(resultRobot);
   assertSame(resultRobot.getId(),testRobot.getId());
   assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
   assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
   assertEquals(resultRobot.getProducer(),testRobot.getProducer());
}
Ở đây chúng ta thấy rằng bài kiểm tra có ba phần rõ ràng: Dòng 3-9 — xác định đồ đạc. Dòng 11 - thực thi mã đang được kiểm tra. Dòng 13-17 — kiểm tra kết quả. Chi tiết hơn: Dòng 3-4 — thiết lập hành vi cho mô hình DAO. Dòng 5 — đặt phiên bản mà chúng tôi sẽ cập nhật dựa trên tiêu chuẩn của chúng tôi. Dòng 11 - sử dụng phương thức và lấy ví dụ kết quả. Dòng 13 — kiểm tra xem nó có rỗng không. Dòng 14 — so sánh ID của kết quả và các đối số phương thức đã cho. Dòng 15 — kiểm tra xem tên đã được cập nhật chưa. Dòng 16 — xem kết quả CPU. Dòng 17 — chúng tôi không chỉ định trường này trong trường hợp này, vì vậy trường sẽ giữ nguyên. Chúng tôi kiểm tra điều kiện đó ở đây. Hãy chạy nó:Tất cả về kiểm thử đơn vị: kỹ thuật, khái niệm, thực hành - 6Bài kiểm tra có màu xanh lá cây! Chúng ta có thể thở phào nhẹ nhõm :) Tóm lại, thử nghiệm cải thiện chất lượng của mã và làm cho quá trình phát triển trở nên linh hoạt và đáng tin cậy hơn. Hãy tưởng tượng cần bao nhiêu nỗ lực để thiết kế lại phần mềm liên quan đến hàng trăm tệp lớp. Khi chúng tôi có các bài kiểm tra đơn vị được viết cho tất cả các lớp này, chúng tôi có thể tự tin cấu trúc lại. Và quan trọng nhất, nó giúp chúng ta dễ dàng tìm ra lỗi trong quá trình phát triển. Các bạn và các cô gái, đó là tất cả những gì tôi có ngày hôm nay. Hãy cho tôi một lượt thích và để lại nhận xét :)
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION