Giao diện
📚 GTest Basics Foundation
Master the fundamentals: ASSERT vs EXPECT, Test Fixtures, và tổ chức test suite như một QA Engineer chuyên nghiệp.
ASSERT vs EXPECT — The Critical Difference
┌─────────────────────────────────────────────────────────────────────────┐
│ ASSERT vs EXPECT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ASSERT_*: Fatal failure │
│ ────────────────────── │
│ • Test STOPS immediately on failure │
│ • Use when continuing makes no sense │
│ • Example: ASSERT_NE(ptr, nullptr) — can't deref null │
│ │
│ EXPECT_*: Non-fatal failure │
│ ────────────────────────── │
│ • Test CONTINUES after failure │
│ • Collects all failures in one run │
│ • Use for independent checks │
│ │
│ Rule of Thumb: │
│ • Use ASSERT for preconditions │
│ • Use EXPECT for the actual assertions │
│ │
└─────────────────────────────────────────────────────────────────────────┘Example
cpp
TEST(PointerTest, ValidPointer) {
auto* ptr = GetUserData(user_id);
// ASSERT: Nếu null thì không thể tiếp tục
ASSERT_NE(ptr, nullptr) << "User not found";
// EXPECT: Những check sau đây độc lập
EXPECT_EQ(ptr->name, "John");
EXPECT_EQ(ptr->age, 30);
EXPECT_TRUE(ptr->is_active);
// Nếu name sai, vẫn check age và is_active
}Assertion Macros Reference
Boolean Assertions
cpp
ASSERT_TRUE(condition); // condition == true
ASSERT_FALSE(condition); // condition == false
EXPECT_TRUE(IsValid(x));
EXPECT_FALSE(IsEmpty(list));Comparison Assertions
cpp
// Equality
EXPECT_EQ(val1, val2); // val1 == val2
EXPECT_NE(val1, val2); // val1 != val2
// Relational
EXPECT_LT(val1, val2); // val1 < val2
EXPECT_LE(val1, val2); // val1 <= val2
EXPECT_GT(val1, val2); // val1 > val2
EXPECT_GE(val1, val2); // val1 >= val2String Assertions
cpp
// C-string comparison
EXPECT_STREQ(str1, str2); // strcmp(str1, str2) == 0
EXPECT_STRNE(str1, str2); // strcmp(str1, str2) != 0
EXPECT_STRCASEEQ(str1, str2); // Case-insensitive equal
EXPECT_STRCASENE(str1, str2); // Case-insensitive not equal
// std::string uses EXPECT_EQ directly
std::string s1 = "hello";
std::string s2 = "hello";
EXPECT_EQ(s1, s2); // Works!Floating-Point Assertions
cpp
// ❌ NEVER use EXPECT_EQ for floats!
EXPECT_EQ(0.1 + 0.2, 0.3); // FAILS! (floating point error)
// ✅ Use EXPECT_FLOAT_EQ / EXPECT_DOUBLE_EQ
EXPECT_FLOAT_EQ(0.1f + 0.2f, 0.3f); // 4 ULP tolerance
EXPECT_DOUBLE_EQ(0.1 + 0.2, 0.3); // 4 ULP tolerance
// ✅ Or use EXPECT_NEAR for custom tolerance
EXPECT_NEAR(calculated, expected, 1e-6);Exception Assertions
cpp
// Expect specific exception type
EXPECT_THROW(ThrowingFunction(), std::runtime_error);
// Expect any exception
EXPECT_ANY_THROW(ThrowingFunction());
// Expect no exception
EXPECT_NO_THROW(SafeFunction());Custom Failure Messages
cpp
TEST(UserTest, ValidAge) {
User user = GetUser(123);
EXPECT_GE(user.age, 0)
<< "Age cannot be negative. Got: " << user.age;
EXPECT_LE(user.age, 150)
<< "Age seems unrealistic. Got: " << user.age
<< " for user: " << user.name;
}Output on failure:
[ FAILURE ] UserTest.ValidAge
Expected: user.age >= 0
Actual: -5
Age cannot be negative. Got: -5Test Fixtures — SetUp & TearDown
Vấn đề: Duplicate Setup Code
cpp
// ❌ Code duplication in every test
TEST(DatabaseTest, InsertUser) {
auto db = std::make_unique<Database>();
db->Connect("localhost", 5432);
db->Migrate();
// ... actual test ...
db->Disconnect();
}
TEST(DatabaseTest, DeleteUser) {
auto db = std::make_unique<Database>(); // Duplicate!
db->Connect("localhost", 5432); // Duplicate!
db->Migrate(); // Duplicate!
// ... actual test ...
db->Disconnect(); // Duplicate!
}Solution: Test Fixture
cpp
// ✅ Using Test Fixture
class DatabaseTest : public ::testing::Test {
protected:
// Runs BEFORE each test
void SetUp() override {
db_ = std::make_unique<Database>();
db_->Connect("localhost", 5432);
db_->Migrate();
}
// Runs AFTER each test
void TearDown() override {
db_->Disconnect();
}
std::unique_ptr<Database> db_;
};
// Use TEST_F instead of TEST
TEST_F(DatabaseTest, InsertUser) {
// db_ is already set up!
auto result = db_->Insert(User{"John", 30});
EXPECT_TRUE(result.ok());
}
TEST_F(DatabaseTest, DeleteUser) {
// Fresh db_ for each test!
db_->Insert(User{"Jane", 25});
auto result = db_->Delete("Jane");
EXPECT_TRUE(result.ok());
}┌─────────────────────────────────────────────────────────────────────────┐
│ TEST FIXTURE LIFECYCLE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ For each TEST_F: │
│ ───────────────── │
│ │
│ 1. Create new DatabaseTest object │
│ 2. Call SetUp() │
│ 3. Run test body │
│ 4. Call TearDown() │
│ 5. Destroy DatabaseTest object │
│ │
│ → Each test gets a FRESH fixture instance │
│ → Tests are completely ISOLATED │
│ │
└─────────────────────────────────────────────────────────────────────────┘Static SetUpTestSuite
Khi setup quá tốn kém (DB connection expensive):
cpp
class ExpensiveTest : public ::testing::Test {
protected:
// Once for entire test suite
static void SetUpTestSuite() {
shared_db_ = CreateExpensiveConnection();
}
static void TearDownTestSuite() {
shared_db_.reset();
}
// Per-test setup (lightweight)
void SetUp() override {
shared_db_->BeginTransaction();
}
void TearDown() override {
shared_db_->RollbackTransaction(); // Clean state
}
static std::shared_ptr<Database> shared_db_;
};
std::shared_ptr<Database> ExpensiveTest::shared_db_ = nullptr;Test Organization
Naming Convention
cpp
// Format: TEST(ClassName_MethodName, WhenCondition_ExpectedBehavior)
TEST(Calculator_Add, WhenBothPositive_ReturnsSum) { }
TEST(Calculator_Add, WhenOneNegative_ReturnsCorrectResult) { }
TEST(Calculator_Divide, WhenDivisorZero_ThrowsException) { }
// Or simpler format for small projects
TEST(CalculatorTest, AddPositiveNumbers) { }
TEST(CalculatorTest, DivideByZeroThrows) { }File Organization
tests/
├── unit/
│ ├── calculator_test.cpp
│ ├── user_service_test.cpp
│ └── payment_service_test.cpp
├── integration/
│ ├── database_integration_test.cpp
│ └── api_integration_test.cpp
└── fixtures/
└── test_data.hppDisabling Tests
cpp
// Temporarily disable a test
TEST(DISABLED_SlowTest, TakesTooLong) {
// Won't run, but shows in output as disabled
}
// Disable entire suite
class DISABLED_BrokenTests : public ::testing::Test { };
TEST_F(DISABLED_BrokenTests, WillNotRun) { }Test Filters (Command Line)
bash
# Run only specific tests
./tests --gtest_filter=CalculatorTest.*
# Run tests matching pattern
./tests --gtest_filter=*Add*
# Exclude tests
./tests --gtest_filter=-SlowTest.*
# Combine
./tests --gtest_filter=Fast*:-*Slow*
# List all tests without running
./tests --gtest_list_tests
# Repeat tests (for flakiness detection)
./tests --gtest_repeat=10
# Shuffle order (detect order dependencies)
./tests --gtest_shuffleBest Practices
┌─────────────────────────────────────────────────────────────────────────┐
│ GTEST BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ DO │
│ ───── │
│ • One logical assertion per test (keeps failures clear) │
│ • Use descriptive test names │
│ • Use fixtures for shared setup │
│ • Add failure messages to assertions │
│ • Test edge cases and error conditions │
│ │
│ ❌ DON'T │
│ ─────── │
│ • Don't use ASSERT when EXPECT works │
│ • Don't test multiple units in one test │
│ • Don't depend on test execution order │
│ • Don't leave DISABLED tests forever │
│ • Don't use sleep() in tests (flaky!) │
│ │
│ 💡 TIPS │
│ ──────── │
│ • Run with --gtest_shuffle to catch order dependencies │
│ • Run with --gtest_repeat=N to catch flaky tests │
│ • Use SCOPED_TRACE for loops │
│ │
└─────────────────────────────────────────────────────────────────────────┘SCOPED_TRACE for Loops
cpp
TEST(ArrayTest, AllPositive) {
std::vector<int> values = {1, 2, -3, 4};
for (size_t i = 0; i < values.size(); ++i) {
SCOPED_TRACE("Index: " + std::to_string(i));
EXPECT_GT(values[i], 0);
}
}
// Output shows WHICH iteration failed:
// [ FAILURE ] ArrayTest.AllPositive
// Index: 2
// Expected: values[i] > 0
// Actual: -3 <= 0