Xem thêm

Tự động hóa kiểm thử mã C++ với Frameworks - Phần 1

Huy Erick
Kiểm thử tự động là một phần không thể thiếu trong quy trình phát triển phần mềm hiện đại. Nó không chỉ giúp đảm bảo chất lượng của phần mềm mà còn giúp các nhà...

Automatisiertes Testing

Kiểm thử tự động là một phần không thể thiếu trong quy trình phát triển phần mềm hiện đại. Nó không chỉ giúp đảm bảo chất lượng của phần mềm mà còn giúp các nhà phát triển phát hiện và sửa lỗi và vấn đề sớm. Có nhiều loại kiểm thử khác nhau, ví dụ như kiểm thử đơn vị để kiểm tra một thành phần cụ thể và kiểm thử tích hợp để kiểm tra tương tác giữa nhiều thành phần. Đối với hầu hết các ngôn ngữ lập trình, cũng có các Frameworks giúp việc tạo và chạy kiểm thử trở nên dễ dàng, đồng nhất. C++ cũng không ngoại lệ.

Gtest và Gmock Frameworks

Các Frameworks gtest và gmock của Google ban đầu được phát triển trong các dự án riêng lẻ, nhưng hiện đã được hợp nhất thành một. gtest là một Test Framework cho C++, tạo ra môi trường kiểm thử và đảm bảo các ứng dụng được kiểm thử. gmock cho phép tạo các đối tượng Mock để kiểm thử xem một hàm có được gọi với các tham số dự kiến hay không. Trong phần đầu của bài viết này, chúng ta sẽ tìm hiểu sâu hơn về Framework gtest.

So sánh gtest và gmock với các Framework kiểm thử đơn vị C++ khác

Sự đa dạng của các Framework kiểm thử đơn vị C++ trên thị trường làm cho việc lựa chọn đúng trở nên khó khăn. Vì vậy, việc so sánh các Framework theo nhiều tiêu chí khác nhau trước khi lựa chọn là rất hữu ích. Ngay từ năm 2004, một số Framework như CppUnit hoặc Boost.Test đã được so sánh dựa trên các tiêu chí sau:

  • Đòi hỏi bao nhiêu công việc để tạo một kiểm thử đơn giản?
  • Có thể tổ chức các kiểm thử không?
  • Framework này được nhà phát hành vẫn hỗ trợ và phát triển không?
  • Có nhiều chọn lựa khẳng định hay không?
  • Việc tích hợp Framework vào dự án hiện tại đòi hỏi công việc nặng nhọc không?
  • Cung cấp các đầu ra khác nhau để xử lý kết quả kiểm thử không?

Những câu hỏi này sẽ được trả lời chi tiết trong bài viết này. Tuy nhiên, trước hết, chúng ta phải nhận thấy rằng gtest đáp ứng tất cả các yêu cầu đã nêu. Tuy nhiên, việc lựa chọn Framework vẫn phụ thuộc vào dự án cụ thể và phải được quyết định theo từng trường hợp.

Gtest

Bài viết này tập trung vào gtest, tuy nhiên, tất cả các khía cạnh chung đều áp dụng cho gmock vì chức năng của chúng đã được hợp nhất vào một thư viện.

Để mô tả gtest, phải ghi rõ cú pháp. gtest sử dụng TEST () hoặc Test để mô tả từng kiểm thử riêng lẻ và Test Case để nhóm các kiểm thử liên quan với nhau. Sử dụng Test Fixtures, một lớp chứa các hàm và dữ liệu có thể được tái sử dụng trong nhiều kiểm thử.

Thiết lập

Framework này có thể hoạt động trên nhiều nền tảng và với nhiều trình biên dịch khác nhau. Hỗ trợ các hệ thống bao gồm Windows, Mac OS X và Linux. Có thể tạo và liên kết gtest dưới dạng thư viện tĩnh và động. Việc tích hợp gtest vào dự án cũng trở nên dễ dàng khi sử dụng cmake. gtest có thể được xây dựng độc lập hoặc tích hợp vào các dự án khác.

add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
                 ${CMAKE_CURRENT_BINARY_DIR}/googletest-build
                 EXCLUDE_FROM_ALL)

Trước khi triển khai thực tế như đã mô tả ở trên, vẫn cần tải xuống mã nguồn từ GitHub. Có thể tích hợp mã nguồn trực tiếp hoặc tải nó dưới dạng phần phụ thuộc tự động.

Cấu trúc chung

Lý thuyết, việc xây dựng kiểm thử tương tự với cấu trúc của dự án sẽ làm cho việc tìm kiếm kiểm thử của một lớp cụ thể trở nên dễ dàng hơn cho các nhà phát triển khác. Điều này cũng giúp xác định nhanh chóng liệu một lớp có kiểm thử hay không. Các kiểm thử cần tài nguyên chung nên được viết trong một tệp riêng. Điều này giúp sử dụng đúng tài nguyên và chuẩn bị tại một vị trí.

Ví dụ kiểm thử

