GTest

[GTest] (3) Example

i5 2025. 1. 9. 02:05
반응형

 

Below is an example of how you might structure a Car class that “has-a” Tier (think of it like a “Tire,” but we’ll stick to the requested name). We’ll show:

  1. Tier.h / Tier.cpp – A simple Tier class.
  2. Car.h / Car.cpp – A Car class that depends on (owns) a Tier.
  3. CarAndTierTest.cpp – A GTest file demonstrating typical usage and testing of Car+Tier.
  4. MockTierTest.cpp – An example using Google Mock (gMock) to mock Tier in order to test Car in isolation.

 

 

Important: In typical scenarios, for mocking, you want to inject dependencies via pointers or references (dependency injection), rather than direct ownership. That allows Car to work with either a real Tier or a mock Tier. The example below demonstrates a pointer-based approach so that we can easily replace the real Tier with a mock.

 

1. Tier.h

#ifndef TIER_H
#define TIER_H

class Tier {
public:
    virtual ~Tier() = default;

    // Returns the current durability (0 to 100, for example).
    // 100 => brand new, 0 => completely worn out.
    virtual int getDurability() const = 0;

    // Reduces durability based on distance.
    // For example, each 1 unit of distance reduces durability by 1.
    virtual void wearDown(int distance) = 0;

    // Returns true if the tire is worn out (durability <= 0).
    virtual bool isWornOut() const = 0;
};

#endif // TIER_H



 

 

We make all methods virtual and provide a virtual destructor, so we can derive a real Tier implementation as well as a mock later on.

 

 

2. Tier.cpp (Optional)

If you are only declaring a pure virtual interface (as above), you might not have a corresponding Tier.cpp. Instead, you would implement a concrete Tier in a separate class, e.g. BasicTier, SportsTier, etc.

Example of a simple, concrete Tier implementation:

 

 

BasicTier.h

 

#ifndef BASICTIER_H
#define BASICTIER_H

#include "Tier.h"

class BasicTier : public Tier {
private:
    int durability;

public:
    // Start with full durability, e.g. 100
    BasicTier(int initialDurability = 100);

    int getDurability() const override;
    void wearDown(int distance) override;
    bool isWornOut() const override;
};

#endif // BASICTIER_H

 

 

 

 

BasicTier.cpp

#include "BasicTier.h"
#include <algorithm> // for std::max

BasicTier::BasicTier(int initialDurability)
    : durability(initialDurability)
{
}

int BasicTier::getDurability() const {
    return durability;
}

void BasicTier::wearDown(int distance) {
    // Each 1 unit of distance reduces durability by 1
    // Avoid going below zero
    durability = std::max(0, durability - distance);
}

bool BasicTier::isWornOut() const {
    return (durability <= 0);
}

 

 

 

3. Car.h

#ifndef CAR_H
#define CAR_H

#include <string>
#include "Tier.h"

class Car {
private:
    std::string make;
    std::string model;
    int year;
    double speed;
    Tier* tier; // Pointer to a Tier (could be BasicTier, MockTier, etc.)

public:
    // Constructor takes ownership or reference to an existing Tier.
    // For real ownership, you might want to store a unique_ptr<Tier>.
    // For this example, we keep a raw pointer for simplicity.
    Car(const std::string& make, 
        const std::string& model, 
        int year, 
        Tier* tier);

    ~Car(); // We'll delete the tier if we truly own it

    // Accessors
    std::string getMake() const;
    std::string getModel() const;
    int getYear() const;
    double getSpeed() const;
    Tier* getTier() const; // So we can test the Tier from outside

    // Car actions
    void accelerate(double amount, int distance);
    void brake(double amount);
};

#endif // CAR_H

 

 

 

 

Notes

  • Car stores a pointer to a Tier. In a real application, you might use std::unique_ptr<Tier> to express ownership more safely, or accept the pointer via dependency injection if you don’t own the Tier.
  • The accelerate() method takes both a speed increment (amount) and a distance parameter, which we’ll use to wear down the tier accordingly.

 

 

4. Car.cpp

