C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
129: Unit Testing with Google Test
I've been working on a small utility class called TemperatureConverter. It’s simple enough—converting Celsius to Fahrenheit and Kelvin—but I’ve found myself falling into a bad habit: writing a main() function, printing a few values to the console, and squinting at the screen to see if the numbers look "about right."
// My current "testing" method
int main() {
TemperatureConverter tc;
std::cout << "C to F (100): " << tc.celsiusToFahrenheit(100) << std::endl;
// I'm just hoping this prints 212...
return 0;
}
The pain of manual verification
The problem is that as soon as I add a new feature—say, a check to ensure we don't go below absolute zero—I have to manually re-check every single previous print statement. It's tedious, and frankly, it's where bugs slip through. I need a way to define what "correct" looks like once, and then just hit a button to verify it every time I change the code.
That's where Google Test (gTest) comes in. I've already linked the library to my project, so I don't want to bore you with the CMake boilerplate. Instead, let's just start writing a test. I'll create a new file specifically for tests so I don't clutter my production code.
#include
#include "TemperatureConverter.h"
TEST(TempConverterTest, HandlesBoilingPoint) {
TemperatureConverter tc;
EXPECT_EQ(tc.celsiusToFahrenheit(100.0), 212.0);
}
I'm using the TEST() macro here. The first argument is the "Test Suite" (a group of related tests) and the second is the specific "Test Case." When I run this, gTest tells me it passed. Great. But let's try something that actually fails to see how it behaves.
When the math doesn't quite line up
I'll add a test for the Kelvin conversion. I know that 0°C is 273.15K.
TEST(TempConverterTest, HandlesFreezingPointKelvin) {
TemperatureConverter tc;
EXPECT_EQ(tc.celsiusToKelvin(0.0), 273.15);
}
I run the tests, and... it fails. Wait, what? I checked my math, and the function is definitely returning 273.15. The error message says something like Expected: 273.15, Actual: 273.15000000000003. Ah, the classic floating-point precision trap. I forgot that EXPECT_EQ does a strict equality check, which is almost always a mistake with double or float.
I need to tell gTest, "These numbers are close enough." I'll swap EXPECT_EQ for EXPECT_NEAR, which lets me specify a tolerance.
TEST(TempConverterTest, HandlesFreezingPointKelvin) {
TemperatureConverter tc;
// 0.001 is plenty of precision for a thermometer
EXPECT_NEAR(tc.celsiusToKelvin(0.0), 273.15, 0.001);
}
That passes. Now we're actually testing the logic, not the quirks of IEEE 754 floating-point representation.
Catching the edge cases
Now for the real reason I'm doing this. I want to make sure my converter throws an exception if someone tries to set a temperature below absolute zero (-273.15°C). I'll add a guard clause to my actual TemperatureConverter class first:
double celsiusToFahrenheit(double c) {
if (c < -273.15) throw std::invalid_argument("Below absolute zero!");
return (c * 9.0 / 5.0) + 32.0;
}
Now, how do I test that an exception actually happens? I can't just call the function, or the test runner will crash. gTest has a specific macro for this: EXPECT_THROW.
TEST(TempConverterTest, ThrowsOnAbsoluteZero) {
TemperatureConverter tc;
EXPECT_THROW(tc.celsiusToFahrenheit(-300.0), std::invalid_argument);
}
I love this part of the process. Instead of me manually passing -300 into the program and watching it crash to see if the error message is correct, I've codified the failure. If some future version of me (or a teammate) accidentally removes that guard clause to "optimize" the code, this test will immediately turn red and yell at us.
EXPECT_EQ: Use for integers or booleans.EXPECT_NEAR: Use for floating point numbers.EXPECT_THROW: Use to verify your error handling works.
📋 Practical Task
Implementing a Robust StockPortfolio Validator
You are building a StockPortfolio class that tracks the total value of a user's holdings. The class has a method double calculateTotalValue(double shares, double pricePerShare).
Your task is to write a Google Test suite that ensures the following requirements are met:
- Positive Calculation: Verify that 10 shares at $150.50 equals $1505.00. (Use
EXPECT_NEAR). - Zero Value: Verify that 0 shares results in a total value of 0.0.
- Input Validation: The method should throw a
std::domain_errorif either thesharesorpricePerShareis negative, as you cannot have negative shares or a negative stock price.
Write the StockPortfolio implementation and the corresponding gTest cases to prove the logic is sound.
There are no comments for now.