Một kiểm thử đơn giản được viết bằng một macro như sau:

TEST(TestCaseName, TestName) {
  // ... test body ...
}

TestCaseName và TestName có thể được đặt tùy ý. Tên kiểm thử có thể lặp lại trong nhiều test case, nhưng phải duy nhất trong mỗi test case.

Kiểm thử Fixture được sử dụng để chia sẻ tài nguyên giữa nhiều trường hợp kiểm thử. Nó có thể là một đối tượng giả được điền với dữ liệu kiểm thử hoặc một quá trình chuẩn bị phức tạp. Để sử dụng chức năng này, phải tạo một lớp chứa các hàm và thuộc tính được sử dụng trong macro kiểm thử.

#include 

class MyFixture : public testing::Test {
protected:
  void SetUp() override;
  bool myFunction();
};

void MyFixture::SetUp() {
  Test::SetUp();
  // ... my additional setup ...
}

TEST_F(MyFixture, TestName) {
  bool myBool = myFunction();
  // ... my additional testing ...
}

Trong đầu ra, hàm "myFunction" có quyền truy cập "protected". Tất cả các hàm và thuộc tính được gọi trong macro kiểm thử phải có khả năng nhìn thấy "protected" hoặc "public". SetUp () có thể sử dụng để thực hiện việc khởi tạo phức tạp và sẽ được chạy trước mỗi kiểm thử. Tương tự, "TearDown" có thể được triển khai để thực hiện các tác vụ khi kiểm thử kết thúc. Macro trong ví dụ thứ hai đã thay đổi so với ví dụ thứ nhất. TEST đã trở thành TEST_F để chia sẻ kiểm thử với Fixture. Tên Fixture có thể được chỉ định tự do, miễn là giống nhau trong lớp Fixture và trong Macro.

Kiểm tra (Assertions)

Assertion là một trong những tính năng quan trọng của mọi Framework kiểm thử. gtest cung cấp nhiều chức năng kiểm tra khác nhau để so sánh các giá trị hoặc đối tượng.

Có hai kiểu kiểm tra: Check fatal và non-fatal. Kiểm tra khiến kiểm thử kết thúc nếu so sánh sai được đánh giá là fatal. Check non-fatal cho phép kiểm thử tiếp tục sau khi được đánh giá sai, nhưng đánh dấu nó như thất bại.

Kiểm tra fatal bắt đầu bằng "ASSERT", non-fatal bắt đầu bằng "EXPECT". Nếu có thể, luôn kiểm tra non-fatal.

Dưới đây là một số ví dụ về việc sử dụng các kiểm tra:

EXPECT_TRUE(MyClass::myBoolFunc());
std::string expectedResult("this result");
EXPECT_STREQ(expectedResult.c_str(), MyClass::myStringFunc().c_str());
EXPECT_EQ(42, MyClass::myIntFunc());
EXPECT_GT(0, MyClass::myLongFunc());

Các macro trong ví dụ kiểm tra nhiều điều kiện khác nhau. Macro với "_STREQ" kiểm tra sự giống nhau của hai chuỗi, "_EQ" so sánh hai số và "_GT" kiểm tra xem số thứ nhất lớn hơn số thứ hai.

Kiểm tra Exception

Kể từ khi exception trở nên quan trọng hơn trong C++, gtest cũng hỗ trợ kiểm tra exception. Có hai loại assertion cho Exception: một cho trường hợp có exception và một cho trường hợp không. Kiểm tra exception cho phép theo dõi xem chương trình có bị crash không.

EXPECT_THROW(_sut->myThrowingFunc(), std::exception);
ASSERT_THROW(_sut->myThrowingFunc(), std::exception);

EXPECT_NO_THROW(_sut->myNotThrowingFunc());
ASSERT_NO_THROW(_sut->myNotThrowingFunc());

Trong (1), sự tồn tại của đối tượng được kiểm tra bằng ASSERT_NE. Tất nhiên, không thể cho phép kiểm tra tiếp tục nếu đối tượng không tồn tại. Tuy nhiên, kiểm tra tuổi được thực hiện bằng cách sử dụng EXPECT_EQ vì có thể kiểm tra các thuộc tính khác ngay cả khi tuổi không chính xác. Khi xảy ra lỗi trong các tài liệu của bạn, hãy gửi mã này kèm theo:

TEST_F(MyTestFixture, checkitemCount) {
  int itemCount = _sut->getItemCount();
  EXPECT_EQ(0, itemCount) << "Item count was not the expected size.";
}

Chỉ mục Death Tests

Chỉ mục Death Tests cho phép kiểm tra xem chương trình có crash không trong một trạng thái xác định. Trong C ++, việc sử dụng abort () để bảo vệ điều kiện trước đó thường được sử dụng trong các thư viện như st1.

Để sử dụng Chỉ mục Death Tests, phải xác định trạng thái của chương trình:

EXPECT_DEATH(_sut->myDeadlyFunction(), ".*this did happen.*")
1