State of the Art Testability By Chad Parry. Background  The industry offers great advice on testability  Projects that follow that advice look different.

Slides:



Advertisements
Similar presentations
Composition CMSC 202. Code Reuse Effective software development relies on reusing existing code. Code reuse must be more than just copying code and changing.
Advertisements

A practical guide John E. Boal TestDrivenDeveloper.com.
Chapter 8Java: an Introduction to Computer Science & Programming - Walter Savitch 1 Chapter 8 l Basic Exception Handling »the mechanics of exceptions l.
Chapter 10 THINKING IN OBJECTS 1 Object Oriented programming Instructor: Dr. Essam H. Houssein.
1 Software Maintenance and Evolution CSSE 575: Session 6, Part 4 Breaking Dependencies Steve Chenoweth Office Phone: (812) Cell: (937)
Inheritance. Extending Classes It’s possible to create a class by using another as a starting point  i.e. Start with the original class then add methods,
1 Generics and Using a Collection Generics / Parameterized Classes Using a Collection Customizing a Collection using Inheritance Inner Classes Use of Exceptions.
CS 106 Introduction to Computer Science I 03 / 30 / 2007 Instructor: Michael Eckmann.
1 Creating Classes. 2 Writing Classes Thus far, we have mainly used existing classes in the Java library  (also main classes for executing) True object-oriented.
Chapter 5 Part 2 COSI 11a Prepared by Ross Shaull.
Algorithm Programming Coding Advices Bar-Ilan University תשס " ו by Moshe Fresko.
Struts 2.0 an Overview ( )
Inheritance. Types of Inheritance Implementation inheritance means that a type derives from a base type, taking all the base type’s member fields and.
Starting Chapter 4 Starting. 1 Course Outline* Covered in first half until Dr. Li takes over. JAVA and OO: Review what is Object Oriented Programming.
Chapter 7 Designing Classes. Class Design When we are developing a piece of software, we want to design the software We don’t want to just sit down and.
REFACTORING Lecture 4. Definition Refactoring is a process of changing the internal structure of the program, not affecting its external behavior and.
Introduction to AOP.
Chapter 8 More Object Concepts
October, 2006 © Copyright 2006, Larry A. Beaty. Copying and distribution of this document is permitted in any medium, provided this notice is preserved.
Question of the Day  On a game show you’re given the choice of three doors: Behind one door is a car; behind the others, goats. After you pick a door,
Chapter 2 Introducing Interfaces Summary prepared by Kirk Scott.
Testing. 2 Overview Testing and debugging are important activities in software development. Techniques and tools are introduced. Material borrowed here.
23-Oct-15 Abstract Data Types. 2 Data types A data type is characterized by: a set of values a data representation, which is common to all these values,
CS 350 – Software Design The Strategy Pattern – Chapter 9 Changes to software, like other things in life, often focus on the immediate concerns and ignore.
Dependency Injection Technion – Institute of Technology Author: Gal Lalouche - Technion 2015 ©
Best Practices. Contents Bad Practices Good Practices.
The Factory Patterns SE-2811 Dr. Mark L. Hornick 1.
Design Patterns Gang Qian Department of Computer Science University of Central Oklahoma.
Object Oriented Software Development
Refactoring & Testability. Testing in OOP programming No life in flexible methodologies and for refactoring- infected developers without SOME kind of.
Refactoring 2. Admin Blackboard Quiz Acknowledgements Material in this presentation was drawn from Martin Fowler, Refactoring: Improving the Design of.
Factory Method Explained. Intent  Define an interface for creating an object, but let subclasses decide which class to instantiate.  Factory Method.
Computer Programming with JAVA Chapter 8. Exception Handling Basic Exception Handling the mechanics of exceptions Defining and Using Exceptions some "simple"
“Education is a Treasure that follows you everywhere.” – Chines Proverb Methods and Functions.
The Singleton Pattern SE-2811 Dr. Mark L. Hornick 1.
Programming with Java © 2002 The McGraw-Hill Companies, Inc. All rights reserved. 1 McGraw-Hill/Irwin Chapter 5 Creating Classes.
Test-Driven Development Eduard Miric ă. The problem.
Design Patterns David Talby. This Lecture Re-routing method calls Chain of Responsibility Coding partial algorithms Template Method The Singleton Pattern.
Object Oriented Programming
Scalatest. 2 Test-Driven Development (TDD) TDD is a technique in which you write the tests before you write the code you want to test This seems backward,
Interfaces About Interfaces Interfaces and abstract classes provide more structured way to separate interface from implementation
Design Patterns Software Engineering CS 561. Last Time Introduced design patterns Abstraction-Occurrence General Hierarchy Player-Role.
Dependency Injection Frameworks Technion – Institute of Technology Author: Assaf Israel - Technion 2013 ©
SEG 4110 – Advanced Software Design and Reengineering Topic T Introduction to Refactoring.
Programming Fundamentals. Topics to be covered Today Recursion Inline Functions Scope and Storage Class A simple class Constructor Destructor.
The Last Lecture CS 5010 Program Design Paradigms "Bootcamp" Lesson © Mitchell Wand, This work is licensed under a Creative Commons Attribution-NonCommercial.
Chapter 4 Request and Response. Servlets are controlled by the container.
M1G Introduction to Programming 2 2. Creating Classes: Game and Player.
Dependency Injection with Guice Technion – Institute of Technology Author: Gal Lalouche - Technion 2016 ©
CLASSIFICATION OF DESIGN PATTERNS Hladchuk Maksym.
Chapter 5 Introduction to Defining Classes Fundamentals of Java.
Catalog of Refactoring (6) Making Method Calls Simpler.
TDD Unit tests from a slightly different point of view Katie Dwyer.
Principles and examples
Programming Logic and Design Seventh Edition
Introduction to Redux Header Eric W. Greene Microsoft Virtual Academy
The Singleton Pattern SE-2811 Dr. Mark L. Hornick.
C++ coding standard suggestion… Separate reasoning from action, in every block. Hi, this talk is to suggest a rule (or guideline) to simplify C++ code.
Behavioral Design Patterns
Chapter 3: Using Methods, Classes, and Objects
Lecture 10: More on Methods and Scope
Chapter 5 Conclusion CIS 61.
How to be a Good Developer
Magento Technical Guidelines Eugene Shakhsuvarov, Software Magento
Un</br>able’s MySecretSecrets
Objects First with Java
The Object-Oriented Thought Process Chapter 05
Designing For Testability
HFOOAD Chapter 5 Interlude
Presentation transcript:

