cpp
exercises
exercises.cpp⚙️cpp
/**
* Unit Testing Exercises with Google Test
*
* Practice writing unit tests for various C++ classes and functions.
* Each exercise provides code to test - write comprehensive test suites.
*
* To compile with Google Test:
* g++ -std=c++17 exercises.cpp -lgtest -lgtest_main -pthread -o test_exercises
*/
#include <iostream>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <cmath>
#include <stdexcept>
#include <memory>
#include <algorithm>
#include <numeric>
#include <map>
// Uncomment when Google Test is available:
// #include <gtest/gtest.h>
using std::cout;
using std::endl;
// ============================================================================
// EXERCISE 1: Temperature Converter
// Write tests for this temperature conversion class
// ============================================================================
class TemperatureConverter {
public:
static double celsiusToFahrenheit(double celsius) {
return celsius * 9.0 / 5.0 + 32.0;
}
static double fahrenheitToCelsius(double fahrenheit) {
return (fahrenheit - 32.0) * 5.0 / 9.0;
}
static double celsiusToKelvin(double celsius) {
if (celsius < -273.15) {
throw std::domain_error("Temperature below absolute zero");
}
return celsius + 273.15;
}
static double kelvinToCelsius(double kelvin) {
if (kelvin < 0) {
throw std::domain_error("Kelvin cannot be negative");
}
return kelvin - 273.15;
}
};
/*
* TODO: Write tests for TemperatureConverter
*
* Test cases to consider:
* 1. Basic conversions (0°C = 32°F, 100°C = 212°F)
* 2. Negative temperatures
* 3. Freezing and boiling points
* 4. Absolute zero handling
* 5. Round-trip conversions (C -> F -> C should be original)
* 6. Exception handling for invalid temperatures
*/
/*
TEST(TemperatureConverterTest, CelsiusToFahrenheitFreezing) {
// TODO: Test that 0°C = 32°F
}
TEST(TemperatureConverterTest, CelsiusToFahrenheitBoiling) {
// TODO: Test that 100°C = 212°F
}
TEST(TemperatureConverterTest, FahrenheitToCelsius) {
// TODO: Test reverse conversion
}
TEST(TemperatureConverterTest, RoundTripConversion) {
// TODO: Verify C -> F -> C returns original value
}
TEST(TemperatureConverterTest, AbsoluteZeroException) {
// TODO: Test exception for temperatures below -273.15°C
}
TEST(TemperatureConverterTest, KelvinConversions) {
// TODO: Test Kelvin conversions
}
*/
// ============================================================================
// EXERCISE 2: Stack-based Calculator
// Write tests for this postfix calculator
// ============================================================================
class PostfixCalculator {
public:
double evaluate(const std::vector<std::string>& tokens) {
std::stack<double> stack;
for (const auto& token : tokens) {
if (isOperator(token)) {
if (stack.size() < 2) {
throw std::runtime_error("Invalid expression: insufficient operands");
}
double b = stack.top(); stack.pop();
double a = stack.top(); stack.pop();
stack.push(applyOperator(token, a, b));
} else {
try {
stack.push(std::stod(token));
} catch (const std::exception&) {
throw std::runtime_error("Invalid token: " + token);
}
}
}
if (stack.size() != 1) {
throw std::runtime_error("Invalid expression: too many operands");
}
return stack.top();
}
private:
bool isOperator(const std::string& s) const {
return s == "+" || s == "-" || s == "*" || s == "/" || s == "^";
}
double applyOperator(const std::string& op, double a, double b) const {
if (op == "+") return a + b;
if (op == "-") return a - b;
if (op == "*") return a * b;
if (op == "/") {
if (b == 0) throw std::runtime_error("Division by zero");
return a / b;
}
if (op == "^") return std::pow(a, b);
throw std::runtime_error("Unknown operator: " + op);
}
};
/*
* TODO: Write tests for PostfixCalculator
*
* Test expressions:
* 1. "3 4 +" = 7
* 2. "5 2 -" = 3
* 3. "3 4 * 5 +" = 17 (3*4+5)
* 4. "10 2 /" = 5
* 5. "2 3 ^" = 8
* 6. Division by zero
* 7. Invalid expressions
* 8. Empty expression
*/
/*
class PostfixCalculatorTest : public ::testing::Test {
protected:
PostfixCalculator calc;
};
TEST_F(PostfixCalculatorTest, SimpleAddition) {
// TODO: Test "3 4 +" = 7
}
TEST_F(PostfixCalculatorTest, ComplexExpression) {
// TODO: Test "3 4 * 5 +"
}
TEST_F(PostfixCalculatorTest, DivisionByZero) {
// TODO: Test exception handling
}
TEST_F(PostfixCalculatorTest, InvalidToken) {
// TODO: Test with invalid tokens
}
*/
// ============================================================================
// EXERCISE 3: Password Validator
// Write comprehensive tests for password validation
// ============================================================================
class PasswordValidator {
public:
struct ValidationResult {
bool isValid;
std::vector<std::string> errors;
};
ValidationResult validate(const std::string& password) const {
ValidationResult result{true, {}};
if (password.length() < minLength_) {
result.isValid = false;
result.errors.push_back("Password too short (min " + std::to_string(minLength_) + ")");
}
if (password.length() > maxLength_) {
result.isValid = false;
result.errors.push_back("Password too long (max " + std::to_string(maxLength_) + ")");
}
if (requireUppercase_ && !hasUppercase(password)) {
result.isValid = false;
result.errors.push_back("Password must contain uppercase letter");
}
if (requireLowercase_ && !hasLowercase(password)) {
result.isValid = false;
result.errors.push_back("Password must contain lowercase letter");
}
if (requireDigit_ && !hasDigit(password)) {
result.isValid = false;
result.errors.push_back("Password must contain digit");
}
if (requireSpecial_ && !hasSpecial(password)) {
result.isValid = false;
result.errors.push_back("Password must contain special character");
}
return result;
}
// Builder pattern for configuration
PasswordValidator& setMinLength(size_t len) { minLength_ = len; return *this; }
PasswordValidator& setMaxLength(size_t len) { maxLength_ = len; return *this; }
PasswordValidator& requireUppercase(bool req) { requireUppercase_ = req; return *this; }
PasswordValidator& requireLowercase(bool req) { requireLowercase_ = req; return *this; }
PasswordValidator& requireDigit(bool req) { requireDigit_ = req; return *this; }
PasswordValidator& requireSpecial(bool req) { requireSpecial_ = req; return *this; }
private:
size_t minLength_ = 8;
size_t maxLength_ = 128;
bool requireUppercase_ = true;
bool requireLowercase_ = true;
bool requireDigit_ = true;
bool requireSpecial_ = false;
bool hasUppercase(const std::string& s) const {
return std::any_of(s.begin(), s.end(), ::isupper);
}
bool hasLowercase(const std::string& s) const {
return std::any_of(s.begin(), s.end(), ::islower);
}
bool hasDigit(const std::string& s) const {
return std::any_of(s.begin(), s.end(), ::isdigit);
}
bool hasSpecial(const std::string& s) const {
return std::any_of(s.begin(), s.end(), [](char c) {
return !std::isalnum(c);
});
}
};
/*
* TODO: Write tests for PasswordValidator
*
* Test cases:
* 1. Valid password with all requirements
* 2. Too short password
* 3. Too long password
* 4. Missing uppercase
* 5. Missing lowercase
* 6. Missing digit
* 7. Multiple errors at once
* 8. Custom configuration
* 9. Edge cases (empty, only spaces, etc.)
*/
// ============================================================================
// EXERCISE 4: Shopping Cart
// Write tests for a shopping cart system
// ============================================================================
struct Product {
std::string id;
std::string name;
double price;
int stock;
};
class ShoppingCart {
public:
void addItem(const Product& product, int quantity) {
if (quantity <= 0) {
throw std::invalid_argument("Quantity must be positive");
}
if (quantity > product.stock) {
throw std::runtime_error("Insufficient stock");
}
auto it = items_.find(product.id);
if (it != items_.end()) {
it->second.quantity += quantity;
} else {
items_[product.id] = {product, quantity};
}
}
void removeItem(const std::string& productId) {
auto it = items_.find(productId);
if (it == items_.end()) {
throw std::runtime_error("Product not in cart");
}
items_.erase(it);
}
void updateQuantity(const std::string& productId, int newQuantity) {
if (newQuantity <= 0) {
removeItem(productId);
return;
}
auto it = items_.find(productId);
if (it == items_.end()) {
throw std::runtime_error("Product not in cart");
}
it->second.quantity = newQuantity;
}
double getSubtotal() const {
double total = 0;
for (const auto& [id, item] : items_) {
total += item.product.price * item.quantity;
}
return total;
}
double getTotal(double taxRate = 0.0, double discount = 0.0) const {
double subtotal = getSubtotal();
double discounted = subtotal * (1.0 - discount);
return discounted * (1.0 + taxRate);
}
int getItemCount() const {
int count = 0;
for (const auto& [id, item] : items_) {
count += item.quantity;
}
return count;
}
bool isEmpty() const { return items_.empty(); }
void clear() { items_.clear(); }
private:
struct CartItem {
Product product;
int quantity;
};
std::map<std::string, CartItem> items_;
};
/*
* TODO: Write tests for ShoppingCart
*
* Test cases:
* 1. Empty cart state
* 2. Add single item
* 3. Add multiple items
* 4. Add same item multiple times (quantity accumulation)
* 5. Remove item
* 6. Update quantity
* 7. Subtotal calculation
* 8. Total with tax
* 9. Total with discount
* 10. Total with tax AND discount
* 11. Exception handling (insufficient stock, negative quantity)
*/
/*
class ShoppingCartTest : public ::testing::Test {
protected:
ShoppingCart cart;
Product apple{"P001", "Apple", 1.50, 100};
Product banana{"P002", "Banana", 0.75, 50};
Product laptop{"P003", "Laptop", 999.99, 5};
void SetUp() override {
cart.clear();
}
};
TEST_F(ShoppingCartTest, StartsEmpty) {
// TODO: Verify empty cart state
}
TEST_F(ShoppingCartTest, AddItem) {
// TODO: Test adding items
}
// ... more tests
*/
// ============================================================================
// EXERCISE 5: Linked List
// Write tests for a custom linked list implementation
// ============================================================================
template<typename T>
class LinkedList {
public:
LinkedList() = default;
~LinkedList() {
clear();
}
// Copy operations
LinkedList(const LinkedList& other) {
for (Node* current = other.head_; current; current = current->next) {
push_back(current->data);
}
}
LinkedList& operator=(LinkedList other) {
swap(*this, other);
return *this;
}
// Move operations
LinkedList(LinkedList&& other) noexcept
: head_(other.head_), tail_(other.tail_), size_(other.size_) {
other.head_ = other.tail_ = nullptr;
other.size_ = 0;
}
friend void swap(LinkedList& a, LinkedList& b) noexcept {
using std::swap;
swap(a.head_, b.head_);
swap(a.tail_, b.tail_);
swap(a.size_, b.size_);
}
void push_front(const T& value) {
Node* node = new Node{value, head_};
head_ = node;
if (!tail_) tail_ = head_;
++size_;
}
void push_back(const T& value) {
Node* node = new Node{value, nullptr};
if (tail_) {
tail_->next = node;
} else {
head_ = node;
}
tail_ = node;
++size_;
}
void pop_front() {
if (!head_) throw std::runtime_error("List is empty");
Node* temp = head_;
head_ = head_->next;
if (!head_) tail_ = nullptr;
delete temp;
--size_;
}
T& front() {
if (!head_) throw std::runtime_error("List is empty");
return head_->data;
}
const T& front() const {
if (!head_) throw std::runtime_error("List is empty");
return head_->data;
}
T& back() {
if (!tail_) throw std::runtime_error("List is empty");
return tail_->data;
}
bool contains(const T& value) const {
for (Node* current = head_; current; current = current->next) {
if (current->data == value) return true;
}
return false;
}
size_t size() const { return size_; }
bool empty() const { return size_ == 0; }
void clear() {
while (head_) {
Node* temp = head_;
head_ = head_->next;
delete temp;
}
tail_ = nullptr;
size_ = 0;
}
void reverse() {
Node* prev = nullptr;
Node* current = head_;
tail_ = head_;
while (current) {
Node* next = current->next;
current->next = prev;
prev = current;
current = next;
}
head_ = prev;
}
private:
struct Node {
T data;
Node* next;
};
Node* head_ = nullptr;
Node* tail_ = nullptr;
size_t size_ = 0;
};
/*
* TODO: Write tests for LinkedList
*
* Test cases:
* 1. Empty list state
* 2. push_front and push_back
* 3. pop_front
* 4. front() and back() access
* 5. contains() method
* 6. size() and empty()
* 7. clear()
* 8. reverse()
* 9. Copy constructor
* 10. Move constructor
* 11. Exception handling for empty list operations
* 12. Memory management (no leaks with valgrind)
*/
// ============================================================================
// EXERCISE 6: Priority Queue
// Write tests for a max-heap based priority queue
// ============================================================================
template<typename T, typename Compare = std::less<T>>
class PriorityQueue {
public:
void push(const T& value) {
data_.push_back(value);
heapifyUp(data_.size() - 1);
}
void pop() {
if (empty()) {
throw std::runtime_error("Queue is empty");
}
std::swap(data_.front(), data_.back());
data_.pop_back();
if (!empty()) {
heapifyDown(0);
}
}
const T& top() const {
if (empty()) {
throw std::runtime_error("Queue is empty");
}
return data_.front();
}
size_t size() const { return data_.size(); }
bool empty() const { return data_.empty(); }
private:
std::vector<T> data_;
Compare comp_;
void heapifyUp(size_t index) {
while (index > 0) {
size_t parent = (index - 1) / 2;
if (!comp_(data_[parent], data_[index])) break;
std::swap(data_[parent], data_[index]);
index = parent;
}
}
void heapifyDown(size_t index) {
size_t largest = index;
size_t left = 2 * index + 1;
size_t right = 2 * index + 2;
if (left < data_.size() && comp_(data_[largest], data_[left])) {
largest = left;
}
if (right < data_.size() && comp_(data_[largest], data_[right])) {
largest = right;
}
if (largest != index) {
std::swap(data_[index], data_[largest]);
heapifyDown(largest);
}
}
};
/*
* TODO: Write tests for PriorityQueue
*
* Test cases:
* 1. Empty queue
* 2. Single element
* 3. Multiple elements (max-heap property)
* 4. pop() removes largest
* 5. Insert in ascending order
* 6. Insert in descending order
* 7. Insert duplicates
* 8. Exception on empty pop/top
* 9. Custom comparator (min-heap)
*/
// ============================================================================
// EXERCISE 7: Rate Limiter
// Write tests for a token bucket rate limiter
// ============================================================================
#include <chrono>
class RateLimiter {
public:
RateLimiter(int maxTokens, int refillRate)
: maxTokens_(maxTokens),
tokens_(maxTokens),
refillRate_(refillRate),
lastRefill_(std::chrono::steady_clock::now()) {}
bool tryAcquire(int tokens = 1) {
refill();
if (tokens_ >= tokens) {
tokens_ -= tokens;
return true;
}
return false;
}
int availableTokens() const {
return tokens_;
}
// For testing: manually set tokens
void setTokens(int tokens) {
tokens_ = std::min(tokens, maxTokens_);
}
private:
void refill() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - lastRefill_).count();
if (elapsed > 0) {
tokens_ = std::min(maxTokens_, tokens_ + static_cast<int>(elapsed * refillRate_));
lastRefill_ = now;
}
}
int maxTokens_;
int tokens_;
int refillRate_; // tokens per second
std::chrono::steady_clock::time_point lastRefill_;
};
/*
* TODO: Write tests for RateLimiter
*
* Test cases:
* 1. Initial state has max tokens
* 2. Acquire single token
* 3. Acquire multiple tokens
* 4. Fail when not enough tokens
* 5. Tokens don't exceed max
* 6. Partial token acquisition
*/
// ============================================================================
// EXERCISE 8: Write Parameterized Tests
// Create parameterized tests for mathematical functions
// ============================================================================
namespace MathFunctions {
int factorial(int n) {
if (n < 0) throw std::invalid_argument("Negative input");
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int fibonacci(int n) {
if (n < 0) throw std::invalid_argument("Negative input");
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int temp = a + b;
a = b;
b = temp;
}
return b;
}
bool isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
int gcd(int a, int b) {
if (a < 0) a = -a;
if (b < 0) b = -b;
while (b) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
}
/*
* TODO: Write parameterized tests
*
* Factorial tests:
* - {0, 1}, {1, 1}, {2, 2}, {3, 6}, {4, 24}, {5, 120}, {10, 3628800}
*
* Fibonacci tests:
* - {0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}, {10, 55}
*
* Prime tests:
* - Primes: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
* - Non-primes: 0, 1, 4, 6, 8, 9, 10, 15, 21
*
* GCD tests:
* - {12, 18, 6}, {48, 18, 6}, {17, 5, 1}, {100, 10, 10}
*/
/*
class FactorialTest : public ::testing::TestWithParam<std::pair<int, int>> {};
TEST_P(FactorialTest, ReturnsCorrectResult) {
auto [input, expected] = GetParam();
EXPECT_EQ(MathFunctions::factorial(input), expected);
}
INSTANTIATE_TEST_SUITE_P(
FactorialValues,
FactorialTest,
::testing::Values(
// TODO: Add test values
)
);
*/
// ============================================================================
// MAIN - Demo without Google Test
// ============================================================================
int main() {
cout << "╔══════════════════════════════════════════════════════════════╗" << endl;
cout << "║ UNIT TESTING EXERCISES ║" << endl;
cout << "╚══════════════════════════════════════════════════════════════╝" << endl;
cout << "\nThis file contains code to be tested with Google Test." << endl;
cout << "Uncomment the test code and implement the TODO sections." << endl;
cout << "\n=== Demo: TemperatureConverter ===" << endl;
cout << " 0°C = " << TemperatureConverter::celsiusToFahrenheit(0) << "°F" << endl;
cout << " 100°C = " << TemperatureConverter::celsiusToFahrenheit(100) << "°F" << endl;
cout << "\n=== Demo: PostfixCalculator ===" << endl;
PostfixCalculator calc;
cout << " '3 4 +' = " << calc.evaluate({"3", "4", "+"}) << endl;
cout << " '10 2 /' = " << calc.evaluate({"10", "2", "/"}) << endl;
cout << "\n=== Demo: PasswordValidator ===" << endl;
PasswordValidator validator;
auto result = validator.validate("StrongPass123");
cout << " 'StrongPass123' is " << (result.isValid ? "valid" : "invalid") << endl;
cout << "\n=== Demo: ShoppingCart ===" << endl;
ShoppingCart cart;
Product apple{"P001", "Apple", 1.50, 100};
cart.addItem(apple, 5);
cout << " 5 apples @ $1.50 = $" << cart.getSubtotal() << endl;
cout << "\n=== Demo: LinkedList ===" << endl;
LinkedList<int> list;
list.push_back(1);
list.push_back(2);
list.push_back(3);
cout << " List size: " << list.size() << endl;
cout << " Front: " << list.front() << ", Back: " << list.back() << endl;
cout << "\n=== Demo: MathFunctions ===" << endl;
cout << " factorial(5) = " << MathFunctions::factorial(5) << endl;
cout << " fibonacci(10) = " << MathFunctions::fibonacci(10) << endl;
cout << " isPrime(17) = " << MathFunctions::isPrime(17) << endl;
cout << " gcd(48, 18) = " << MathFunctions::gcd(48, 18) << endl;
cout << "\n═══════════════════════════════════════════════════════════════" << endl;
cout << "Challenge: Write comprehensive tests for each class above!" << endl;
cout << "Goal: 100% code coverage with meaningful assertions" << endl;
return 0;
}