#include "Car.h"
#include <algorithm> // for std::max

Car::Car(const std::string& make, 
         const std::string& model, 
         int year,
         Tier* tier)
    : make(make), 
      model(model), 
      year(year), 
      speed(0.0),
      tier(tier)
{
}

Car::~Car() {
    // If we truly own the Tier, we delete it here.
    // Otherwise, if the Tier is owned elsewhere, remove this.
    delete tier;
    tier = nullptr;
}

std::string Car::getMake() const {
    return make;
}

std::string Car::getModel() const {
    return model;
}

int Car::getYear() const {
    return year;
}

double Car::getSpeed() const {
    return speed;
}

Tier* Car::getTier() const {
    return tier;
}

void Car::accelerate(double amount, int distance) {
    // Only accelerate if the tier isn't worn out and amount is positive
    if (amount > 0 && tier && !tier->isWornOut()) {
        speed += amount;
        // Wear down the tier based on distance traveled
        tier->wearDown(distance);
    }
}

void Car::brake(double amount) {
    if (amount > 0) {
        speed -= amount;
        if (speed < 0) {
            speed = 0;
        }
    }
}

 

 

5. CarAndTierTest.cpp (Using the real BasicTier)

This test demonstrates how you can test the interaction between a real Tier (like BasicTier) and Car.

 

#include <gtest/gtest.h>
#include "Car.h"
#include "BasicTier.h"

// Basic test to check Car initialization
TEST(CarAndTierTest, CarConstructor) {
    // Create a BasicTier with 100 durability
    Tier* tier = new BasicTier(100);
    Car car("Toyota", "Corolla", 2022, tier);

    EXPECT_EQ(car.getMake(), "Toyota");
    EXPECT_EQ(car.getModel(), "Corolla");
    EXPECT_EQ(car.getYear(), 2022);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 0.0);

    // Tier should not be worn out initially
    EXPECT_FALSE(car.getTier()->isWornOut());
    EXPECT_EQ(car.getTier()->getDurability(), 100);
}

TEST(CarAndTierTest, AccelerateWearsDownTier) {
    // Start with a Tier of durability 50
    Tier* tier = new BasicTier(50);
    Car car("Honda", "Civic", 2023, tier);

    // Initially speed is 0
    EXPECT_DOUBLE_EQ(car.getSpeed(), 0.0);
    EXPECT_EQ(tier->getDurability(), 50);

    // Accelerate by 10, driving distance = 10
    car.accelerate(10.0, 10);
    // Speed should be 10
    EXPECT_DOUBLE_EQ(car.getSpeed(), 10.0);
    // Durability should go from 50 to 40
    EXPECT_EQ(tier->getDurability(), 40);

    // Accelerate more
    car.accelerate(5.0, 5);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 15.0);
    // Tier should wear from 40 to 35
    EXPECT_EQ(tier->getDurability(), 35);
}

TEST(CarAndTierTest, AccelerateDoesNothingIfTierWornOut) {
    // Start with a Tier that is nearly worn out
    Tier* tier = new BasicTier(1);
    Car car("Ford", "Focus", 2020, tier);

    // Accelerate with a distance of 10 => That wears the tier from 1 -> 0
    car.accelerate(10.0, 10);
    // Speed should now be 10
    EXPECT_DOUBLE_EQ(car.getSpeed(), 10.0);
    EXPECT_EQ(tier->getDurability(), 0);
    EXPECT_TRUE(tier->isWornOut());

    // Try accelerating again => tier is worn out, so should do nothing
    car.accelerate(20.0, 20);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 10.0); // no change
    EXPECT_EQ(tier->getDurability(), 0);    // no change
}

TEST(CarAndTierTest, BrakeReducesSpeed) {
    Tier* tier = new BasicTier(100);
    Car car("VW", "Golf", 2021, tier);

    // Let's accelerate to get some speed
    car.accelerate(20.0, 5);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 20.0);

    // Apply brakes
    car.brake(5.0);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 15.0);

    // Over-brake => speed to 0
    car.brake(100.0);
    EXPECT_DOUBLE_EQ(car.getSpeed(), 0.0);
}

 

 

 

 

