Your SlideShare is downloading. ×
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Stopping the Rot - Putting Legacy C++ Under Test
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Stopping the Rot - Putting Legacy C++ Under Test

3,585

Published on

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

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
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
3,585
On Slideshare
0
From Embeds
0
Number of Embeds
6
Actions
Shares
0
Downloads
56
Comments
0
Likes
4
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide
  • Spaghetti code; Big Ball of Mud
  • “ Islands of test”
  • Spaghetti code; Big Ball of Mud
  • Transcript

    • 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

    ×