Applied Software Design
Published:
Code: CMake and Catch2
Managing projects without CMake using multiple header and source files; Building projects with CMake and Make; CMakeLists.txt; Unit Testing using Catch2; Writing Test Cases
Step 1: What is the output
#include <iostream>
#include <string>
std::string GenerateWelcomeMessage(const std::string userName){
std::string greeting = CreateGreeting(userName);
greeting += " Welcome to the class!";
return greeting;
}
std::string CreateGreeting(const std::string userName){
return "Hello, " + userName + "!";
}
int main() {
std::string userName = "ECE 3574";
std::cout << GenerateWelcomeMessage(userName) << "\n";
}
Solution 1
#include <iostream>
#include <string>
std::string CreateGreeting(const std::string userName){
return "Hello, " + userName + "!";
}
std::string GenerateWelcomeMessage(const std::string userName){
std::string greeting = CreateGreeting(userName);
greeting += " Welcome to the class!";
return greeting;
}
int main() {
std::string userName = "ECE 3574";
std::cout << GenerateWelcomeMessage(userName) << "\n";
}
Solution 2
#include <iostream>
#include <string>
std::string GenerateWelcomeMessage(const std::string userName);
std::string CreateGreeting(const std::string userName);
std::string GenerateWelcomeMessage(const std::string userName){
std::string greeting = CreateGreeting(userName);
greeting += " Welcome to the class!";
return greeting;
}
std::string CreateGreeting(const std::string userName){
return "Hello, " + userName + "!";
}
int main() {
std::string userName = "ECE 3574";
std::cout << GenerateWelcomeMessage(userName) << "\n";
}
Step 2
// greeting.h
#ifndef GREETING_H
#define GREETING_H
#include <string>
std::string GenerateWelcomeMessage(const std::string userName);
std::string CreateGreeting(const std::string userName);
#endif
//greeting.cpp
#include "greeting.h"
#include <iostream>
std::string GenerateWelcomeMessage(const std::string userName){
std::string greeting = CreateGreeting(userName);
greeting += " Welcome to the class!";
return greeting;
}
std::string CreateGreeting(const std::string userName){
return "Hello, " + userName + "!";
}
//main.cpp
#include <iostream>
#include <string>
#include "greeting.h"
int main() {
std::string userName = "ECE 3574";
std::cout << GenerateWelcomeMessage(userName) << "\n";
return 0;
}
g++ -std=c++20 main.cpp greeting.cpp && ./a.out
Step 3
g++ -std=c++20 -c *.cpp #compile all without linking
#greeting.o and main.o
g++ *.o #create executable file
#a.out
./a.out # execute
clear && g++ -std=c++20 -c *.cpp && g++ *.o && ./a.out
#Need to compile all *.cpp files
clear && g++ -std=c++20 -c main.cpp && g++ *.o && ./a.out
#However, manually tracking files which have been changed is very difficult task.
Make and Make
- >greeting
- >include
- greeting.h
- >src
- greeting.cpp
- main.cpp
- CMakeLists.txt
- >include
CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
# Set the C++ standard to C++23
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# used internally by CMake to identify your project
project(greeting)
# Include the directory headers are located
include_directories(${CMAKE_SOURCE_DIR}/include)
# Add the main executable
add_executable(greeting src/main.cpp src/greeting.cpp)
Mac
mkdir build
cd build
cmake ..
make
./greeting
Windows
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
cmake --build .
greeting.exe
Catch2
- >greeting
- >include: greeting.h
- >src: greeting.cpp; main.cpp
- >build
- >tests
- >catch2
- catch.hpp
- test.cpp
- >catch2
- CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.5.1)
# Set the C++ standard to C++23
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# used internally by CMake to identify your project
project(greeting)
# Include the directory headers are located
include_directories(${CMAKE_SOURCE_DIR}/include)
# Add the main executable
add_executable(greeting src/main.cpp src/greeting.cpp)
####################################################
# Add the test executable
add_executable(my_test src/greeting.cpp tests/test.cpp)
# Include directories for the test target
target_include_directories(my_test PRIVATE ${PROJECT_SOURCE_DIR}/include)
# Since Catch2 is header-only, we don't need to link a library
# No need to link Catch2 as it's just a header file
# Enable testing
enable_testing()
# Register the test executable with CTest
add_test(NAME my_test COMMAND my_test)
// tests/test.cpp
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "greeting.h"
TEST_CASE("Testing CreateGreeting", "[CreateGreeting]") {
REQUIRE(CreateGreeting("Alice") == "Hello, Alice!");
REQUIRE(CreateGreeting("") == "Hello, !");
REQUIRE(CreateGreeting("1") == "Hello, 1!");
}
TEST_CASE("Testing GenerateWelcomeMessage", "[GenerateWelcomeMessage]") {
REQUIRE(GenerateWelcomeMessage("Alice") == "Hello, Alice! Welcome to the class!");
REQUIRE(GenerateWelcomeMessage("") == "Hello, ! Welcome to the class!");
REQUIRE(GenerateWelcomeMessage("1") == "Hello, 1! Welcome to the class!");
}
// src/greeting.cpp
#include "greeting.h"
#include <iostream>
std::string GenerateWelcomeMessage(const std::string userName){
if (userName == "") {
throw std::invalid_argument("name cannot be empty");
}
std::string greeting = CreateGreeting(userName);
greeting += " Welcome to the class!";
return greeting;
}
std::string CreateGreeting(const std::string userName){
if (userName == "") {
throw std::invalid_argument("name cannot be empty");
}
return "Hello, " + userName + "!";
}
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "greeting.h"
TEST_CASE("CreateGreeting with invalid input", "[CreateGreeting][invalidinput]") {
SECTION("Empty input should throw exception") {
REQUIRE_THROWS_AS(CreateGreeting(""), std::invalid_argument);
}
}
TEST_CASE("GenerateWelcomeMessage with invalid input", "[GenerateWelcomeMessage][invalidinput]") {
SECTION("Empty input should throw exception") {
REQUIRE_THROWS_AS(GenerateWelcomeMessage(""), std::invalid_argument);
}
}
TEST_CASE("CreateGreeting with valid inputs", "[CreateGreeting][validinput]") {
SECTION("Greeting Alice") {
REQUIRE(CreateGreeting("Alice") == "Hello, Alice!");
}
SECTION("Greeting numeric string") {
REQUIRE(CreateGreeting("1") == "Hello, 1!");
}
}
// Test case for GenerateWelcomeMessage with valid inputs
TEST_CASE("GenerateWelcomeMessage with valid inputs", "[GenerateWelcomeMessage][validinput]") {
SECTION("Welcome Alice") {
REQUIRE(GenerateWelcomeMessage("Alice") == "Hello, Alice! Welcome to the class!");
}
SECTION("Welcome numeric string") {
REQUIRE(GenerateWelcomeMessage("1") == "Hello, 1! Welcome to the class!");
}
}
cmake .. && make && ./my_test
./my_test "[CreateGreeting]"
./my_test “[CreateGreeting][invalidinput]"
./my_test "[CreateGreeting][validinput]"
./my_test "[GenerateWelcomeMessage]"
./my_test "[GenerateWelcomeMessage][invalidinput]"
./my_test “[GenerateWelcomeMessage][validinput]"
./my_test "[validinput]"
Behaviour Driven Development (BDD) Style
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include "greeting.h"
SCENARIO("CreateGreeting handles empty input correctly", "[CreateGreeting][invalidinput]") {
GIVEN("an invalid input") {
std::string invalidInput = "";
WHEN("the input is empty") {
THEN("an exception should be thrown") {
REQUIRE_THROWS_AS(CreateGreeting(invalidInput), std::invalid_argument);
}
}
}
}
SCENARIO("CreateGreeting handles valid input correctly", "[CreateGreeting][validinput]") {
GIVEN("a valid input") {
WHEN("the input is 'Alice'") {
std::string validInput = "Alice";
THEN("the greeting should be 'Hello, Alice!'") {
REQUIRE(CreateGreeting(validInput) == "Hello, Alice!");
}
}
WHEN("the input is a numeric string '1'") {
std::string numericInput = "1";
THEN("the greeting should be 'Hello, 1!'") {
REQUIRE(CreateGreeting(numericInput) == "Hello, 1!");
}
}
}
}
SCENARIO("GenerateWelcomeMessage handles empty input correctly", "[GenerateWelcomeMessage][invalidinput]") {
GIVEN("an invalid input") {
std::string invalidInput = "";
WHEN("the input is empty") {
THEN("an exception should be thrown") {
REQUIRE_THROWS_AS(GenerateWelcomeMessage(invalidInput), std::invalid_argument);
}
}
}
}
SCENARIO("GenerateWelcomeMessage handles valid input correctly", "[GenerateWelcomeMessage][validinput]") {
GIVEN("a valid input") {
WHEN("the input is 'Alice'") {
std::string validInput = "Alice";
THEN("the welcome message should be 'Hello, Alice! Welcome to the class!'") {
REQUIRE(GenerateWelcomeMessage(validInput) == "Hello, Alice! Welcome to the class!");
}
}
WHEN("the input is a numeric string '1'") {
std::string numericInput = "1";
THEN("the welcome message should be 'Hello, 1! Welcome to the class!'") {
REQUIRE(GenerateWelcomeMessage(numericInput) == "Hello, 1! Welcome to the class!");
}
}
}
}
cmake .. && make && ./my_test
./my_test "[CreateGreeting]"
./my_test “[CreateGreeting][invalidinput]"
./my_test "[CreateGreeting][validinput]"
./my_test "[GenerateWelcomeMessage]"
./my_test "[GenerateWelcomeMessage][invalidinput]"
./my_test “[GenerateWelcomeMessage][validinput]"
./my_test "[validinput]"