29f2faaed58bb05d73e3cfbecc9e8573.ppt
- Количество слайдов: 56
® IBM Software Group Stopping the rot Putting legacy C++ under test Seb Rose ACCU 2011 Innovation for a smarter planet © 2009 IBM Corporation
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 2
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 3
IBM Software Group | Rational software A brief history of DOORS § Developed in C in early 1990 s § 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 Innovation for a smarter planet 4
IBM Software Group | Rational software 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 Innovation for a smarter planet 5
IBM Software Group | Rational software New direction § Move to iterative development 4 Implementation driven by User Stories not SRD § All new/modified code to have unit tests § All unit tests to be run every build 4 Nightly builds 4 CI server § Develop “Whole Team” approach 4 Automated acceptance tests written by test & dev 4 Test to pick up nightly builds § Align with Rational toolset Innovation for a smarter planet 6
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 7
IBM Software Group | Rational software Why Unit Test? § Greater confidence than Buddy check only § Fewer regressions § Tests as documentation 4 don’t get out of step with the code § “Legacy Code is code without Unit Tests” – Michael Feathers § Can drive out clean designs Innovation for a smarter planet 8
IBM Software Group | Rational software 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 4 Componentise 4 Partition Innovation for a smarter planet 9
IBM Software Group | Rational software 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 4 Refactor to remove redundancy & improve architecture 4 Adhere to all coding standards § Tests are documentation 4 They must ‘read well’ Innovation for a smarter planet 10
IBM Software Group | Rational software 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. Innovation for a smarter planet 11
IBM Software Group | Rational software 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) Innovation for a smarter planet 12
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 13
IBM Software Group | Rational software 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 Innovation for a smarter planet 14
IBM Software Group | Rational software Googletest § No need to register tests § Builds as command line executable § Familiar to users of x. Unit: 4 Suites 4 Fixtures 4 Set. Up, Tear. Down 4 Filters to enable running subsets 4 Handles exceptions Innovation for a smarter planet 15
IBM Software Group | Rational software Googlemock § Feature-rich § Dependency on C++ TC 1, 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 Innovation for a smarter planet 16
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 17
IBM Software Group | Rational software The first test TEST(Http. Response, default_response_code_should_be_unset) { Http. Response response; ASSERT_EQ(Http. Response: : Unset, response. get. Code()); } Innovation for a smarter planet 18
IBM Software Group | Rational software The first mock (1) class Restful. Server { virtual bool does. Directory. Exist(const std: : string& name) = 0; virtual bool does. Resource. Exist(const std: : string& name) = 0; }; class Mock. Restful. Server : public Restful. Server { MOCK_METHOD 1(does. Directory. Exist, bool(const std: : string& name)); MOCK_METHOD 1(does. Resource. Exist, bool(const std: : string& name)); }; Innovation for a smarter planet 19
IBM Software Group | Rational software The first mock (2) TEST(Jazz. Proxy_file. Exists, should_return_true_if_directory_exists) { Mock. Restful. Server mock. Server; Proxy proxy(mock. Server); EXPECT_CALL(mock. Server, does. Directory. Exist(_)). Will. Once(Return(true)); EXPECT_CALL(mock. Server, does. Resource. Exist(_)). Times(0); bool exists = false; ASSERT_NO_THROW(proxy. file. Exists(“my. Folder", exists) ); ASSERT_TRUE(exists); } Innovation for a smarter planet 20
IBM Software Group | Rational software Another Mock (1) Http. Timer: : ~Http. Timer() { if (the. Logger. get. Level() >= LOG_LEVEL_WARNING) the. Logger. write. Ln(“Timer: %d ms", stop. Clock()); } class Logger { public: virtual ~Logger(); // Operations for logging textual entries to a log file. virtual unsigned get. Level() const = 0; virtual void write(const char* fmt, . . . ) = 0; virtual void write. Ln(const char* fmt, . . . ) = 0; }; Innovation for a smarter planet 21
IBM Software Group | Rational software Another Mock (2) class Mock. Logger : public Logger { public: MOCK_CONST_METHOD 0(get. Level, unsigned int()); void write(const char* fmt, . . . ) {}; void write. Ln(const char* fmt, . . . ) { va_list ap; va_start(ap, fmt); DWORD clock = va_arg(ap, DWORD); va_end(ap); mock. Write. Ln(fmt, clock); } MOCK_METHOD 2(mock. Write. Ln, void(const char*, DWORD)); }; Innovation for a smarter planet 22
IBM Software Group | Rational software Another Mock (3) TEST(Http. Timer, writes_to_logger_if_log_level_is_at_warning) { Mock. Logger test. Logger; EXPECT_CALL(test. Logger, get. Level()). Will. Once(Return(LOG_LEVEL_WARNING)); EXPECT_CALL(test. Logger, mock. Write. Ln( _, _)). Times(1); Http. Timer timer(test. Logger); } Innovation for a smarter planet 23
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 24
IBM Software Group | Rational software Wrap Dependency CONTEXT § We want to test some legacy code § The legacy code has an ugly dependency 4 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 Innovation for a smarter planet 25
IBM Software Group | Rational software 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 Innovation for a smarter planet 26
IBM Software Group | Rational software Code Under Test tree* open. Baseline(tree *module, Version. Id version) { tree *baseline = NULL; … Baseline. Id baseline. Id = Doors. Server: : get. Instance(). find. Baseline( module, version); … return baseline; } Innovation for a smarter planet 27
IBM Software Group | Rational software Test The Defect TEST(Open. Baseline, opening_a_baseline_with_default_version_should_throw) { tree my. Tree; Version. Id version; ASSERT_THROWS_ANY(open. Baseline(&my. Tree, version)); } § Won’t link without inclusion of Doors. Server Innovation for a smarter planet 28
IBM Software Group | Rational software Describe Behaviour class Server { virtual Baseline. Id find. Baseline(tree*, Version. Id) = 0; } class Doors. Server : public Server { … Baseline. Id find. Baseline(tree*, Version. Id); … } Innovation for a smarter planet 29
IBM Software Group | Rational software Refactor Code Under Test tree* open. Baseline( Server& server, tree *module, Version. Id version) { tree *baseline = NULL; … Baseline. Id baseline. Id = server. find. Baseline( module, version); … return baseline; } Innovation for a smarter planet 30
IBM Software Group | Rational software Modify the Test class Test. Server : public Server{ Baseline. Id find. Baseline(tree*, Version. Id) { return Baseline. Id(); } }; TEST(Open. Baseline, opening_a_baseline_with_default_version_should_throw) { Test. Server server; tree my. Tree; Version. Id version; ASSERT_THROWS_ANY( open. Baseline(server, &my. Tree, version)); } Innovation for a smarter planet 31
IBM Software Group | Rational software After the test passes § Modify all call sites open. Baseline(t, version); becomes open. Baseline(Doors. Server: : get. Instance(), t, version); § Add more methods to the interface as necessary 4 Consider cohesion 4 Don’t mindlessly create a monster interface § A similar result can be achieved without introducing an interface at all. Innovation for a smarter planet 32
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 33
IBM Software Group | Rational software 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 4 used by application and tests Innovation for a smarter planet 34
IBM Software Group | Rational software Before Refactoring While App Executes Application Interesting Code During Unit Test Application Interesting Code main Tests Innovation for a smarter planet 35
IBM Software Group | Rational software Simple Extraction Not Enough Application Utility Functionality Interesting Code main § Utility code still dependent on app § No build time improvement Tests Innovation for a smarter planet 36
IBM Software Group | Rational software Break Dependency PROCEDURE § Create new interface(s) for dependencies of ‘utility’ class User. Notifier { virtual void notify(char*) =0; }; § Implement interface in application code class Doors. User. Notifier : public User. Notifier { virtual void notify(char*) { … } }; § Inject implementation of interface into ‘utility’ at initialisation Doors. User. Notifier user. Notifier; utility. set. User. Notifier(user. Notifier); Innovation for a smarter planet 37
IBM Software Group | Rational software Modify Utility Code § Interface registration void Utility: : set. User. Notifier(User. Notifier notifier) { user. Notifier = 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: : notify. User(char* message) { if (!user. Notifier. is. Null()) user. Notifier->notify(message); } Innovation for a smarter planet 38
IBM Software Group | Rational software Full extraction § Utility code is used in many places § All test projects will depend on it § Package as shared library 4 Reduces build times 4 Helps keep contracts explicit Innovation for a smarter planet 39
IBM Software Group | Rational software After Refactoring While App Executes During Unit Test <<interface>> Application Dependencies <<interface>> Mock Dependencies Application Utility Functionality Interesting Code 2. Run Application main 1. Inject Dependencies Tests Innovation for a smarter planet 40
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 41
IBM Software Group | Rational software Original code // startup. c void startup() { db_initialize(); … db_initialize } // database. h startup extern void db_initialize(); // database. c void db_initialize() { … } Innovation for a smarter planet 42
IBM Software Group | Rational software 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? 4 Use preprocessor 4 Use runtime switch 4 Supply ‘mock’ database object § The Mocking solution is the most versatile 4… but also the most complex Innovation for a smarter planet 43
IBM Software Group | Rational software 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 Innovation for a smarter planet 44
IBM Software Group | Rational software Create new interface // Database. h class Database { Database virtual void initialize() = 0; …. db_initialize }; startup Innovation for a smarter planet 45
IBM Software Group | Rational software Move legacy code into namespace // database. h namespace Legacy { extern void db_initialize(); } Database // database. c namespace Legacy { void db_initialize() startup db_initialize { … Global namespace Legacy namespace } } Innovation for a smarter planet 46
IBM Software Group | Rational software Implement the new interface // Legacy. Database. h class Legacy. Database : public Database { void initialize(); Database }; Legacy. Database // Legacy. Database. cpp void Legacy. Database: : initialize() startup { Legacy: : db_initialize(); } Global namespace Innovation for a smarter planet db_initialize Legacy namespace 47
IBM Software Group | Rational software Create a shim // shim. h extern void db_initialize(); Database // shim. cpp void db_initialize() shim { Legacy. Database Factory: : get. Database(). initialize(); startup db_initialize } Global namespace Innovation for a smarter planet Legacy namespace 48
IBM Software Group | Rational software Redirect client to shim // startup. c #include “shim. h” Database void startup() shim { Legacy. Database db_initialize(); … startup db_initialize } Global namespace Innovation for a smarter planet Legacy namespace 49
IBM Software Group | Rational software Schematic of transformation Before After Database db_initialize shim startup Legacy. Database db_initialize Global namespace Innovation for a smarter planet Legacy namespace 50
IBM Software Group | Rational software 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 Innovation for a smarter planet 51
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 52
IBM Software Group | Rational software 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 4 Automated acceptance tests written by test & dev 4 Test to pick up nightly builds § Align with Rational toolset Innovation for a smarter planet 53
IBM Software Group | Rational software Conclusions § New skills/techniques to learn 4 Unit testing is hard 4 Writing testable code is hard 4 Books are not enough… practice needed § Up-front refactoring cost 4 Lots of hard work making legacy code testable 4 One step at a time § Build times are important to developers 4 But other metrics are equally interesting Innovation for a smarter planet 54
IBM Software Group | Rational software 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 Innovation for a smarter planet 55
IBM Software Group | Rational software Agenda § Background § Unit Testing § Frameworks § Test & Mock: First Examples § Refactoring: Wrap Dependency § Refactoring: Extract Component § Refactoring: Non-Intrusive C Seam § Conclusions § Questions Innovation for a smarter planet 56
29f2faaed58bb05d73e3cfbecc9e8573.ppt