Download presentation
Presentation is loading. Please wait.
1
Designing For Testability
2
Characteristics of Testable Code
Highly cohesive Loosely coupled Dependencies minimized Dependency injected Minimal use of static methods and final methods Static methods contain few parameters Complex object creation logic is placed in Factories and/or Builders
3
Features that are Difficult to Automate
Dependencies created by the class under test Prevents mocking / unit isolation Final methods Can’t be overridden by mocks or other types of test doubles Static methods Code that contains complex object creation logic (especially if it creates complex object graphs) GUI / View Code
4
Strategies Test-Driven Development Dependency Injection
Use of configurable factories with simple (preferably no-argument) constructors In-class wrapper methods for calls to static method Use of MVC / MVP with view interfaces
5
Problem: Dependencies Created by the Class Under Test
public class PayrollProcessor { private DatabaseService dbService; public PayrollProcessor() { dbService = new DatabaseService(); } public double getTaxAmount(String employeeID) { Employee emp = dbService.getEmployee(employeeID); double taxAmount = 0; // Some complicated code that uses information from // 'emp' to calculate taxes return taxAmount; } }
6
Solution Dependency Injection
Mocking of the DatabaseService instance in the test public class PayrollProcessor2 { private DatabaseService dbService; public PayrollProcessor2(DatabaseService dbService) { this.dbService = dbService; } public double getTaxAmount(String employeeID) { Employee emp = dbService.getEmployee(employeeID); double taxAmount = 0; // Some complicated code that uses information from 'emp' to // calculate taxes return taxAmount; } }
7
Solution: Setter Injection
public class PayrollProcessor3 { private DatabaseService dbService; public void setDbService(DatabaseService dbService) { this.dbService = dbService; } public double getTaxAmount(String employeeID) { Employee emp = dbService.getEmployee(employeeID); double taxAmount = 0; // Some complicated code that uses information from 'emp' to // calculate taxes return taxAmount; } }
8
The “Mock Object” design pattern
Allows “mock objects” to be inserted anywhere in a program Allows a class to be isolated from its dependencies during unit testing, or for other reasons (e.g., a class I depend on doesn’t exist yet, or I want to avoid calling it for some reason) Supports “fault injection” Cause the software to fail at points where it normally wouldn’t to test error handling code Easy to generate with frameworks such as Mockito and EasyMock
9
The “Mock Object” design pattern
Mock objects can simulate the behavior of complex, real (non-mock) objects and are therefore useful when a real object is impractical or impossible to incorporate into a unit test. If an object has any of the following characteristics, it may be useful to use a mock object in its place: supplies non-deterministic results (e.g., current time or current temperature); has states that are difficult to create or reproduce (e.g., a network error); is slow (e.g., a complete database, which would have to be initialized); does not yet exist or may change behavior; would have to include information and methods exclusively for testing purposes (and not for its actual task). Mock object method implementations can contain assertions of their own. This means that a true mock, in this sense, will examine the context of each call— perhaps checking the order in which its methods are called, perhaps performing tests on the data passed into the method calls as arguments.
10
Mock Object Creation with Mockito
public class PayrollProcessorTest public void testGetTaxAmount() { DatabaseService mockDBService = Mockito.mock(DatabaseService.class); Employee mockEmployee = Mockito.mock(Employee.class); Mockito.doReturn(mockEmployee).when( mockDBService.getEmployee(Mockito.anyString())); // Mockito.when calls to setup mockEmployee to do what we will write // our test to expect PayrollProcessor2 processor = new PayrollProcessor2(mockDBService); // Code to test the getTaxAmount() method assuming that the // DatabaseService returns an employee object that does what we mocked // our mockEmployee to do } }
11
Dependency Injection Programming style that allows objects to receive their dependencies instead of creating them Makes your design more flexible (dependencies are no longer “hard-coded” into the class that uses them Facilitates testing by making it easy for a test to mock a dependency Choose a “Dependency Injection Container” Spring, Ninject, Google Guice, etc. Configure your dependency injection container with a mapping from abstract interfaces to concrete classes that implement them Create objects by asking the dependency injection container for an implementation of an abstract interface Allows program code to depend on abstract interfaces, and not on concrete classes Allows mock objects to be easily inserted anywhere in the code
12
Problem: Final Methods
Mocking frameworks mock interfaces by creating their own implementations They mock classes by creating subclasses Final methods cannot be sub-classed, so most mocking frameworks cannot mock them Final methods can be tested, but if the dependency you want to mock has a final method, you typically cannot replace that method with a mock implementation Can’t do this: Mockito.doReturn(xyz).when(mockObject.myFinalMethod())
13
Solutions Minimize the use of final methods (warning: virtual methods are slower than final methods) Use a mocking framework that can mock final methods (i.e. PowerMock) PowerMock is slow Generally considered bad practice to use it
14
Problem: Static Methods
Similar problems as final methods Can’t be mocked by most mocking frameworks Static methods are testable, but classes that have dependencies on them are difficult to test
15
Solutions Minimize the use of static methods
Wrap calls to static methods in non-static methods in the dependent class Use a mocking framework that can mock static methods (i.e. PowerMock)
16
Problem: Code that Contains Complex Creation Logic
Similar to the general problem of classes that create their own dependencies Difficult to use dependency injection
17
Solution Use a factory to create the dependency object(s)
Use a non-static ‘get…’ method to create the object Makes the entire factory mockable public class ServiceFactory { public DatabaseService getDatabaseService() { DatabaseService dbService = null; // Complex logic to create and initialize a // database service instance return dbService; } }
18
Factory in Greater Detail
19
Problem: GUI / View Code
GUIs are difficult to test using test automation tools GUIs change frequently (making GUI test code brittle) GUI automation tools (i.e. Selenium for browser-based applications) are somewhat flaky GUI tests that automate and mimic user behavior are slow (due to screen creation and redraws)
20
Solution Use an MVC / MVP design
Separate actual view code from what can go in controller/presenter and model classes that are easier to test Use interfaces for your views and make controllers/presenters only depend on the view interfaces (not the actual views) Allows mocking of the view code Will still need a few system level tests (automated or manual) but most view code can be tested without actually generating the view
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.