How to Build and Run

  1. Make sure you link against Google Test.
  2. Include Car.cpp, BasicTier.cpp, and CarAndTierTest.cpp in your build.
  3. Run the resulting test executable (or ctest) to see all tests pass.

 

 

 

6. Mocking Tier with gMock (MockTierTest.cpp)

Here’s how you might mock Tier using Google Mock. This allows you to test Car in isolation, ensuring your tests focus on Car’s logic without relying on a real Tier implementation.

 

 

MockTier.h

#ifndef MOCKTIER_H
#define MOCKTIER_H

#include <gmock/gmock.h>
#include "Tier.h"

class MockTier : public Tier {
public:
    // We mock each virtual method from Tier
    MOCK_METHOD(int, getDurability, (), (const, override));
    MOCK_METHOD(void, wearDown, (int distance), (override));
    MOCK_METHOD(bool, isWornOut, (), (const, override));
};

#endif // MOCKTIER_H

 

 

MockTierTest.cpp

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "Car.h"
#include "MockTier.h"

using ::testing::Return;     // For stubbing return values
using ::testing::_;          // Wildcard for parameter matching
using ::testing::Exactly;    // For call count verification
using ::testing::InSequence; // Optional for call ordering

TEST(CarWithMockTierTest, AccelerateCallsWearDown) {
    // Create our mock Tier
    MockTier* mockTier = new MockTier();

    // We'll create a Car using the mock Tier
    Car car("Test", "MockCar", 2025, mockTier);

    // Expect isWornOut() to return false at first => meaning the tier is good
    EXPECT_CALL(*mockTier, isWornOut())
        .Times(1)
        .WillOnce(Return(false));

    // Then we expect wearDown(10) to be called exactly once
    EXPECT_CALL(*mockTier, wearDown(10))
        .Times(1);

    // Finally, we might check getDurability() or other calls as needed
    // but let's keep it simple for now

    // Now we run the code under test
    car.accelerate(5.0, 10);
    // If the tier is not worn out, accelerate() will call wearDown(10).
}

TEST(CarWithMockTierTest, NoAccelerateIfTierWornOut) {
    MockTier* mockTier = new MockTier();
    Car car("Test", "WornOutCar", 2025, mockTier);

    // If the tier is worn out, accelerate() should do nothing
    EXPECT_CALL(*mockTier, isWornOut())
        .Times(1)
        .WillOnce(Return(true));

    // So wearDown() should NOT be called at all
    EXPECT_CALL(*mockTier, wearDown(_)).Times(0);

    car.accelerate(10.0, 50);

    // Speed should remain 0 if we do nothing
    EXPECT_DOUBLE_EQ(car.getSpeed(), 0.0);
}

 

 

 

 

Explanation

  • We create a MockTier class deriving from Tier, using MOCK_METHOD macros from gMock.
  • In each test, we create MockTier on the heap, pass it to Car, and set up expectations using gMock.
  • EXPECT_CALL(*mockTier, <method>(...)) helps us verify how Car uses the Tier. For example:
    • isWornOut() might return true or false.
    • wearDown(distance) is called a specific number of times with specific parameters.

 

Note: Because Car’s constructor deletes the Tier in its destructor, our test typically should let Car go out of scope before the test ends. That ensures no memory leaks. Alternatively, you can store the tier as a std::shared_ptr or std::unique_ptr if your design calls for that.

 

 

 

 


7. Summary

  • Tier (interface / abstract class) – Defines the contract for any type of tier/tire.
  • BasicTier – A concrete implementation with simple durability logic.
  • Car – Owns a Tier*. Uses accelerate(distance) to wear down the tier.
  • CarAndTierTest – Tests Car using a real Tier implementation (e.g. BasicTier).
  • MockTierTest – Demonstrates how to mock the Tier using Google Mock to test Car in isolation.

With these patterns, you can expand functionality (e.g. multiple tires, different wearDown logic, etc.) while keeping your tests focused and maintainable. If you have any further questions, feel free to ask!





반응형