State of the Art Testability By Chad Parry

Background  The industry offers great advice on testability  Projects that follow that advice look different than projects that don’t  It’s important that we all recognize what testable code looks like

Objective  Start with a simple example  Identify helpful coding idioms one by one  Apply these improvements in steps  Arrive at a testable example  Examine best practices in the result

Step 1: Naïve Example  Command-line tool to execute a “buy” order  3 classes that need testing  TradingApplication  Trade  MarketClient  Some external classes not part of this project: ApplicationWrapper, Account, MarketService

Step 1: Naïve TradingApplication

Step 1: Naïve Trade

Step 1: Naïve MarketClient

Step 2 Pain Points  “That code to create fake Trade objects is repeated in enough places that we should put it in a testing library.”  “The three—well, actually four—things that this object is responsible for are…”

Step 2 Principle: Value Objects  Separate service objects from value objects  Value objects  Hold state  Are easy to create in tests  Service objects  Perform work  Are harder to fake in tests

Step 2 Code: Move Trade Services  Many objects in the system are going to pass around Trade objects  Trades depend on market prices and accounts  Tests would be easier to write if those dependencies didn’t need to be mocked  The “buy” method should be moved out  The resulting class is a simple value object

Step 2: Trade Before

Step 2: Trade After

Step 2: BookingService After

Step 3 Pain Points  “How do I mock a static method?”  “When I modify static variables in my tests, I sometimes start seeing unpredictable failures.”  “Did I call all the setters I need to so I can use this object?”

