Every once in a while, a developer scratches an itch so persistent it turns into something bigger. Thatβs exactly what CppForge is: a response to the pain of boilerplate, inconsistencies, and the lack of elegant BDD tooling in C++.
CppForge isnβt just another framework. Itβs a developer-first, test-driven, and scaffold-powered engine designed to elevate the experience of writing, testing, and maintaining C++ applications.
π§± Why I Started This
It began with a vision: building my own portable cloud, powered by a cluster of Raspberry Pis β not in a datacenter, but right at home. I wanted services I could control, scale, and take with me. Not just as a personal cloud, but as a resilient, modular system that could one day run anywhere.
To get there, I needed the right tools β tools that understood structure, automation, and developer-first thinking. Most C++ systems lacked that. They were brittle, boilerplate-heavy, and far from portable.
CppForge is my way of changing that β by building a framework that can scaffold services, generate testable modules, and orchestrate clusters with ease. It's not just about better code β it's about creating the backbone for portable, self-sovereign cloud systems.
π What CppForge Solves
- π§ͺ Full BDD support in C++ with scenario-based test generation
- π Template-powered scaffolding: libraries, executables, and testable interfaces
- π‘ Feature-focused development: think in terms of outcomes, not just files
𧬠The Vision
CppForge isn't just for scaffolding or testing β it's laying the groundwork for a developer operating system for C++. Imagine a platform where:
- You write your use case in plain language
- The engine scaffolds the entire architecture
- Tests, interfaces, and integrations are wired in from the start
- You focus only on what matters: business logic
But a vision is only as powerful as the tools it delivers. So hereβs whatβs already working β and why itβs changing the game:
π§° Scaffolding Engine
CppForge can generate fully structured modules β libraries or executables β with just a single scenario description. You define what the system should do, and CppForge builds the folder structure, CMake config, and interface contracts.
Think:
monitor.bddβmonitor/src,monitor/include,CMakeLists.txt,main.cpp, test stubs β all automatically created.
./agent
βββ CMakeLists.txt
βββ include
βΒ Β βββ agent.hpp
βΒ Β βββ agent_factory.hpp
βββ src
βΒ Β βββ agent.cpp
βΒ Β βββ main.cpp
βββ test
βββ CMakeLists.txt
βββ test_1_agent_heartbeat_agent_sends_heartbeat.cpp
βββ test_2_monitor_local_services_docker_is_active.cpp
βββ test_2_monitor_local_services_k3s_is_inactive.cpp
βββ test_3_agent_package_reporting_agent_lists_all_packages.cpp
βββ test_3_agent_package_reporting_agent_lists_simplified_packages.cpp
βββ test_4_remote_command_execution_execute_shell_command_on_target_node.cpp
βββ test_4_remote_command_execution_handle_command_timeouts.cpp
βββ test_4_remote_command_execution_return_execution_result_to_master.cpp
βββ test_4_remote_command_execution_sanitize_and_validate_incoming_commands.cpp
βββ test_main.cpp
π§ͺ Scenario-Driven Test Generation
At the heart of CppForge is a test-first mindset. Developers write features in plain English using BDD-style syntax, and CppForge translates them into structured, ready-to-run C++ test scaffolds using Google Test.
This approach bridges the gap between behavioral expectations and actual test code β enforcing structure, encouraging clarity, and ensuring nothing gets implemented without a test.
π Example Feature Spec
A developer defines a feature like this:
Feature: Monitor Local Services
Scenario: Docker is active
Given Docker is installed
When the agent checks for running services
Then Docker should be reported as running
Scenario: K3s is inactive
Given k3s is not running
When the agent checks for running services
Then it should report k3s as inactive
CppForge then automatically generates a test file with:
- A virtual interface class representing the scenario
- A stub implementation that causes the test to fail until implemented
- A factory method
forge_bdd_steps()to swap in the real implementation when ready - A runnable Google Test case that exercises the scenario
#include <gtest/gtest.h>
#include <iostream>
#include <memory>
// Step Interface
class scn_2_monitor_local_services_docker_is_active {
public:
virtual void given_docker_is_installed() {
FAIL() << "[STEP NOT IMPLEMENTED] Given Docker is installed";
}
virtual void when_the_agent_checks_for_running_services() {
FAIL() << "[STEP NOT IMPLEMENTED] When the agent checks for running services";
}
virtual void then_docker_should_be_reported_as_running() {
FAIL() << "[STEP NOT IMPLEMENTED] Then Docker should be reported as running";
}
virtual ~scn_2_monitor_local_services_docker_is_active() = default;
};
// Stub fallback
class stub_scn_2_monitor_local_services_docker_is_active : public scn_2_monitor_local_services_docker_is_active {};
#ifdef HAS_IMPL_scn_2_monitor_local_services_docker_is_active
std::unique_ptr<scn_2_monitor_local_services_docker_is_active> forge_bdd_steps() {
return std::make_unique<impl_scn_2_monitor_local_services_docker_is_active>();
}
#else
std::unique_ptr<scn_2_monitor_local_services_docker_is_active> forge_bdd_steps() {
return std::make_unique<stub_scn_2_monitor_local_services_docker_is_active>();
}
#endif
// Executable scenario
TEST(scn_2_monitor_local_services_docker_is_active_test, executes_scenario) {
auto steps = forge_bdd_steps();
std::cout << "[GIVEN] Given Docker is installed" << std::endl;
steps->given_docker_is_installed();
std::cout << "[WHEN] When the agent checks for running services" << std::endl;
steps->when_the_agent_checks_for_running_services();
std::cout << "[THEN] Then Docker should be reported as running" << std::endl;
steps->then_docker_should_be_reported_as_running();
}
π§βπ» Developer Implementation
Once ready, the developer creates their real implementation:
class impl_scn_2_monitor_local_services_docker_is_active
: public scn_2_monitor_local_services_docker_is_active {
public:
void given_docker_is_installed() override {
// Simulate installation or validate system state
}
void when_the_agent_checks_for_running_services() override {
// Trigger service check logic
}
void then_docker_should_be_reported_as_running() override {
// Assert result or state output
ASSERT_TRUE(service_report().docker_running);
}
};
The test now passes β and the developer never has to touch the test runner logic. All they write is the behavior logic in one clean implementation file.
π§ Smart CMake Integration
Every module, whether itβs an app or a library, is wired into your projectβs build system automatically. CppForge knows how to cleanly register, link, and organize CMake targets β even supporting modular test discovery.
You never have to touch CMake unless you want to.
π§± Composable Interfaces
CppForge creates standard, override-ready interfaces for every generated module. These are designed for testability, extension, and inversion of control. Developers write only the logic; the glue is already there.
Encourages a clean separation between core logic and system boundaries.
π Plugin-Ready Foundation
From the start, the engine is built with plugins in mind β whether for source analysis, metadata extraction, DI, or CI hooks. CppForge is positioning itself as a pluggable, opinionated, developer-focused platform.
Future tools like DI or clustering will simply drop into place.
π§ Run Configuration Support
Scenarios generate test runners with built-in CLion support β each test can have its own green button, making BDD development visible, trackable, and clickable.
That scenario you defined? It now lives in your IDEβs test runner β no config required.
This is just scratching the surface β weβre building towards something where the developer experience leads the architecture, not the other way around.
Top comments (0)