SlideShare a Scribd company logo
Stopping the rot Putting legacy C++ under test Seb Rose ACCU 2011
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
A brief history of DOORS Developed in C in early 1990s Home grown cross platform GUI Heavy use of pre-processor macros Server and client share codebase Ported to C++ in 1999 No unit tests – ever DXL extension language tests brittle Success led to rapid team growth Proliferation of products and integrations
Challenges Highly coupled code Long build times Developer ‘silos’ SRD - “Big Design Up Front” Long manual regression test ‘tail’ Hard to make modifications without errors No experience writing unit tests
New direction Move to iterative development Implementation driven by User Stories not SRD All new/modified code to have unit tests All unit tests to be run every build Nightly builds  CI server Develop “Whole Team” approach Automated acceptance tests written by test & dev Test to pick up nightly builds Align with Rational toolset
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Why Unit Test? Greater confidence than Buddy check only Fewer regressions Tests as documentation don’t get out of step with the code “ Legacy Code is code without Unit Tests” – Michael Feathers Can drive out clean designs
When to write Unit Tests? ALWAYS Test Before (TDD) tends to lead to cleaner interfaces Test After tends to miss some test cases & takes longer For TDD to work the component under test & the tests must build fast (< 1 minute) You CAN make this possible Componentise Partition
Unit Test guidelines Only test a single behaviour Use descriptive names (as long as necessary) Group related tests Do not make tests brittle Treat tests just like production code Refactor to remove redundancy & improve architecture Adhere to all coding standards Tests are documentation They must ‘read well’
How to write the first Unit Test Major refactoring needed to put “seams” in place Patterns used extensively for initial refactoring: “Working Effectively With Legacy Code” Link errors in unit test build point to unwanted dependencies Replace dependencies with ‘injected’ mock/fake objects … … until you really are UNIT testing.
A test is not a unit test if: It talks to the database It communicates across the network It touches the file system It can’t run at the same time as other unit tests You have to do special things to your environment (such as editing config files) to run it (Michael Feathers’ blog, 2005)
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Which framework to use? We chose Googletest & Googlemock Available from Googlecode Very liberal open source license Cross platform Can use independently, but work together “out of the box” Implemented using macros & templates Easy to learn Well documented
Googletest No need to register tests Builds as command line executable Familiar to users of xUnit: Suites Fixtures SetUp, TearDown Filters to enable running subsets Handles exceptions
Googlemock Feature-rich Dependency on C++ TC1, but can use Boost Extensible matching operators Declarative style (using operator chaining) Sequencing can be enforced Use of templates slows build time Can only mock virtual methods Still need to declare mock interface Inconvenient to mock operators, destructors & vararg
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
The first test TEST(HttpResponse,  default_response_code_should_be_unset ) { HttpResponse  response ; ASSERT_EQ( HttpResponse::Unset, response.getCode ()); }
The first mock (1) class RestfulServer { virtual bool doesDirectoryExist(const std::string& name) = 0; virtual bool doesResourceExist(const std::string& name) = 0; }; class  M ockRestfulServer : public RestfulServer  { MOCK_METHOD1(doesDirectoryExist,  bool(const std::string& name)); MOCK_METHOD1(doesResourceExist,  bool(const std::string& name)); };
The first mock (2) TEST(JazzProxy_fileExists, should _r eturn _t rue _i f _directory_e xists)  { MockRestfulServer mockServer; Proxy  p roxy(mockServer); EXPECT_CALL(mockServer,   doesDirectoryExist( _ ))  .WillOnce(Return(true)); EXPECT_CALL(mockServer, doesResourceExist(_)) .Times(0); bool exists = false; ASSERT_NO_THROW(proxy. fileExists(“ myFolder &quot;, exists)  ) ; ASSERT_TRUE(exists); }
Another Mock (1) HttpTimer::~HttpTimer() { if (theLogger.getLevel() >= LOG_LEVEL_WARNING) theLogger.writeLn(“ Timer:  %d ms&quot;, stopClock()); } class Logger  { public: virtual ~Logger(); // Operations for logging textual entries to a log file. virtual unsigned getLevel() const = 0; virtual void write ( const char* fmt, ...) = 0;  virtual void writeLn(const char* fmt, ...) = 0; };
Another Mock (2) class MockLogger : public Logger { public: MOCK_CONST_METHOD0(getLevel, unsigned  int ()); void write(const char* fmt, ...) {}; void writeLn(const char* fmt, ...)  { va_list ap; va_start(ap, fmt); DWORD clock = va_arg(ap, DWORD); va_end(ap); mockWriteLn(fmt, clock); } MOCK_METHOD 2 (mockWriteLn,   void(const char*, DWORD)); } ;
Another Mock (3) TEST(HttpTimer,  writes_to_logger_if_log_level_is_at_warning ) { MockLogger testLogger; EXPECT_CALL(testLogger, getLevel()) .Will Once (Return( LOG_LEVEL_WARNING )); EXPECT_CALL(testLogger, mockWriteLn( _,  _ )) .Times(1) ; HttpTimer timer ( testLogger); }
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Wrap Dependency CONTEXT We want to test some legacy code The legacy code has an ugly dependency Requires inclusion of code we don’t want to test SOLUTION Create an interface that describes behaviour of dependency Re-write call to inject dependency In test code inject a test double
Test Doubles Dummy: never used – only passed around to fill parameter list Stub: provides canned responses Fake: has simplified implementation Mock: object pre-programmed with expectations – the specification of calls they are expected to receive “ Test Double”: generic term for any of the above
Code Under Test tree* openBaseline(tree *module, VersionId version) { tree *baseline = NULL; … BaselineId baselineId =   DoorsServer::getInstance().findBaseline(   module,    version); … return baseline; }
Test The Defect TEST(OpenBaseline, opening_a_baseline_with_default_version_should_throw) { tree myTree; VersionId version; ASSERT_THROWS_ANY(openBaseline(&myTree, version)); } Won’t link without inclusion of DoorsServer
Describe Behaviour class Server { virtual BaselineId findBaseline(tree*, VersionId) = 0; } class DoorsServer : public Server { … BaselineId findBaseline(tree*, VersionId); … }
Refactor Code Under Test tree* openBaseline( Server& server, tree *module,  VersionId version) { tree *baseline = NULL; … BaselineId baselineId =   server.findBaseline(   module,    version); … return baseline; }
Modify the Test class TestServer : public Server{ BaselineId findBaseline(tree*, VersionId) { return BaselineId(); } }; TEST(OpenBaseline, opening_a_baseline_with_default_version_should_throw) { TestServer server; tree myTree; VersionId version; ASSERT_THROWS_ANY( openBaseline(server, &myTree, version)); }
After the test passes Modify all call sites openBaseline(t, version); becomes openBaseline(DoorsServer::getInstance(), t, version); Add more methods to the interface as necessary Consider cohesion Don’t mindlessly create a monster interface A similar result can be achieved without introducing an interface at all.
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Extract Component CONTEXT All our code has dependency on ‘utility’ functionality Some ‘utility’ functionality has dependencies on core application Leads to linking test with entire codebase SOLUTION Build ‘utility’ functionality as independent component used by application and tests
Tests Application Before Refactoring During Unit Test Interesting Code Application While App Executes main Interesting Code
Simple Extraction Not Enough Interesting Code Utility Functionality Application main Utility code still dependent on app No build time improvement Tests
Break Dependency PROCEDURE Create new interface(s) for dependencies of ‘utility’ class UserNotifier { virtual void notify(char*) =0;  }; Implement interface in application code class DoorsUserNotifier : public UserNotifier { virtual void notify(char*) { … }  }; Inject implementation of interface into ‘utility’ at initialisation DoorsUserNotifier userNotifier; utility.setUserNotifier(userNotifier);
Modify Utility Code Interface registration void Utility::setUserNotifier(UserNotifier notifier) { userNotifier = notifier; } Modify call sites in ‘utility’ to use injected interface If no implementation present (i.e. during unit testing), then use of interface does nothing void Utility::notifyUser(char* message) { if (!userNotifier.isNull())  userNotifier->notify(message); }
Full extraction Utility code is used in many places All test projects will depend on it Package as shared library Reduces build times Helps keep contracts explicit
Tests After Refactoring During Unit Test Utility Functionality <<interface>> Mock Dependencies Application Interesting Code Utility Functionality <<interface>> Application Dependencies Application While App Executes main Interesting Code 1. Inject  Dependencies 2. Run Application
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Original code // startup.c void startup() { db_initialize(); … } // database.h extern void db_initialize(); // database.c void db_initialize() { … } db_initialize startup
How to unit test? We want to test the  startup  method, but we don’t want to use the database How can we test  startup  without calling  db_initialize ? Use preprocessor Use runtime switch Supply ‘mock’ database object The Mocking solution is the most versatile …  but also the most complex
Non-Intrusive C Seam CONTEXT We want to replace some existing functionality The functionality is implemented by procedural C code with no well defined interface We don’t want to modify the ‘client’ code that uses this functionality SOLUTION Create/extract an interface Use C++ namespaces to silently redirect client calls through a factory/shim
Create new interface // Database.h class Database { virtual void initialize() = 0; … . }; db_initialize startup Database
Move legacy code into namespace // database.h namespace Legacy { extern void db_initialize(); } // database.c namespace Legacy { void db_initialize() { … } } startup db_initialize Global namespace Legacy namespace Database
Implement the new interface // LegacyDatabase.h class LegacyDatabase : public Database { void initialize(); }; // LegacyDatabase.cpp void LegacyDatabase::initialize() { Legacy::db_initialize(); } startup Global namespace db_initialize Database LegacyDatabase Legacy namespace
Create a shim // shim.h extern void db_initialize(); // shim.cpp void db_initialize() { Factory::getDatabase() .initialize(); } startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
Redirect client to shim // startup.c #include “shim.h” void startup() { db_initialize(); … } startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
Schematic of transformation db_initialize startup Before After Global namespace startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
What have we achieved? Extracted an interface with minimal changes to client code Original invocation now calls shim code Shim uses factory to select implementation Factory can return a fake or mock object Legacy implementation behaves exactly as before Code can be unit tested independently Alternative implementations of interface can be provided
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
Are we there yet? Move to iterative development All new/modified code to have unit tests All unit tests to be run every build Develop “Whole Team” approach Automated acceptance tests written by test & dev Test to pick up nightly builds Align with Rational toolset
Conclusions New skills/techniques to learn Unit testing is hard Writing testable code is hard Books are not enough… practice needed Up-front refactoring cost Lots of hard work making legacy code testable One step at a time Build times are important to developers But other metrics are equally interesting
Musical trivia You can lead a horse to water, But you can’t make it drink Think about it, All you’ve got to do is think about it There’s no cure. - The Beast, The Only Ones
Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions

More Related Content

PPTX
Applying TDD to Legacy Code
PPTX
TDD and the Legacy Code Black Hole
PPT
Presentation_C++UnitTest
PPT
20111018 boost and gtest
PDF
Modern Python Testing
PPT
Google C++ Testing Framework in Visual Studio 2008
PDF
Python Testing Fundamentals
PDF
Testing Legacy Rails Apps
Applying TDD to Legacy Code
TDD and the Legacy Code Black Hole
Presentation_C++UnitTest
20111018 boost and gtest
Modern Python Testing
Google C++ Testing Framework in Visual Studio 2008
Python Testing Fundamentals
Testing Legacy Rails Apps

What's hot (19)

ODT
Testing in-python-and-pytest-framework
ODP
Automated testing in Python and beyond
 
PDF
Living With Legacy Code
DOCX
Test driven development and unit testing with examples in C++
ODP
Python unit testing
PDF
TDD in Python With Pytest
PPTX
Introduction to JUnit testing in OpenDaylight
PDF
Doing the Impossible
PDF
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
PDF
Mutation testing in Java
PDF
C++ Unit Test with Google Testing Framework
PDF
Unit testing legacy code
ODP
Grails unit testing
KEY
Unit Test Your Database
PPT
Google mock for dummies
PDF
MUTANTS KILLER - PIT: state of the art of mutation testing system
PDF
Unit testing on embedded target with C++Test
PPTX
Refactoring legacy code driven by tests - ENG
PDF
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
Testing in-python-and-pytest-framework
Automated testing in Python and beyond
 
Living With Legacy Code
Test driven development and unit testing with examples in C++
Python unit testing
TDD in Python With Pytest
Introduction to JUnit testing in OpenDaylight
Doing the Impossible
An Introduction to JUnit 5 and how to use it with Spring boot tests and Mockito
Mutation testing in Java
C++ Unit Test with Google Testing Framework
Unit testing legacy code
Grails unit testing
Unit Test Your Database
Google mock for dummies
MUTANTS KILLER - PIT: state of the art of mutation testing system
Unit testing on embedded target with C++Test
Refactoring legacy code driven by tests - ENG
JUnit 5 - Evolution and Innovation - SpringOne Platform 2019
Ad

Similar to Stopping the Rot - Putting Legacy C++ Under Test (20)

PPT
Google test training
PPT
Google mock training
PPTX
Testing, a pragmatic approach
PPTX
Unit testing
PDF
EKON28 - Beyond Legacy Apps with mORMot 2
PPTX
Testing the untestable
PPTX
Loopt unit test experiences
PPTX
Unit tests and TDD
PDF
Working With Legacy Code
PPT
XP through TDD
PPT
Working Effectively With Legacy Code
KEY
Test-Driven Development for TYPO3
PDF
Test and Behaviour Driven Development (TDD/BDD)
PPTX
Practical unit testing in c & c++
PDF
Testing untestable code - ConFoo13
PPTX
presentation des google test dans un contexte de tdd
PPTX
Unit Testing Full@
KEY
Client Side Unit Testing
PDF
Unit testing (eng)
PPTX
An Introduction to unit testing
Google test training
Google mock training
Testing, a pragmatic approach
Unit testing
EKON28 - Beyond Legacy Apps with mORMot 2
Testing the untestable
Loopt unit test experiences
Unit tests and TDD
Working With Legacy Code
XP through TDD
Working Effectively With Legacy Code
Test-Driven Development for TYPO3
Test and Behaviour Driven Development (TDD/BDD)
Practical unit testing in c & c++
Testing untestable code - ConFoo13
presentation des google test dans un contexte de tdd
Unit Testing Full@
Client Side Unit Testing
Unit testing (eng)
An Introduction to unit testing
Ad

More from Seb Rose (20)

PDF
AI and developer obsolescence - BCS 2025.pdf
PDF
Software contracts - Global Enterprise Agile 2023.pdf
PDF
Micro-service delivery - without the pitfalls
PDF
DevSecOps - Agile Get-Together 2022.pdf
PDF
Contract testing - Sealights 2022.pdf
PDF
Example mapping - slice any story into testable examples - SoCraTes 2022.pdf
PDF
Software testing - learning to walk again (expoQA22)
PDF
DevSecOps - Unicom Agile and DevOps Expo (Adaptive Challenges) 2021
PDF
A brief history of requirements - Unicom 2022
PDF
Example mapping (with builds) - ProductWorld 2022
PDF
Example mapping - ProductWorld 2022
PDF
No code, low code, machine code QA ATL 2021
PDF
No code, low code, machine code QA ATL 2021
PDF
No code, low code, machine code - Unicom 2021
PDF
BDD: from soup to nuts - The Future of Work Scotland 2021
PDF
Contrasting test automation and BDD - 2020
PDF
Are BDD and test automation the same thing? Automation Guild 2021
PDF
"Our BDDs are broken!" Lean Agile Exchange 2020
PDF
User stories: from good intentions to bad advice - Agile Scotland 2019
PDF
User stories: from good intentions to bad advice - Lean Agile Scotland 2019
AI and developer obsolescence - BCS 2025.pdf
Software contracts - Global Enterprise Agile 2023.pdf
Micro-service delivery - without the pitfalls
DevSecOps - Agile Get-Together 2022.pdf
Contract testing - Sealights 2022.pdf
Example mapping - slice any story into testable examples - SoCraTes 2022.pdf
Software testing - learning to walk again (expoQA22)
DevSecOps - Unicom Agile and DevOps Expo (Adaptive Challenges) 2021
A brief history of requirements - Unicom 2022
Example mapping (with builds) - ProductWorld 2022
Example mapping - ProductWorld 2022
No code, low code, machine code QA ATL 2021
No code, low code, machine code QA ATL 2021
No code, low code, machine code - Unicom 2021
BDD: from soup to nuts - The Future of Work Scotland 2021
Contrasting test automation and BDD - 2020
Are BDD and test automation the same thing? Automation Guild 2021
"Our BDDs are broken!" Lean Agile Exchange 2020
User stories: from good intentions to bad advice - Agile Scotland 2019
User stories: from good intentions to bad advice - Lean Agile Scotland 2019

Recently uploaded (20)

PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
Cloud computing and distributed systems.
PPT
Teaching material agriculture food technology
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
A Presentation on Artificial Intelligence
PDF
Electronic commerce courselecture one. Pdf
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
Big Data Technologies - Introduction.pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Empathic Computing: Creating Shared Understanding
PDF
KodekX | Application Modernization Development
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Machine learning based COVID-19 study performance prediction
PDF
Unlocking AI with Model Context Protocol (MCP)
Mobile App Security Testing_ A Comprehensive Guide.pdf
Cloud computing and distributed systems.
Teaching material agriculture food technology
Encapsulation_ Review paper, used for researhc scholars
Reach Out and Touch Someone: Haptics and Empathic Computing
A Presentation on Artificial Intelligence
Electronic commerce courselecture one. Pdf
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Digital-Transformation-Roadmap-for-Companies.pptx
Big Data Technologies - Introduction.pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Empathic Computing: Creating Shared Understanding
KodekX | Application Modernization Development
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Understanding_Digital_Forensics_Presentation.pptx
Advanced methodologies resolving dimensionality complications for autism neur...
Machine learning based COVID-19 study performance prediction
Unlocking AI with Model Context Protocol (MCP)

Stopping the Rot - Putting Legacy C++ Under Test

  • 1. Stopping the rot Putting legacy C++ under test Seb Rose ACCU 2011
  • 2. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 3. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 4. A brief history of DOORS Developed in C in early 1990s Home grown cross platform GUI Heavy use of pre-processor macros Server and client share codebase Ported to C++ in 1999 No unit tests – ever DXL extension language tests brittle Success led to rapid team growth Proliferation of products and integrations
  • 5. Challenges Highly coupled code Long build times Developer ‘silos’ SRD - “Big Design Up Front” Long manual regression test ‘tail’ Hard to make modifications without errors No experience writing unit tests
  • 6. New direction Move to iterative development Implementation driven by User Stories not SRD All new/modified code to have unit tests All unit tests to be run every build Nightly builds CI server Develop “Whole Team” approach Automated acceptance tests written by test & dev Test to pick up nightly builds Align with Rational toolset
  • 7. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 8. Why Unit Test? Greater confidence than Buddy check only Fewer regressions Tests as documentation don’t get out of step with the code “ Legacy Code is code without Unit Tests” – Michael Feathers Can drive out clean designs
  • 9. When to write Unit Tests? ALWAYS Test Before (TDD) tends to lead to cleaner interfaces Test After tends to miss some test cases & takes longer For TDD to work the component under test & the tests must build fast (< 1 minute) You CAN make this possible Componentise Partition
  • 10. Unit Test guidelines Only test a single behaviour Use descriptive names (as long as necessary) Group related tests Do not make tests brittle Treat tests just like production code Refactor to remove redundancy & improve architecture Adhere to all coding standards Tests are documentation They must ‘read well’
  • 11. How to write the first Unit Test Major refactoring needed to put “seams” in place Patterns used extensively for initial refactoring: “Working Effectively With Legacy Code” Link errors in unit test build point to unwanted dependencies Replace dependencies with ‘injected’ mock/fake objects … … until you really are UNIT testing.
  • 12. A test is not a unit test if: It talks to the database It communicates across the network It touches the file system It can’t run at the same time as other unit tests You have to do special things to your environment (such as editing config files) to run it (Michael Feathers’ blog, 2005)
  • 13. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 14. Which framework to use? We chose Googletest & Googlemock Available from Googlecode Very liberal open source license Cross platform Can use independently, but work together “out of the box” Implemented using macros & templates Easy to learn Well documented
  • 15. Googletest No need to register tests Builds as command line executable Familiar to users of xUnit: Suites Fixtures SetUp, TearDown Filters to enable running subsets Handles exceptions
  • 16. Googlemock Feature-rich Dependency on C++ TC1, but can use Boost Extensible matching operators Declarative style (using operator chaining) Sequencing can be enforced Use of templates slows build time Can only mock virtual methods Still need to declare mock interface Inconvenient to mock operators, destructors & vararg
  • 17. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 18. The first test TEST(HttpResponse, default_response_code_should_be_unset ) { HttpResponse response ; ASSERT_EQ( HttpResponse::Unset, response.getCode ()); }
  • 19. The first mock (1) class RestfulServer { virtual bool doesDirectoryExist(const std::string& name) = 0; virtual bool doesResourceExist(const std::string& name) = 0; }; class M ockRestfulServer : public RestfulServer { MOCK_METHOD1(doesDirectoryExist, bool(const std::string& name)); MOCK_METHOD1(doesResourceExist, bool(const std::string& name)); };
  • 20. The first mock (2) TEST(JazzProxy_fileExists, should _r eturn _t rue _i f _directory_e xists) { MockRestfulServer mockServer; Proxy p roxy(mockServer); EXPECT_CALL(mockServer, doesDirectoryExist( _ )) .WillOnce(Return(true)); EXPECT_CALL(mockServer, doesResourceExist(_)) .Times(0); bool exists = false; ASSERT_NO_THROW(proxy. fileExists(“ myFolder &quot;, exists) ) ; ASSERT_TRUE(exists); }
  • 21. Another Mock (1) HttpTimer::~HttpTimer() { if (theLogger.getLevel() >= LOG_LEVEL_WARNING) theLogger.writeLn(“ Timer: %d ms&quot;, stopClock()); } class Logger { public: virtual ~Logger(); // Operations for logging textual entries to a log file. virtual unsigned getLevel() const = 0; virtual void write ( const char* fmt, ...) = 0; virtual void writeLn(const char* fmt, ...) = 0; };
  • 22. Another Mock (2) class MockLogger : public Logger { public: MOCK_CONST_METHOD0(getLevel, unsigned int ()); void write(const char* fmt, ...) {}; void writeLn(const char* fmt, ...) { va_list ap; va_start(ap, fmt); DWORD clock = va_arg(ap, DWORD); va_end(ap); mockWriteLn(fmt, clock); } MOCK_METHOD 2 (mockWriteLn, void(const char*, DWORD)); } ;
  • 23. Another Mock (3) TEST(HttpTimer, writes_to_logger_if_log_level_is_at_warning ) { MockLogger testLogger; EXPECT_CALL(testLogger, getLevel()) .Will Once (Return( LOG_LEVEL_WARNING )); EXPECT_CALL(testLogger, mockWriteLn( _, _ )) .Times(1) ; HttpTimer timer ( testLogger); }
  • 24. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 25. Wrap Dependency CONTEXT We want to test some legacy code The legacy code has an ugly dependency Requires inclusion of code we don’t want to test SOLUTION Create an interface that describes behaviour of dependency Re-write call to inject dependency In test code inject a test double
  • 26. Test Doubles Dummy: never used – only passed around to fill parameter list Stub: provides canned responses Fake: has simplified implementation Mock: object pre-programmed with expectations – the specification of calls they are expected to receive “ Test Double”: generic term for any of the above
  • 27. Code Under Test tree* openBaseline(tree *module, VersionId version) { tree *baseline = NULL; … BaselineId baselineId = DoorsServer::getInstance().findBaseline( module, version); … return baseline; }
  • 28. Test The Defect TEST(OpenBaseline, opening_a_baseline_with_default_version_should_throw) { tree myTree; VersionId version; ASSERT_THROWS_ANY(openBaseline(&myTree, version)); } Won’t link without inclusion of DoorsServer
  • 29. Describe Behaviour class Server { virtual BaselineId findBaseline(tree*, VersionId) = 0; } class DoorsServer : public Server { … BaselineId findBaseline(tree*, VersionId); … }
  • 30. Refactor Code Under Test tree* openBaseline( Server& server, tree *module, VersionId version) { tree *baseline = NULL; … BaselineId baselineId = server.findBaseline( module, version); … return baseline; }
  • 31. Modify the Test class TestServer : public Server{ BaselineId findBaseline(tree*, VersionId) { return BaselineId(); } }; TEST(OpenBaseline, opening_a_baseline_with_default_version_should_throw) { TestServer server; tree myTree; VersionId version; ASSERT_THROWS_ANY( openBaseline(server, &myTree, version)); }
  • 32. After the test passes Modify all call sites openBaseline(t, version); becomes openBaseline(DoorsServer::getInstance(), t, version); Add more methods to the interface as necessary Consider cohesion Don’t mindlessly create a monster interface A similar result can be achieved without introducing an interface at all.
  • 33. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 34. Extract Component CONTEXT All our code has dependency on ‘utility’ functionality Some ‘utility’ functionality has dependencies on core application Leads to linking test with entire codebase SOLUTION Build ‘utility’ functionality as independent component used by application and tests
  • 35. Tests Application Before Refactoring During Unit Test Interesting Code Application While App Executes main Interesting Code
  • 36. Simple Extraction Not Enough Interesting Code Utility Functionality Application main Utility code still dependent on app No build time improvement Tests
  • 37. Break Dependency PROCEDURE Create new interface(s) for dependencies of ‘utility’ class UserNotifier { virtual void notify(char*) =0; }; Implement interface in application code class DoorsUserNotifier : public UserNotifier { virtual void notify(char*) { … } }; Inject implementation of interface into ‘utility’ at initialisation DoorsUserNotifier userNotifier; utility.setUserNotifier(userNotifier);
  • 38. Modify Utility Code Interface registration void Utility::setUserNotifier(UserNotifier notifier) { userNotifier = notifier; } Modify call sites in ‘utility’ to use injected interface If no implementation present (i.e. during unit testing), then use of interface does nothing void Utility::notifyUser(char* message) { if (!userNotifier.isNull()) userNotifier->notify(message); }
  • 39. Full extraction Utility code is used in many places All test projects will depend on it Package as shared library Reduces build times Helps keep contracts explicit
  • 40. Tests After Refactoring During Unit Test Utility Functionality <<interface>> Mock Dependencies Application Interesting Code Utility Functionality <<interface>> Application Dependencies Application While App Executes main Interesting Code 1. Inject Dependencies 2. Run Application
  • 41. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 42. Original code // startup.c void startup() { db_initialize(); … } // database.h extern void db_initialize(); // database.c void db_initialize() { … } db_initialize startup
  • 43. How to unit test? We want to test the startup method, but we don’t want to use the database How can we test startup without calling db_initialize ? Use preprocessor Use runtime switch Supply ‘mock’ database object The Mocking solution is the most versatile … but also the most complex
  • 44. Non-Intrusive C Seam CONTEXT We want to replace some existing functionality The functionality is implemented by procedural C code with no well defined interface We don’t want to modify the ‘client’ code that uses this functionality SOLUTION Create/extract an interface Use C++ namespaces to silently redirect client calls through a factory/shim
  • 45. Create new interface // Database.h class Database { virtual void initialize() = 0; … . }; db_initialize startup Database
  • 46. Move legacy code into namespace // database.h namespace Legacy { extern void db_initialize(); } // database.c namespace Legacy { void db_initialize() { … } } startup db_initialize Global namespace Legacy namespace Database
  • 47. Implement the new interface // LegacyDatabase.h class LegacyDatabase : public Database { void initialize(); }; // LegacyDatabase.cpp void LegacyDatabase::initialize() { Legacy::db_initialize(); } startup Global namespace db_initialize Database LegacyDatabase Legacy namespace
  • 48. Create a shim // shim.h extern void db_initialize(); // shim.cpp void db_initialize() { Factory::getDatabase() .initialize(); } startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
  • 49. Redirect client to shim // startup.c #include “shim.h” void startup() { db_initialize(); … } startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
  • 50. Schematic of transformation db_initialize startup Before After Global namespace startup db_initialize shim Database LegacyDatabase Global namespace Legacy namespace
  • 51. What have we achieved? Extracted an interface with minimal changes to client code Original invocation now calls shim code Shim uses factory to select implementation Factory can return a fake or mock object Legacy implementation behaves exactly as before Code can be unit tested independently Alternative implementations of interface can be provided
  • 52. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions
  • 53. Are we there yet? Move to iterative development All new/modified code to have unit tests All unit tests to be run every build Develop “Whole Team” approach Automated acceptance tests written by test & dev Test to pick up nightly builds Align with Rational toolset
  • 54. Conclusions New skills/techniques to learn Unit testing is hard Writing testable code is hard Books are not enough… practice needed Up-front refactoring cost Lots of hard work making legacy code testable One step at a time Build times are important to developers But other metrics are equally interesting
  • 55. Musical trivia You can lead a horse to water, But you can’t make it drink Think about it, All you’ve got to do is think about it There’s no cure. - The Beast, The Only Ones
  • 56. Agenda Background Unit Testing Frameworks Test & Mock: First Examples Refactoring: Wrap Dependency Refactoring: Extract Component Refactoring: Non-Intrusive C Seam Conclusions Questions

Editor's Notes

  • #6: Spaghetti code; Big Ball of Mud
  • #7: “ Islands of test”
  • #54: Spaghetti code; Big Ball of Mud