Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ set(PROJECT_NAME_TEST ${PROJECT_NAME}_unit_test) # name for the unit-test execut
# ----------------------------------------------------------------------------------------
# Compiler and language configuration
# ----------------------------------------------------------------------------------------
# Require C++17 for GoogleTest and modern C++ features
# Require at least C++17 for GoogleTest and modern C++ features
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Expand Down Expand Up @@ -124,6 +124,7 @@ set(APP_SOURCES
"src/core/filehandle/IOStream.cpp"
"src/core/filehandle/StringStream.cpp"
"src/core/filehandle/FileIO.cpp"
"src/core/filehandle/Directory.cpp"
## Container
"src/core/datatypes/container/sequence/Array.cpp"
"src/core/datatypes/container/sequence/Vector.cpp"
Expand Down Expand Up @@ -175,9 +176,10 @@ target_include_directories(${PROJECT_NAME_TEST}
PRIVATE ${APP_HEADERS} # PRIVATE for internal use within a target,
)

# Link GoogleTest to the test executable
# Link GoogleTest, GoogleMock to the test executable
target_link_libraries(${PROJECT_NAME_TEST}
GTest::gtest_main
GTest::gmock_main
)

# Register tests with CTest
Expand Down
45 changes: 45 additions & 0 deletions src/core/filehandle/Directory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <filesystem> // for creating dir
#include <iostream>
#include <string>
namespace {
void run() {
std::filesystem::path path1{"p1"};
std::filesystem::path path2{"p1/p2"};

std::cout << "[1] Try create_directory(p1/p2)\n";
try {
if (!std::filesystem::create_directory(path2)) {
std::cout << " Failed: parent directory does not exist\n";
}
} catch (const std::filesystem::filesystem_error& e) {
std::cout << " Exception: " << e.what() << '\n';
}

std::cout << "[2] Create parent directories: p1\n";
std::filesystem::create_directories(path1);

std::cout << "[3] Try create_directory(p1/p2) again\n";
if (std::filesystem::create_directory(path2)) {
std::cout << " Success\n";
} else {
std::cout << " Failed: directory already exists\n";
}

std::cout << "[4] Cleanup\n";
if (std::filesystem::exists(path2)) {
std::filesystem::remove(path2);
std::cout << " Removed p1/p2\n";
}

if (std::filesystem::exists(path1)) {
std::filesystem::remove(path1);
std::cout << " Removed p1\n";
}
}
} // namespace

struct DirectoryRunner {
DirectoryRunner() { run(); }
};

static DirectoryRunner autoRunner;
Empty file.
Empty file.
49 changes: 49 additions & 0 deletions tests/DeleteMeTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

#include <gtest/gtest.h>

/**
* ASSERT_* versions generate fatal failures when they fail, and abort the current function
* EXPECT_* are preferred, as they allow more than one failure to be reported in a test.
*/
TEST(AddTest, ValidNumbers) {
DeleteMe calc = DeleteMe();

Expand Down Expand Up @@ -44,6 +48,51 @@ TEST(DivTest, InvalidNumbers) {
ASSERT_EQ(0, calc.get_last_result());
}

TEST(DivTest, FailureMessage) {
// ASSERT_FALSE(true) << "FailureMessage: ASSERT_FALSE(true)";
}

#include <gmock/gmock.h>
#include "mock/MockTurtle.h"
#include "mock/Painter.h"
TEST(MockPainterTest, DrawSquare_CallsCorrectTurtleOperations) {
MockTurtle turtle;
Painter painter{turtle};

EXPECT_CALL(turtle, PenDown()).Times(1);

EXPECT_CALL(turtle, Forward(100)).Times(4);
EXPECT_CALL(turtle, Turn(90)).Times(4);

EXPECT_CALL(turtle, PenUp()).Times(1);

painter.DrawSquare(100);
}

#include "mock/FakeTurtle.h"
TEST(FakePainterTest, DrawSquare_ProducesSquarePath) {
FakeTurtle turtle;
Painter painter{turtle};

painter.DrawSquare(10);

const auto& path = turtle.Path();

ASSERT_EQ(path.size(), 8); // 4 lines → 8 points (start/end)

EXPECT_EQ(path[0], std::make_pair(0, 0));
EXPECT_EQ(path[1], std::make_pair(0, 10));

EXPECT_EQ(path[2], std::make_pair(0, 10));
EXPECT_EQ(path[3], std::make_pair(10, 10));

EXPECT_EQ(path[4], std::make_pair(10, 10));
EXPECT_EQ(path[5], std::make_pair(10, 0));

EXPECT_EQ(path[6], std::make_pair(10, 0));
EXPECT_EQ(path[7], std::make_pair(0, 0));
}

int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down
97 changes: 97 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# GoogleTest
- GoogleTest helps you write better C++ tests.
## 1. Basic Concepts
- **assertions:** the statememts that check whether a condition is true. The result can be:
- success
- nonfatal failure
- fatal failure
- **test suite:** that contains one or many tests.
- **text fixture:** the class contain test suite used for multiple tests to share common objects and subroutines.

## 2. Test Fixtures: Using the Same Data Configuration for Multiple Tests
- To create a fixture class:
- derive a class from `testing::Test` with protected body
- declare used objects
- write a default constructor or `Setup()` function to prepare the object for each test.
- write a default destructor or `TearDown()` function to release resources.
- use `TEST_F(TestFixtureClassName, TestName)` instead of `TEST()`

- e.g.
```cpp
#include "this/package/foo.h"

#include <gtest/gtest.h>

namespace my {
namespace project {
namespace {

// The fixture for testing class Foo.
class FooTest : public testing::Test {
protected:
// You can remove any or all of the following functions if their bodies would
// be empty.

FooTest() {
// You can do set-up work for each test here.
}

~FooTest() override {
// You can do clean-up work that doesn't throw exceptions here.
}

// If the constructor and destructor are not enough for setting up
// and cleaning up each test, you can define the following methods:

void SetUp() override {
// Code here will be called immediately after the constructor (right
// before each test).
}

void TearDown() override {
// Code here will be called immediately after each test (right
// before the destructor).
}

// Class members declared here can be used by all tests in the test suite
// for Foo.
};

// Tests that the Foo::Bar() method does Abc.
TEST_F(FooTest, MethodBarDoesAbc) {
const std::string input_filepath = "this/package/testdata/myinputfile.dat";
const std::string output_filepath = "this/package/testdata/myoutputfile.dat";
Foo f;
EXPECT_EQ(f.Bar(input_filepath, output_filepath), 0);
}

// Tests that Foo does Xyz.
TEST_F(FooTest, DoesXyz) {
// Exercises the Xyz feature of Foo.
}

} // namespace
} // namespace project
} // namespace my

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
```

## 3. gMock
- It's not feasible or wide to rely on real objects entirely when testing.
- A mocks object implements the same interface as a real object, but lets you specify at run time how it will be used and what it should do (which methods will be called? in which order? how many times? with what arguments? what will they return? etc).
- `Fake objects` vs `Mock objects`:
- Fake objects have working implementations, but usually take some shortcut. e.g. return a static or in-memory value instead of the actual computation.“what happened?”

- Mock objects are objects pre-programmed with expectations, which form a specification of the calls they are expected to receive. “how did it happen?”

- **gMock** is a library (sometimes we also call it a “framework” to make it sound cool) for creating mock classes and using them. It does to C++ what jMock/EasyMock does to Java (well, more or less).

>When using gMock,
+) first, you use some simple macros to describe the interface you want to mock, and they will expand to the implementation of your mock class;
+) next, you create some mock objects and specify its expectations and behavior using an intuitive syntax;
+) then you exercise code that uses the mock objects. gMock will catch any violation to the expectations as soon as it arises.
TBD https://google.github.io/googletest/gmock_for_dummies.html
59 changes: 59 additions & 0 deletions tests/mock/FakeTurtle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include <utility>
#include <vector>
#include "Turtle.h"

