350 likes | 479 Vues
Designing For Testability Testable designs in the real world. Roy Osherove Principal, Team Agile Blog : ISerializable.com. Why Should I Care?. Maintainable System Extensible System Code Quality. Automated Unit/FIT Testing Is Important. Leads to better design Extensible System
E N D
Designing For TestabilityTestable designs in the real world Roy Osherove Principal, Team Agile Blog: ISerializable.com
Why Should I Care? Maintainable System Extensible System Code Quality
Automated Unit/FIT Testing Is Important • Leads to better design • Extensible System • Helps with Regression Testing • Maintainable System • Finds bugs early • Code Quality • Better understanding of the requirements • API Documentation • “Executable” Requirements
Testing in Context • Engineering methodologies (waterfall for example..) • Agile methodologies • XP • Automated Unit Testing • Test Driven Development • Customer Testing (FIT) • The Crystal family • MSF-Agile • Highsmith's ASD (Adaptive Software Development) • Scrum • Feature Driven Development • DSDM (Dynamic Systems Development Method) • Rational Unified Process? (dX) • …
A Unit Test is a testof a small functional piece of code Public bool IsLoginOK(string user, string password) { //………………………… }
What’s an automated unit test? • Runs without intervention • Anyone can run it • Can run as part of a daily build process
The xUnit Frameworks • Original was for SmallTalk • Kent Beck and Erich Gamma • Ported to Various languages and platforms • JUnit, CppUnit, DUnit, VBUnit, RUnit, PyUnit, Sunit, HtmlUnit, … • Good list at www.xprogramming.com • Standard test architecture • Introducing NUnit
Implementing a test caseNUnit [Test] Public void Add_2Numbers_ReturnsSum() { Calculator c = new Calculator(); int result = c.Add(1,2); Assert.AreEqual(3,result,”Sum of 1 and 2 was wrong”) }
The FIT Framework • Customer Testing • Uses HTML Tables • Allows testing “flows” • Requires “Adapter” classes to execute the system under test
Unit Level Testing Requires Testability • Testable Code Design • Allows Unit Testing • FIT Testing • Test Driven Development • Creates Testable code • Design Up front • ?
What is a Unit-Testable System • For each piece of coded logic in the system, a unit test can be written easily enough to verify it works as expected while keeping the PC-COF rules • Partial runs are possible • Configuration is not needed • Consistent pass/fail result • Order does not matter • Fast
Logic: Throw if Bad validation Return true/false based on … bool CanPurchase(Person p) { If(Validator.IsNotValid(p) { throw new Exception(“…”); } return p. SSID !=null&& p.SubscriptionType!=null && p.CreditOnFile>0; } Consistent pass/fail result for each test Problems: Validator has a bug How do you control Validator to validate or not?
Possible solution: Extract Interface for Validator Send in a “fake” validator IValidator validator; bool CanPurchase(Person p) { If(this.validator.IsNotValid(p) { throw new Exception(“…”); } return p. SSID !=null&& p.SubscriptionType!=null && p.CreditOnFile>0; } Testable designs use Interfaces
Demo Interface based design & testing
Logic: Throw if Bad validation Return true/false based on … bool CanPurchase(Person p) { If(Validator.IsNotValid(p) { throw new Exception(“…”); } return p. SSID !=null&& p.SubscriptionType!=null && p.CreditOnFile>0; } Fast run times Problems: Validator takes a long time
Config file is needed String[] Foo(Person p) { String connectionString= Settings.ConnectionString; … } No Configuration needed
Extract dependency into virtual method Override method in derived class Used derived class in tests String[] Foo(Person p) { String connectionString= getConnectionString(); … } Protected virtual string getConnectionString() { … } Solving Configuration with override
Demo Inherit & Override
Verify insert works Verify Delete Order: Insert has to come before delete test? Delete Before Insert test? Rollback database state between each test to make order irrelevant. Is that easy? Is it a unit test? bool Insert(Person p) { //insert person to database } bool Delete(Person p) { //Delete person from database } Test Order does not matterfor consistent results
Verify only Delete Works Order: Will is work just the same as running all tests? Rollback database state between each test to make order irrelevant. Is that easy? Is it a unit test? bool Insert(Person p) { //insert person to database } bool Delete(Person p) { //Delete person from database } Partial runs of tests produce consistent results
Are Singletons Evil? • Single Responsibility • Global Variables • Tight Coupling • State that lasts forever • Threading?
The GOD Method • One huge do-it-all method • Lots of little private ones perhaps • The design smell • Should you test private methods? • TDD evolves into private tested methods
Singletons & statics Hard coded dependencies Configuration based objects Constructors that use external dependencies Private Stateless Methods Convert static to instance Extract Interface Use Fake Inherit & override Use TestableXXX Extract hard coded dependency to different class Extract to helper class or static helper method Pitfalls and traps
Demos Hard coded dependencies statics Private Helper Methods Constructors that use external dependencies
Test Driven Dev/Design • Tests have to use a testable class • Test-first means testable-design-first • Decoupled design and architecture • More control on state simulations
BDUF • Design just enough to have something to build • Note that it will likely change in the future • Design as you go and change the master plan if needed
Decoupled Design • Maintainable • Extensible • Testable • Replaceable parts • More Agile – Dealing with design changes becomes easier
Service Oriented Design • Decoupled Services • Independently Testable • External Dependencies easily replaceable
Approaching legacy code & design • Step 1 – write integration tests • Step 2- Refactor into testability • Step 3 – Write unit tests • Design evolves • Book: “Working effectively with legacy code” Michael Feathers
Design Guidelines • Interface based Designs • Try to avoid Singletons • Avoid GOD Methods – Use Polymorphism instead • Virtual by default • Use Factory methods or classes instead of hard coded initializations • Single Responsibility for class and method
Key points • Design for testability leads to Agility • Common patterns • Recognizing test challenges in the design • Refactoring the design for testability • Test Driven Development leads to a testable design • Testable code == Well designed, decoupled design
Thank you! Roy@TeamAgile.com TeamAgile.com ISerializable.com (blog)