Your SlideShare is downloading. ×
0
Stopping the rot Putting legacy C++ under test Seb Rose ACCU 2011
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
A brief history of DOORS <ul><li>Developed in C in early 1990s </li></ul><ul><li>Home grown cross platform GUI </li></ul><...
Challenges <ul><li>Highly coupled code </li></ul><ul><li>Long build times </li></ul><ul><li>Developer ‘silos’ </li></ul><u...
New direction <ul><li>Move to iterative development </li></ul><ul><ul><li>Implementation driven by User Stories not SRD </...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Why Unit Test? <ul><li>Greater confidence than Buddy check only </li></ul><ul><li>Fewer regressions </li></ul><ul><li>Test...
When to write Unit Tests? <ul><li>ALWAYS </li></ul><ul><li>Test Before (TDD) tends to lead to cleaner interfaces </li></ul...
Unit Test guidelines <ul><li>Only test a single behaviour </li></ul><ul><li>Use descriptive names (as long as necessary) <...
How to write the first Unit Test <ul><li>Major refactoring needed to put “seams” in place </li></ul><ul><li>Patterns used ...
A test is not a unit test if: <ul><li>It talks to the database </li></ul><ul><li>It communicates across the network </li><...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Which framework to use? <ul><li>We chose Googletest & Googlemock </li></ul><ul><li>Available from Googlecode </li></ul><ul...
Googletest <ul><li>No need to register tests </li></ul><ul><li>Builds as command line executable </li></ul><ul><li>Familia...
Googlemock <ul><li>Feature-rich </li></ul><ul><li>Dependency on C++ TC1, but can use Boost </li></ul><ul><li>Extensible ma...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
The first test <ul><li>TEST(HttpResponse,  default_response_code_should_be_unset ) </li></ul><ul><li>{ </li></ul><ul><li>H...
The first mock (1) <ul><li>class RestfulServer </li></ul><ul><li>{ </li></ul><ul><li>virtual bool doesDirectoryExist(const...
The first mock (2) <ul><li>TEST(JazzProxy_fileExists, should _r eturn _t rue _i f _directory_e xists)  </li></ul><ul><li>{...
Another Mock (1) <ul><li>HttpTimer::~HttpTimer() </li></ul><ul><li>{ </li></ul><ul><li>if (theLogger.getLevel() >= LOG_LEV...
Another Mock (2) <ul><li>class MockLogger : public Logger </li></ul><ul><li>{ </li></ul><ul><li>public: </li></ul><ul><li>...
Another Mock (3) <ul><li>TEST(HttpTimer,  writes_to_logger_if_log_level_is_at_warning ) </li></ul><ul><li>{ </li></ul><ul>...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Wrap Dependency <ul><li>CONTEXT </li></ul><ul><li>We want to test some legacy code </li></ul><ul><li>The legacy code has a...
Test Doubles <ul><li>Dummy: never used – only passed around to fill parameter list </li></ul><ul><li>Stub: provides canned...
Code Under Test <ul><li>tree* openBaseline(tree *module, VersionId version) </li></ul><ul><li>{ </li></ul><ul><li>tree *ba...
Test The Defect <ul><li>TEST(OpenBaseline, opening_a_baseline_with_default_version_should_throw) </li></ul><ul><li>{ </li>...
Describe Behaviour <ul><li>class Server </li></ul><ul><li>{ </li></ul><ul><li>virtual BaselineId findBaseline(tree*, Versi...
Refactor Code Under Test <ul><li>tree* openBaseline( Server& server, tree *module,  VersionId version) </li></ul><ul><li>{...
Modify the Test <ul><li>class TestServer : public Server{ </li></ul><ul><li>BaselineId findBaseline(tree*, VersionId) { re...
After the test passes <ul><li>Modify all call sites </li></ul><ul><ul><li>openBaseline(t, version); </li></ul></ul><ul><ul...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Extract Component <ul><li>CONTEXT </li></ul><ul><li>All our code has dependency on ‘utility’ functionality </li></ul><ul><...
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 <ul><li>Utility code still dependent ...
Break Dependency <ul><li>PROCEDURE </li></ul><ul><li>Create new interface(s) for dependencies of ‘utility’ </li></ul><ul><...
Modify Utility Code <ul><li>Interface registration </li></ul><ul><li>void Utility::setUserNotifier(UserNotifier notifier) ...
Full extraction <ul><li>Utility code is used in many places </li></ul><ul><li>All test projects will depend on it </li></u...
Tests After Refactoring During Unit Test Utility Functionality <<interface>> Mock Dependencies Application Interesting Cod...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Original code <ul><li>// startup.c </li></ul><ul><li>void startup() </li></ul><ul><li>{ </li></ul><ul><li>db_initialize();...
How to unit test? <ul><li>We want to test the  startup  method, but we don’t want to use the database </li></ul><ul><li>Ho...
Non-Intrusive C Seam <ul><li>CONTEXT </li></ul><ul><li>We want to replace some existing functionality </li></ul><ul><li>Th...
Create new interface <ul><li>// Database.h </li></ul><ul><li>class Database </li></ul><ul><li>{ </li></ul><ul><li>virtual ...
Move legacy code into namespace <ul><li>// database.h </li></ul><ul><li>namespace Legacy </li></ul><ul><li>{ </li></ul><ul...
Implement the new interface <ul><li>// LegacyDatabase.h </li></ul><ul><li>class LegacyDatabase : public Database </li></ul...
Create a shim <ul><li>// shim.h </li></ul><ul><li>extern void db_initialize(); </li></ul><ul><li>// shim.cpp </li></ul><ul...
Redirect client to shim <ul><li>// startup.c </li></ul><ul><li>#include “shim.h” </li></ul><ul><li>void startup() </li></u...
Schematic of transformation db_initialize startup Before After Global namespace startup db_initialize shim Database Legacy...
What have we achieved? <ul><li>Extracted an interface with minimal changes to client code </li></ul><ul><li>Original invoc...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Are we there yet? <ul><li>Move to iterative development </li></ul><ul><li>All new/modified code to have unit tests </li></...
Conclusions <ul><li>New skills/techniques to learn </li></ul><ul><ul><li>Unit testing is hard </li></ul></ul><ul><ul><li>W...
Musical trivia <ul><li>You can lead a horse to water, </li></ul><ul><li>But you can’t make it drink </li></ul><ul><li>Thin...
Agenda <ul><li>Background </li></ul><ul><li>Unit Testing </li></ul><ul><li>Frameworks </li></ul><ul><li>Test & Mock: First...
Upcoming SlideShare
Loading in...5
×

Stopping the Rot - Putting Legacy C++ Under Test

3,681

Published on

Presentation given at the ACCU 2011 Conference in Oxford, UK.

Case study of applying unit test to the DOORS codebase. Includes a quick overview of unit test & the Google Test and Mock libraries. Also 3 specific refactoring examples shown.

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,681
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
57
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide
  • Spaghetti code; Big Ball of Mud
  • “ Islands of test”
  • Spaghetti code; Big Ball of Mud
  • Transcript of "Stopping the Rot - Putting Legacy C++ Under Test"

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

      Clipping is a handy way to collect important slides you want to go back to later.

    ×