Step 3 Principle: Constructor Injection  The best way to acquire dependencies is through constructor injection  This just means creating constructor parameters for all required objects  Objects won’t give errors or behave differently because of missing dependencies  Tests can easily substitute test doubles  Avoid static methods and the “new” operator

Step 3 Code: Add Trade Constructor  The Trade class is not immediately usable when it is created  This could result in guess-and-check programming while clients figure out which methods are necessary to make it viable  Trade objects would be even easier to handle if they were immutable

Step 3: Trade Before

Step 3: Trade After

Step 3 Code: BookingService Parameters  The BookingService calls static methods in the MarketClient and Account classes  These dependencies make testing difficult, because tests cannot substitute their own implementations  A constructor should be added for tests  For product code, another constructor can be added, which retains the production bindings

Step 3: BookingService Before

Step 3: BookingService After

Step 4 Pain Points  “We would have tested that class if the constructor didn’t always throw an exception.”  “The test ‘setUp’ methods are hard to write because we have to fake so many objects in the environment.”  “How do you guarantee that the ‘init’ method gets called for your object?”

Step 4 Principle: Trivial Constructors  Tests are forced to invoke constructors and static initializers and init methods  Expensive initialization code thwarts unit tests  All constructors should be trivial  A constructor that does real work, (such as opening a connection), should be refactored so that it accepts an initialized resource, (such as an opened connection), as a parameter

Step 4 Code: Lazy MarketClient Singleton  Any use of the MarketClient class triggers a static initializer, which calls the expensive “MarketService.fetchPrices()” method  MarketClient should create its singleton lazily  A constructor should be added for tests that avoids the expensive initialization entirely  A backwards-compatible constructor can be added for product code

Step 4: MarketClient Before

Step 4: MarketClient After

Step 5 Pain Points  “How do you create a fake ‘HttpServletRequest?’”  “It’s not worth it to test servlet code.”

Step 5 Principle: Thin Harness  The entry point into your application is sometimes required to extend a third-party object, such as “HttpServlet”  Business logic should be moved somewhere easier to test  The entry point itself will only need to be covered by scenario tests, not unit tests

Step 5 Code: Simplify TradingApplication  The TradingApplication class extends an ApplicationWrapper  An ApplicationWrapper is probably difficult to construct in tests  All business logic should be moved elsewhere  The command-line argument parsing can be moved to a helper class that gets its own tests

Step 5: TradingApplication Before

Step 5: TradingApplication After

Step 5: TradingArgs After

Intermission  Half of the steps have been performed  It’s possible to unit test the business logic now  Unfortunately the tests are long and awkward  The remaining steps make testing simple

Step 6 Pain Points  “Removing the hard-coded dependencies from my code always makes my constructors difficult to read, because there are so many parameters.”  “In the real world, most classes contain at least some code that is really hard to test.”  “The code coverage report always shows gaps that we can’t do anything about.”

Step 6 Principle: Injector  Business logic and glue code are best separated  Moving glue code to its own injector file conveys intention and keeps it organized  Creating many small injection helpers, one to create each object, makes the production bindings easy to read

Step 6 Code: Create TradingInjector  A new TradingInjector class should be created  All the production bindings that we had implemented in constructors should be moved to the injector class  Complicated bindings become simpler because they can be broken out into multiple small injection helpers

Step 6: BookingService Before

Step 6: Injection Helper After

Step 6: More Injection Helpers

Step 6: Injection Guidelines  Injection helpers contain too many sprawling dependencies to be unit tested  Injection helpers need to be trivial  Injection helpers can call other injection helpers but product code never should

Step 6: Injection Control Flow  First, the top-most injection helper is called  It delegates to other injection helpers, which in turn delegate to others  The return value is a complete object graph  After that the injectors are out of the picture while the application executes

Step 7 Pain Points  “Can a mock object return a mock that returns a mock?”  “Can you add some comments to this test code so I can tell what is going on?”  “It took me forever to refactor that class because of all the tests that needed to expect the new contract.”

Step 7 Principle: Demeter  The Law of Demeter says objects should avoid asking for dependencies that they don’t need  For example, instead of asking for a factory, ask for the object produced by the factory  In practice, this is hard to follow unless the project uses injection helpers

