SlideShare a Scribd company logo
Refactoring for
testability in C++
Hard-to-test patterns in C++ and
how to refactor them
About me Dimitrios Platis
● Grew up in Rodos, Greece
● Software Engineer @ Zenuity,
Gothenburg, Sweden
● Course responsible @ Gothenburg
University, DIT112
● Interests:
○ Embedded systems
○ Software Architecture
○ API Design
○ Open source software & hardware
○ Robots, Portable gadgets, IoT
○ 3D printing
○ Autonomous Driving
● Website: https://platis.solutions
[grcpp] Refactoring for testability c++
DIT112-V19
DIT112-V20
How about you?
[grcpp]
● Knowledge spreading
● Discussions
● Development culture
● Networking
Agenda
1. Ηard-to-test code
2. Easy-to-test code
3. Patterns to refactor
Hard-to-test code
When is code hard to test?
● High complexity
● Tight coupling with other components
● Dependence on global/static states
● Sequential coupling
● ...more!
When
it's not fun
● Create fakes containing a lot of logic
● Change visibility of member functions and attributes
● Test things that have been tested before
● Sporadic fails
● Execution takes ages
Easy-to-test code
When is code easy to test?
● Low complexity
● SOLID
● Prefers composition over inheritance
● Abstracted dependencies
● Functional code without side-effects
● ...more!
Patterns to refactor
Grain of salt
● Treat these examples as generic guidelines
○ Won't necessarily apply to your project
● Maybe worse performance
○ 3 rules of optimization
● Possibly incompatible with your domain constraints
○ ISO 26262, AUTOSAR, MISRA
● Perhaps easier/cheaper with more advanced test frameworks
○ Fruit
Mocking
Unit under test
Dependency
Production
Unit under test
Mock
Test
Core philosophy
Getting own
dependencies
Managing own
lifecycle
Getting own
configuration
Ignorantly controlling self
Dependencies
injected
Lifecycle
managed from
outside
Configuration
injected
Inversion of Control
Adopted from picocontainer.com article
Files
Why is it difficult?
bool write(const std::string& filePath,
const std::string& content)
{
std::ofstream outfile(filePath.c_str(),
std::ios::trunc);
if (outfile.good())
{
outfile << content << std::endl;
}
return outfile.good();
}
std::optional<std::string>
read(const std::string& filePath)
{
std::ifstream fileToRead(filePath);
std::stringstream buffer;
buffer << fileToRead.rdbuf();
if (buffer.good())
{
return std::make_optional(buffer.str());
}
return std::nullopt;
}
How would you test this?
bool FileEncoder::encode(const std::string& filePath) const
{
const auto validFileContents = read(filePath);
if (!validFileContents)
{
return false;
}
auto encodedFileContents = validFileContents.value();
// Do something with file contents
const auto wroteFileSuccessfully
= write(filePath + ".encoded", encodedFileContents);
return wroteFileSuccessfully;
}
Refactor
Abstract
struct FileReader {
virtual ~FileReader() = default;
virtual std::optional<std::string>
read(const std::string& filePath) const = 0;
};
struct FileWriter {
virtual ~FileWriter() = default;
virtual bool
write(const std::string& filePath,
const std::string& content) const = 0;
};
Inject
struct FileEncoder {
FileEncoder(FileReader& fileReader,
FileWriter& fileWriter);
bool encode(const std::string& filePath) const;
private:
FileReader& mFileReader;
FileWriter& mFileWriter;
};
Hard-coded
dependencies
Why is it difficult?
struct DirectionlessOdometer
{
DirectionlessOdometer(int pulsePin,
int pulsesPerMeter);
double getDistance() const;
protected:
const int mPulsesPerMeter;
int mPulses{0};
MyInterruptServiceManager mInterruptManager;
};
struct DirectionalOdometer
: public DirectionlessOdometer
{
DirectionalOdometer(int directionPin,
int pulsePin,
int pulsesPerMeter);
private:
MyPinReader mPinReader;
const int mDirectionPin;
};
How would you test this?
DirectionlessOdometer::DirectionlessOdometer(
int pulsePin, int pulsesPerMeter)
: mPulsesPerMeter{pulsesPerMeter}
{
mInterruptManager.triggerOnNewPulse(
pulsePin, [this] () { mPulses++; });
}
double DirectionlessOdometer::getDistance() const
{
return mPulses == 0 ?
0.0 :
static_cast<double>(mPulsesPerMeter) / mPulses;
}
DirectionalOdometer::DirectionalOdometer(
int directionPin,
int pulsePin,
int pulsesPerMeter)
: DirectionlessOdometer(pulsePin, pulsesPerMeter)
, mDirectionPin{directionPin}
{
mInterruptManager.triggerOnNewPulse(
pulsePin,
[this]() {
mPinReader.read(mDirectionPin) ?
mPulses++ :
mPulses--;
});
}
Refactor
Abstract common functionality & inject
struct Encoder
{
virtual ~Encoder() = default;
virtual void incrementPulses() = 0;
virtual void decrementPulses() = 0;
virtual double getDistance() const = 0;
};
struct DirectionlessOdometer
{
DirectionlessOdometer(Encoder& encoder,
InterruptServiceManager& ism,
int pulsePin);
double getDistance() const;
private:
Encoder& mEncoder;
};
Abstract dependencies & inject
struct DirectionalOdometer
{
DirectionalOdometer(Encoder& encoder,
InterruptServiceManager& ism,
PinReader& pinReader,
int directionPin,
int pulsePin);
double getDistance() const;
private:
Encoder& mEncoder;
PinReader& mPinReader;
};
Time
Why is it difficult?
set(gtest_run_flags --gtest_repeat=1000)
How would you test this?
struct PowerController
{
PowerController(PinManager& pinManager,
InterruptManager& interruptManager);
bool turnOn();
private:
PinManager& mPinManager;
std::condition_variable mConditionVariable;
std::atomic<bool> mPulseReceived{false};
std::mutex mRunnerMutex;
};
bool PowerController::turnOn()
{
mPinManager.setPin(kPin);
std::this_thread::sleep_for(1s);
mPinManager.clearPin(kPin);
std::unique_lock<std::mutex> lk(mRunnerMutex);
mConditionVariable.wait_for(
lk,
10s,
[this]() { return mPulseReceived.load(); });
return mPulseReceived.load();
}
Refactor
struct TimeKeeper
{
virtual ~TimeKeeper() = default;
virtual void
sleepFor(std::chrono::milliseconds ms) const = 0;
};
struct AsynchronousTimer
{
virtual ~AsynchronousTimer() = default;
virtual void
schedule(std::function<void()> task,
std::chrono::seconds delay) = 0;
};
bool PowerController::turnOn() {
mPinManager.setPin(kPin);
mTimeKeeper.sleepFor(1s);
mPinManager.clearPin(kPin);
mAsynchronousTimer.schedule(
[this]() {
mPulseTimedOut = true;
mConditionVariable.notify_one(); }, 10s);
std::unique_lock<std::mutex> lk(mRunnerMutex);
mConditionVariable.wait(lk, [this]() {
return mPulseReceived.load() ||
mPulseTimedOut.load(); });
mPulseTimedOut = false;
return mPulseReceived.load();
}
Domain logic dependent
on application logic
Why is it difficult?
● "The dependency rule"
○ Clean architecture
● Circular dependencies
● Domain layer eventually becomes
unsustainable to develop or test
● (C++ specific) Macro madness
Variant A Variant B Variant C
Platform
How would you test this?
struct CommunicationManager
{
CommunicationManager(SerialPortClient& serial);
void sendViaSerial(std::string message);
private:
SerialPortClient& mSerialPortClient;
int mSequenceNumber{0};
};
void
CommunicationManager::sendViaSerial(std::string message)
{
#if defined(FOO_PRODUCT)
mSerialPortClient.send(
std::to_string(mSequenceNumber++) + ":" + message);
#elif defined(BAR_PRODUCT)
mSerialPortClient.send("M:" + message + ",");
#else
#error Did you forget to define a product?
#endif
}
Refactor
struct SerialFormatter {
virtual ~SerialFormatter() = default;
virtual std::string
format(std::string in) = 0;
};
std::string
BarSerialFormatter::format(std::string in) {
return "M:" + in + ",";
}
std::string
FooSerialFormatter::format(std::string in) {
return std::to_string(mSequenceNumber++) +
":" + input;
}
struct CommunicationManager {
CommunicationManager(
SerialPortClient& serialPortClient,
SerialFormatter& serialFormatter);
void sendViaSerial(std::string message);
private:
SerialPortClient& mSerialPortClient;
SerialFormatter& mSerialFormatter;
};
void
CommunicationManager::sendViaSerial(std::string message) {
mSerialPortClient.send(mSerialFormatter.format(message));
}
Takeaways
More patterns & working code examples:
platisd/refactoring-for-testability-cpp
Contact me: dimitris@platis.solutions
● Unit tests should be atomic and run fast
● Verify your code (only)
● It should be fun
○ Not much up-front effort
● Abstract & inject
○ Care about what and not how
● Write SOLID code
● Follow OOP best practices
● Follow CPP core guidelines