class FakeTurtle : public Turtle {
public:
FakeTurtle() = default;

void PenUp() override { penDown_ = false; }

void PenDown() override { penDown_ = true; }

void Forward(int distance) override {
if (penDown_) {
path_.emplace_back(x_, y_);
}

switch (direction_) {
case 0:
y_ += distance;
break; // north
case 90:
x_ += distance;
break; // east
case 180:
y_ -= distance;
break; // south
case 270:
x_ -= distance;
break; // west
}

if (penDown_) {
path_.emplace_back(x_, y_);
}
}

void Turn(int degrees) override { direction_ = (direction_ + degrees) % 360; }

void GoTo(int x, int y) override {
x_ = x;
y_ = y;
}

int GetX() const override { return x_; }
int GetY() const override { return y_; }

const std::vector<std::pair<int, int>>& Path() const { return path_; }

private:
int x_{0};
int y_{0};
int direction_{0}; // 0=north, 90=east, ...
bool penDown_{false};

std::vector<std::pair<int, int>> path_;
};
13 changes: 13 additions & 0 deletions tests/mock/MockTurtle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "Turtle.h" // mock test for this class
#include <gmock/gmock.h> // Brings in gMock.

class MockTurtle : public Turtle {
public:
MOCK_METHOD(void, PenUp, (), (override)); // the MOCK_METHOD macro will generate the definitions for APIs
MOCK_METHOD(void, PenDown, (), (override));
MOCK_METHOD(void, Forward, (int distance), (override));
MOCK_METHOD(void, Turn, (int degrees), (override));
MOCK_METHOD(void, GoTo, (int x, int y), (override));
MOCK_METHOD(int, GetX, (), (const, override));
MOCK_METHOD(int, GetY, (), (const, override));
};
19 changes: 19 additions & 0 deletions tests/mock/Painter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include "Turtle.h"

class Painter {
public:
explicit Painter(Turtle& turtle) : turtle_(turtle) {}

void DrawSquare(int size) {
turtle_.PenDown();
for (int i = 0; i < 4; ++i) {
turtle_.Forward(size);
turtle_.Turn(90);
}
turtle_.PenUp();
}

private:
Turtle& turtle_;
};
13 changes: 13 additions & 0 deletions tests/mock/Turtle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once

class Turtle {
public:
virtual ~Turtle() {}
virtual void PenUp() = 0;
virtual void PenDown() = 0;
virtual void Forward(int distance) = 0;
virtual void Turn(int degrees) = 0;
virtual void GoTo(int x, int y) = 0;
virtual int GetX() const = 0;
virtual int GetY() const = 0;
};