Step 7 Code: Simplify BookingService  The BookingService should remove its dependency on the MarketClient and ask directly for the settlement amount instead  A new SettlementCalculator helper can calculate the settlement amount  The SettlementCalculator also only needs a price, not the whole MarketClient  The glue code is the only place that needs to reference the MarketClient

Step 7: BookingService Before

Step 7: BookingService After

Step 7: SettlementCalculator After

Step 7: Injection Helpers Before

Step 7: Injection Helpers After

Step 8 Pain Points  “In the real world, classes can be decoupled up to a certain point, but then you always have a factory or a service that you can’t get rid of.”  “I can’t specify all my dependencies up front because the class performs lazy instantiation.”

Step 8 Principle: Providers  Sometimes it’s necessary to keep a dependency on a factory  Multiple instances are needed  Lazy construction is desired  The dependency should be made as simple as possible  The “Provider” pattern can be tested without needing mock objects

Step 8 Code: Provider Interface  The Provider interface can be created once and used throughout the application

Step 8 Code: Providers Utility  The Providers utility can be created once and used in all the tests  Tests just need to invoke “Providers.of(value)” to create a test double

Step 8 Code: Fetch Prices Lazily  Instead of asking for prices, objects could ask for a provider of prices  The expensive fetching of the prices will then be delayed until they are actually needed

Step 8: Injection Helper Before

Step 8: Injection Helper After

Step 8: SettlementCalculator Before

Step 8: SettlementCalculator After

Step 9 Pain Points  “I hate it when a change in one class propagates through all the package’s classes.”  “I’ll just add a static method here because I don’t want to change the class’s dependencies.”  “That bug snuck in because I had to fix the plumbing in so many places.”

Step 9 Principle: Scopes  Most projects have a concept of scopes  Application scope  Request scope  An explicit scope object lends uniformity to the plumbing in the injection helpers  Scopes can also hold singletons or other objects with the same lifetime as the scope

Step 9 Code: Use an ApplicationScope  The injection helpers all pass around a parameter for “String[] args”  If this variable type changed, or if a second variable were needed, every injection helper signature would be affected  Instead the injection helpers should pass around an “ApplicationScope”  Changes can be encapsulated in the scope

Step 9: Injection Helpers Before

Step 9: Injection Helpers After

Step 9 Code: Create ApplicationScope  The ApplicationScope class is simple  It holds the “String[] args” that were being passed around  It can also hold the MarketClient singleton, so that the singleton implementation doesn’t need statics  It could hold any other objects that need to be cached for the lifetime of the application

Step 9: ApplicationScope After

Step 9: Anti-Patterns  Service locators and context objects tend to grow and grow, making tests brittle  Scope objects are neither of these  Scope objects stay simple and decoupled, even when the application gets complicated  Scope objects and production objects don’t even have a knowledge of each other

Testable Example  After making those changes, everything is easy to test  Teams tend to write more tests and better tests when they are easy  Once introduced, these patterns are easy to follow

Simple TradingApplication

Testable TradingArgs

Testable Trade

Testable MarketClient

Testable SettlementCalculator

Testable BookingService

Simple ApplicationScope

Simple TradingInjector

Conclusions  The right coding idioms can make hard tests easy and impossible tests possible  This coding style is the logical conclusion of the current state of the art in testability  Each coding idiom would still be valuable taken individually

Summary of Steps  Separate value objects from service objects  Prefer constructor injection  Require trivial constructors  Create a thin application harness  Move glue code to an injector class  Follow the Law of Demeter  Use the Provider interface  Add explicit scope objects

Training  Only an experienced developer can understand all these techniques individually  On the other hand, using dependency injection is simple even for junior developers  In a project that already has injector classes, anyone can follow the pattern  The team then enjoys high testability without the burden of confronting the same testing problems over and over

Result  The final result is a codebase infected with dependency injection  Long-term sustained testability is more likely  Code readability is even better once the idioms become familiar

Further Information  Read the complete how-to manual: DIY-DIDIY-DI  Browse the source code from the case study: dipresentation dipresentation  Questions?