More Related Content

PDF
Refactoring for testability c++
PDF
Preparation for mit ose lab4
PDF
Silicon Valley JUG: JVM Mechanics
PDF
Non-blocking synchronization — what is it and why we (don't?) need it
PDF
Qt Rest Server
PPTX
Disruptor
ODP
Node js lecture
PDF
Actor Concurrency
Refactoring for testability c++
Preparation for mit ose lab4
Silicon Valley JUG: JVM Mechanics
Non-blocking synchronization — what is it and why we (don't?) need it
Qt Rest Server
Disruptor
Node js lecture
Actor Concurrency

What's hot (20)

PDF
Counter Wars (JEEConf 2016)
PPTX
Node.js System: The Landing
PPTX
RealmDB for Android
PDF
Prometheus Storage
DOCX
Java 5 concurrency
PPTX
Zero-Overhead Metaprogramming: Reflection and Metaobject Protocols Fast and w...
PDF
Java9を迎えた今こそ!Java本格(再)入門
PDF
Jenkins 2を使った究極のpipeline ~ 明日もう一度来てください、本物のpipelineをお見せしますよ ~
PDF
Работа с реляционными базами данных в C++
PPTX
Building High-Performance Language Implementations With Low Effort
PDF
Circuit breaker
PPTX
Codable routing
PDF
Java 8 고급 (4/6)
PDF
Much Ado About Blocking: Wait/Wakke in the Linux Kernel
PDF
Implementing STM in Java
PPTX
Weaponizing the Windows API with Metasploit's Railgun
PDF
Java Concurrency Idioms
PDF
Other Approaches (Concurrency)
PPTX
Nicety of Java 8 Multithreading
PDF
Multithreading done right
Counter Wars (JEEConf 2016)
Node.js System: The Landing
RealmDB for Android
Prometheus Storage
Java 5 concurrency
Zero-Overhead Metaprogramming: Reflection and Metaobject Protocols Fast and w...
Java9を迎えた今こそ!Java本格(再)入門
Jenkins 2を使った究極のpipeline ~ 明日もう一度来てください、本物のpipelineをお見せしますよ ~
Работа с реляционными базами данных в C++
Building High-Performance Language Implementations With Low Effort
Circuit breaker
Codable routing
Java 8 고급 (4/6)
Much Ado About Blocking: Wait/Wakke in the Linux Kernel
Implementing STM in Java
Weaponizing the Windows API with Metasploit's Railgun
Java Concurrency Idioms
Other Approaches (Concurrency)
Nicety of Java 8 Multithreading
Multithreading done right
Ad

Similar to [grcpp] Refactoring for testability c++ (20)

PDF
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
PDF
Jvm profiling under the hood
PDF
用 Go 語言打造多台機器 Scale 架構
PPTX
Robust C++ Task Systems Through Compile-time Checks
PDF
TWINS: OOP and FP - Warburton
PDF
JVM Mechanics: When Does the JVM JIT & Deoptimize?
PDF
Loom and concurrency latest
ODP
Linux kernel tracing superpowers in the cloud
PDF
Start Wrap Episode 11: A New Rope
PDF
Giorgio zoppi cpp11concurrency
PDF
How to make a high-quality Node.js app, Nikita Galkin
PPT
Lo Mejor Del Pdc2008 El Futrode C#
PDF
Apidays Paris 2023 - Forget TypeScript, Choose Rust to build Robust, Fast and...
PDF
Twins: OOP and FP
PDF
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
PDF
Brad Wood - CommandBox CLI
PDF
Php 5.6 From the Inside Out
PDF
A New Chapter of Data Processing with CDK
ODP
Finagle and Java Service Framework at Pinterest
PDF
Create C++ Applications with the Persistent Memory Development Kit
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
Jvm profiling under the hood
用 Go 語言打造多台機器 Scale 架構
Robust C++ Task Systems Through Compile-time Checks
TWINS: OOP and FP - Warburton
JVM Mechanics: When Does the JVM JIT & Deoptimize?
Loom and concurrency latest
Linux kernel tracing superpowers in the cloud
Start Wrap Episode 11: A New Rope
Giorgio zoppi cpp11concurrency
How to make a high-quality Node.js app, Nikita Galkin
Lo Mejor Del Pdc2008 El Futrode C#
Apidays Paris 2023 - Forget TypeScript, Choose Rust to build Robust, Fast and...
Twins: OOP and FP
Flink Forward Berlin 2017: Maciek Próchniak - TouK Nussknacker - creating Fli...
Brad Wood - CommandBox CLI
Php 5.6 From the Inside Out
A New Chapter of Data Processing with CDK
Finagle and Java Service Framework at Pinterest
Create C++ Applications with the Persistent Memory Development Kit
Ad

More from Dimitrios Platis (13)

PDF
[gbgcpp] Let's get comfortable with concepts
PDF
Let's get comfortable with C++20 concepts - Cologne C++ User group
PDF
Let's get comfortable with C++20 concepts (XM)
PDF
[GRCPP] Introduction to concepts (C++20)
PDF
OpenAI API crash course
PDF
Builder pattern in C++.pdf
PDF
Interprocess communication with C++.pdf
PDF
Lambda expressions in C++
PDF
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
PDF
Introduction to CMake
PDF
Pointer to implementation idiom
PDF
Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)
PDF
How to create your own Linux distribution (embedded-gothenburg)
[gbgcpp] Let's get comfortable with concepts
Let's get comfortable with C++20 concepts - Cologne C++ User group
Let's get comfortable with C++20 concepts (XM)
[GRCPP] Introduction to concepts (C++20)
OpenAI API crash course
Builder pattern in C++.pdf
Interprocess communication with C++.pdf
Lambda expressions in C++
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Introduction to CMake
Pointer to implementation idiom
Afry software safety ISO26262 (Embedded @ Gothenburg Meetup)
How to create your own Linux distribution (embedded-gothenburg)

Recently uploaded (20)

PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
Digital Systems & Binary Numbers (comprehensive )
PDF
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
PPTX
L1 - Introduction to python Backend.pptx
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Nekopoi APK 2025 free lastest update
PDF
System and Network Administration Chapter 2
PDF
Navsoft: AI-Powered Business Solutions & Custom Software Development
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
ai tools demonstartion for schools and inter college
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Understanding Forklifts - TECH EHS Solution
PPTX
Transform Your Business with a Software ERP System
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
Adobe Illustrator 28.6 Crack My Vision of Vector Design
Digital Systems & Binary Numbers (comprehensive )
EN-Survey-Report-SAP-LeanIX-EA-Insights-2025.pdf
L1 - Introduction to python Backend.pptx
Odoo Companies in India – Driving Business Transformation.pdf
Addressing The Cult of Project Management Tools-Why Disconnected Work is Hold...
How to Choose the Right IT Partner for Your Business in Malaysia
Design an Analysis of Algorithms I-SECS-1021-03
Nekopoi APK 2025 free lastest update
System and Network Administration Chapter 2
Navsoft: AI-Powered Business Solutions & Custom Software Development
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
ai tools demonstartion for schools and inter college
Softaken Excel to vCard Converter Software.pdf
Understanding Forklifts - TECH EHS Solution
Transform Your Business with a Software ERP System
Design an Analysis of Algorithms II-SECS-1021-03
wealthsignaloriginal-com-DS-text-... (1).pdf

[grcpp] Refactoring for testability c++

  • 1. Refactoring for testability in C++ Hard-to-test patterns in C++ and how to refactor them
  • 2. About me Dimitrios Platis ● Grew up in Rodos, Greece ● Software Engineer @ Zenuity, Gothenburg, Sweden ● Course responsible @ Gothenburg University, DIT112 ● Interests: ○ Embedded systems ○ Software Architecture ○ API Design ○ Open source software & hardware ○ Robots, Portable gadgets, IoT ○ 3D printing ○ Autonomous Driving ● Website: https://platis.solutions
  • 6. [grcpp] ● Knowledge spreading ● Discussions ● Development culture ● Networking
  • 7. Agenda 1. Ηard-to-test code 2. Easy-to-test code 3. Patterns to refactor
  • 9. When is code hard to test? ● High complexity ● Tight coupling with other components ● Dependence on global/static states ● Sequential coupling ● ...more!
  • 10. When it's not fun ● Create fakes containing a lot of logic ● Change visibility of member functions and attributes ● Test things that have been tested before ● Sporadic fails ● Execution takes ages
  • 12. When is code easy to test? ● Low complexity ● SOLID ● Prefers composition over inheritance ● Abstracted dependencies ● Functional code without side-effects ● ...more!
  • 14. Grain of salt ● Treat these examples as generic guidelines ○ Won't necessarily apply to your project ● Maybe worse performance ○ 3 rules of optimization ● Possibly incompatible with your domain constraints ○ ISO 26262, AUTOSAR, MISRA ● Perhaps easier/cheaper with more advanced test frameworks ○ Fruit
  • 16. Core philosophy Getting own dependencies Managing own lifecycle Getting own configuration Ignorantly controlling self Dependencies injected Lifecycle managed from outside Configuration injected Inversion of Control Adopted from picocontainer.com article
  • 17. Files
  • 18. Why is it difficult? bool write(const std::string& filePath, const std::string& content) { std::ofstream outfile(filePath.c_str(), std::ios::trunc); if (outfile.good()) { outfile << content << std::endl; } return outfile.good(); } std::optional<std::string> read(const std::string& filePath) { std::ifstream fileToRead(filePath); std::stringstream buffer; buffer << fileToRead.rdbuf(); if (buffer.good()) { return std::make_optional(buffer.str()); } return std::nullopt; }
  • 19. How would you test this? bool FileEncoder::encode(const std::string& filePath) const { const auto validFileContents = read(filePath); if (!validFileContents) { return false; } auto encodedFileContents = validFileContents.value(); // Do something with file contents const auto wroteFileSuccessfully = write(filePath + ".encoded", encodedFileContents); return wroteFileSuccessfully; }
  • 20. Refactor Abstract struct FileReader { virtual ~FileReader() = default; virtual std::optional<std::string> read(const std::string& filePath) const = 0; }; struct FileWriter { virtual ~FileWriter() = default; virtual bool write(const std::string& filePath, const std::string& content) const = 0; }; Inject struct FileEncoder { FileEncoder(FileReader& fileReader, FileWriter& fileWriter); bool encode(const std::string& filePath) const; private: FileReader& mFileReader; FileWriter& mFileWriter; };
  • 22. Why is it difficult? struct DirectionlessOdometer { DirectionlessOdometer(int pulsePin, int pulsesPerMeter); double getDistance() const; protected: const int mPulsesPerMeter; int mPulses{0}; MyInterruptServiceManager mInterruptManager; }; struct DirectionalOdometer : public DirectionlessOdometer { DirectionalOdometer(int directionPin, int pulsePin, int pulsesPerMeter); private: MyPinReader mPinReader; const int mDirectionPin; };
  • 23. How would you test this? DirectionlessOdometer::DirectionlessOdometer( int pulsePin, int pulsesPerMeter) : mPulsesPerMeter{pulsesPerMeter} { mInterruptManager.triggerOnNewPulse( pulsePin, [this] () { mPulses++; }); } double DirectionlessOdometer::getDistance() const { return mPulses == 0 ? 0.0 : static_cast<double>(mPulsesPerMeter) / mPulses; } DirectionalOdometer::DirectionalOdometer( int directionPin, int pulsePin, int pulsesPerMeter) : DirectionlessOdometer(pulsePin, pulsesPerMeter) , mDirectionPin{directionPin} { mInterruptManager.triggerOnNewPulse( pulsePin, [this]() { mPinReader.read(mDirectionPin) ? mPulses++ : mPulses--; }); }
  • 24. Refactor Abstract common functionality & inject struct Encoder { virtual ~Encoder() = default; virtual void incrementPulses() = 0; virtual void decrementPulses() = 0; virtual double getDistance() const = 0; }; struct DirectionlessOdometer { DirectionlessOdometer(Encoder& encoder, InterruptServiceManager& ism, int pulsePin); double getDistance() const; private: Encoder& mEncoder; }; Abstract dependencies & inject struct DirectionalOdometer { DirectionalOdometer(Encoder& encoder, InterruptServiceManager& ism, PinReader& pinReader, int directionPin, int pulsePin); double getDistance() const; private: Encoder& mEncoder; PinReader& mPinReader; };
  • 25. Time
  • 26. Why is it difficult? set(gtest_run_flags --gtest_repeat=1000)
  • 27. How would you test this? struct PowerController { PowerController(PinManager& pinManager, InterruptManager& interruptManager); bool turnOn(); private: PinManager& mPinManager; std::condition_variable mConditionVariable; std::atomic<bool> mPulseReceived{false}; std::mutex mRunnerMutex; }; bool PowerController::turnOn() { mPinManager.setPin(kPin); std::this_thread::sleep_for(1s); mPinManager.clearPin(kPin); std::unique_lock<std::mutex> lk(mRunnerMutex); mConditionVariable.wait_for( lk, 10s, [this]() { return mPulseReceived.load(); }); return mPulseReceived.load(); }
  • 28. Refactor struct TimeKeeper { virtual ~TimeKeeper() = default; virtual void sleepFor(std::chrono::milliseconds ms) const = 0; }; struct AsynchronousTimer { virtual ~AsynchronousTimer() = default; virtual void schedule(std::function<void()> task, std::chrono::seconds delay) = 0; }; bool PowerController::turnOn() { mPinManager.setPin(kPin); mTimeKeeper.sleepFor(1s); mPinManager.clearPin(kPin); mAsynchronousTimer.schedule( [this]() { mPulseTimedOut = true; mConditionVariable.notify_one(); }, 10s); std::unique_lock<std::mutex> lk(mRunnerMutex); mConditionVariable.wait(lk, [this]() { return mPulseReceived.load() || mPulseTimedOut.load(); }); mPulseTimedOut = false; return mPulseReceived.load(); }
  • 29. Domain logic dependent on application logic
  • 30. Why is it difficult? ● "The dependency rule" ○ Clean architecture ● Circular dependencies ● Domain layer eventually becomes unsustainable to develop or test ● (C++ specific) Macro madness Variant A Variant B Variant C Platform
  • 31. How would you test this? struct CommunicationManager { CommunicationManager(SerialPortClient& serial); void sendViaSerial(std::string message); private: SerialPortClient& mSerialPortClient; int mSequenceNumber{0}; }; void CommunicationManager::sendViaSerial(std::string message) { #if defined(FOO_PRODUCT) mSerialPortClient.send( std::to_string(mSequenceNumber++) + ":" + message); #elif defined(BAR_PRODUCT) mSerialPortClient.send("M:" + message + ","); #else #error Did you forget to define a product? #endif }
  • 32. Refactor struct SerialFormatter { virtual ~SerialFormatter() = default; virtual std::string format(std::string in) = 0; }; std::string BarSerialFormatter::format(std::string in) { return "M:" + in + ","; } std::string FooSerialFormatter::format(std::string in) { return std::to_string(mSequenceNumber++) + ":" + input; } struct CommunicationManager { CommunicationManager( SerialPortClient& serialPortClient, SerialFormatter& serialFormatter); void sendViaSerial(std::string message); private: SerialPortClient& mSerialPortClient; SerialFormatter& mSerialFormatter; }; void CommunicationManager::sendViaSerial(std::string message) { mSerialPortClient.send(mSerialFormatter.format(message)); }
  • 33. Takeaways More patterns & working code examples: platisd/refactoring-for-testability-cpp Contact me: dimitris@platis.solutions ● Unit tests should be atomic and run fast ● Verify your code (only) ● It should be fun ○ Not much up-front effort ● Abstract & inject ○ Care about what and not how ● Write SOLID code ● Follow OOP best practices ● Follow CPP core guidelines