“A comprehensive suite of JUnit tests is one of the most import aspects of a software project because it reduces bugs, facilitates adding new developers, and enables refactoring and performance tuning with confidence. Test-driven development (TDD) is the best way to build a suite of tests. And the Dependent Object Framework is the best way to test against database objects.” This presentation covers the benefits of TDD along with practical advice on how to implement TDD in complex projects.
My INSURER PTE LTD - Insurtech Innovation Award 2024
xUnit and TDD: Why and How in Enterprise Software, August 2012
1. JUnit and Test Driven Development:
Experiences in MDM CS 2003 to 2012
Justin Gordon
Enterprise TDD Evangelist
Senior Software Engineer
IBM, Master Data Management Collaboration Server
gordonju@us.ibm.com
justin.gordon@gmail.com
http://www.tddtips.com
https://github.com/justin808/dof
2. Author background
BA Harvard Applied Mathematics, MBA UC
Berkeley
Writing Java Enterprise Software since 1996
Engineer at Trigo, acquired by IBM
Product now called InfoSphere Master Data
Management Collaboration Server. Led rewrite
of storage layer doing using TDD
Founder and author of Open Source Project
“Dependent Object Framework”
Outside of programming, interests include my
kids, surfing, standup paddling, my dogs, cycling,
and home improvement. Check out the house I
designed and built:
http://www.sugarranchmaui.com.
Speaker SD West 2008, Architecture and Design
World 2008, and SD Best Practices 2008. See
my blog for notes! (http://www.tddtips.com)
Please tell me about your challenges and maybe
I can help
2
3. Success Factors
Nature of the project
– Existing, legacy, and not designed for testing?
– New code
– How many dependencies and how containable?
Motivation of the team
– TDD is hard work!
Learning
– There's a ton to learn about writing good tests. Lots of patterns. Lots of pitfalls.
– Recommended: xUnit Test Patterns, but it's huge, and Kent Becks original book
“Test Driven Development by Example”
Measurement is key
– Continuous Integration including Code Coverage Reports
Benefits
– Quality!
– Sanity for the overall team, including developers, QA, and managers
Test First Code
– Really affects the overall structure of the code, as code is written to be testable.
Most important overall is to have automation tests!
3
4. Summary of MDM CS JUnit
Check out my blog articles, especially this one:
JUnit and Test Driven Development for MDM CS -- Fixtures and Factories
– Serialization development was perfect for jUnit
– High algorithmic complexity, limited outside dependencies
Rest of the product for jUnit
– Challenging due to very complex database interactions
– Led to the open source project Dependent Object Framework which works on the
problem of test fixture setup
– Big problem was that developers confused the usage of the framework in terms of
how to use the true fixtures versus setting up scratch objects.
– Good article on the subject of fixtures and factories for ruby testing:
Fixture vs. Factories - Can't we all just get along.
4
5. Presentation given at SD West, 2009
JUnit and Test Driven Development:
Why and How in Enterprise Software
5
7. Conventional versus Agile
CONVENTIONAL
Architects High Level Design (HLD) Detailed Technical Design (DTD)
Coding QA & Bug Fixing Regressions More QA & Bug Fixing
Major Release Bug Fixing Regressions Bug Fixing Minor Release
…
Painful mess! Unhappy developers, unhappy customers, unhappy
managers!
AGILE
Stories and Requirements Simple Specs Write JUnit (automated) tests in
Conjunction with Source Ensure code coverage Refactoring to make
code better QA Limited bug fixing with JUnit tests for each bug fixed
Almost no regressions! Performance tuning with confidence Release
Very few bugs
Happy developers, happy customers, happy managers!
7
8. Premise
“A comprehensive suite of JUnit tests is one of the most import aspects of
a software project because it reduces bugs, facilitates adding new
developers, and enables refactoring and performance tuning with
confidence. Test-driven development (TDD) is the best way to build a suite
of tests. And the Dependent Object Framework is the best way to test
against database objects.”
Justin Gordon
8
9. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
9
10. JUnit Tests: What?
JUnit test: a method, written in Java, that verifies the behavior of an
individual unit of code, or occasionally of a larger subsystem, and
reports errors in an automated fashion.
public void testAddReturnsSum() {
int sum = Calculator.add(2, 3);
assertEquals(5, sum);
}
10
11. JUnit Tests: Why?
If JUnit tests pass and code coverage is high Nearly Bug-Free Code!
When JUnit tests cover requirements and tests pass Code is Complete!
JUnit tests facilitate automatic test running to detect regressions instantly
during bug fix cycles. Can’t do that with manual QA! Can’t do that with QA
Automation tools!
Enables Courage and Creativity
Developers (experienced and new) can change the code with confidence,
enabling
– Refactoring
– Performance Tuning
– JUnit tests serve to document and demonstrate the API
Large team sizes, offshoring, complexity
11
12. Catch 22: Why not write JUnit tests?
“Normal” development cycle inhibits JUnit test creation
Catch-22: existing quality is low, so developers are too busy fixing
problems found in the field to write tests.
(Bad) Attitude: “It’s QA’s job to find my bugs. I don’t have time to write
tests.”
Skills: tough to learn how to write JUnit tests for
new code. Many new patterns!
Even tougher for old code!
Unless existing code is designed for testability,
implementing JUnit tests is very difficult.
Really Tough!
12
13. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
13
14. What is Test Driven Development (TDD)?
“Programming practice in which all production code
is written in response to a failing test.”
List Requirements
Refactor to eliminate code
Write One Test smells (e.g., duplicated code)
Run Test to
Make Sure It Fails
Add or modify just enough code to make new
test pass and all previous tests pass
Read Kent Beck’s:
“Test Driven Development By Example”
14
15. What Is Not Test Driven Development?
Any time you write code that is not fixing a failing test.
I.e., Writing code, then writing tests or intending to eventually write tests.
Relying on QA to automate their manual tests.
Be honest when trying this.
Conventional Big Up Front Design is not TDD!
15
16. Why TDD Code Coverage & Better Code
Guarantees existence of JUnit tests covering most, if not all of your code!
Guarantees code will be written to be testable.
– Reverse is also true: if you write your code first, and then your tests, you may
have difficulty writing tests for the new code, and then you may not write the tests
at all! More natural to write untestable code unless tests written at the same time.
Solves the motivation problem. Test writing becomes part of the coding
process, not a tedious afterthought.
Produces better code: more decoupled, with clearer, tighter contracts.
Tests are the canary in the coal mine! Bad designs show up as hard to test!
16
17. Shooting hoops? Practice Makes Perfect
Developers improve skills because of the immediate feedback
from the tests, rather than months later from QA!
Write Test Write Code
Academic study confirms higher quality code: “An Initial Investigation of Test
Driven Development in Industry” by Boby George and Laurie Williams, 2003,
http://collaboration.csc.ncsu.edu/laurie/Papers/TDDpaperv8.pdf
TDD developers took more time (16%), but non-TDD developers did not write
adequate automated test cases even though instructed to do so!
17
18. Does Test First Matter?
Goal is Automated Unit Test Coverage
Your choice how you get there
Sometimes, you already have lots of code! So too late for
pure TDD!
So…
Consider a broader definition of TDD…
Not just “Test First Programming”
But delivering code with unit tests
You want automated test coverage as code is delivered!
18
19. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
19
20. Making it Happen: Tools for Success
IDEs: Eclipse, RSA, RAD, and IntelliJ offer these
essentials:
– Ease of use to run a single new test
– Refactoring tools.
Code Coverage
– Now built into Eclipse (EclEmma) and IntelliJ
– Clover or Emma are the most popular.
– How do you know how good your tests are?
– Measure progress for morale (and management reports).
Continuous Automation
– Jenkins, Cruise Control, or BuildForge
– Automated system for building code, running JUnit tests and
reporting on code coverage.
– Alerts team of issues (build or JUnit) within minutes of checkins
Mock Objects: Mockito
Dependent Object Framework
– Ease of writing JUnit tests in the context of persistent objects →
helps setup database fixtures
20
21. Code Coverage Inside Eclipse
Code Coverage in
Eclipse provided by
EclEmma:
http://www.eclemma.org
Green lines indicate
coverage by unit
tests.
Pink lines indicate
code not covered by
unit tests
21
22. Exercise 1: Simple TDD: Compute Change Coins
public class Change
Problem: Compute Change Coins { public int pennies, nickels, dimes, quarters;
Coins include: pennies, nickels, public Change(int pennies, int nickels, int dimes, int
quarters) {
dimes, quarters this.pennies = pennies;
Machine may be out of any coin this.nickels = nickels;
this.dimes = dimes;
except pennies this.quarters = quarters;
Give least number of coins }
public class VendingMachine
Example: { // the number of coins left
All coins available: public int pennies, nickels, dimes, quarters;
37 cents: 1 quarter, 1 dime, 2 public Change getChange(int cents)
{ … }
pennies }
80 cents: 3 quarters, 1 nickel
public class VendingMachineTest extends TestCase
{
No nickels: public void testGetChangeReturnsNoChangeIfNoCents)
80 cents: 2 quarters, 3 dimes { … }
}
No dimes:
85 cents: 3 quarters, 2 nickels
22
23. Exercise 1 Tips
Only add code after adding a test that fails!
Do bare minimum to make tests pass
Patterns: Naming Test Methods
– test{MethodName}Returns{Value}When{Condition}
– testGetChangeReturnsZeroWhenNoCents
– testGetChangeReturnsThreeQuartersOneNickelWhenEightyCents
– test{MethodName} {DoesSomething}When{Condition}
– testGetChangeThrowsWhenCentsLessThanZero
– Why such long method names?
1. Method names print out in test failures
2. Programmers never call these methods
– Directory Structure
– src: Where production code goes
– junit:Where junit tests go, use parallel package structure to test package and protected
methods.
Implement equals() so that we can compare Change objects.
– Implement toString() so that error messages are clear
23
24. Exercise 1: Steps for “Initial” example
1. Run VendingMachineTestSuite to see all tests run
2. Run the test suite with code coverage turned on to see code coverage
3. Uncomment Change.toString() to see effect on the error message
implement toString() for better messages
4. Uncomment Change.equals() to see VendingMachineTest tests pass.
5. Put @Ignore in front of failing test and run all tests to see that no failures and
2 ignored tests
24
25. Exercise 1b: Steps for “Intermediate”
1. Check out the triangulation method of verification.
2. Fix the test testGetChangeReturnsThreePenniesIfTwoCents()
3. Check out the use of the @Before setup of the variable VendingMachine
4. Complete the rest of the commented tests
5. Handle the tougher cases where the vending machine can run out of a
certain kind of coin
Question: Can we exhaustively find all the solutions and check the code against
these solutions? Possibly could write tests to use triangulation.
25
26. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
26
27. Architectural benefits of TDD
Without TDD, you often
see tight coupling between Tight Coupling!
classes and throughout to all
dependencies, making the D
A
implementation of new tests
prohibitively painful. class
B
With TDD E interface
C
Better abstractions and better
decoupling of classes.
Loose Coupling: TDD!
Loose coupling, otherwise impossible
to test individual components. A
When using “Test Doubles” (e.g., test
stubs and mock objects), you want to bb cc dd
mock out smaller APIs or else you’ll
work too hard! Keep classes small B e C
and methods tight.
D
27
28. Test Doubles
Test Doubles: Fakes, Stubs, Mocks – see “xUnit Test Patterns” by
Meszaros
Reasons to use Test Doubles
– Isolates code for testing!
– Awkward to setup for JUnit test – e.g. web services
– Not available (or not yet written) – new component in large project
– Too slow (less so now with in-memory DBs like H2)
Reasons to not use Test Doubles
– Extra code to maintain that is only used for testing
– Difficult to refactor code to use test doubles
– You still need to test the delivery code!
– Don’t mock out the database!
28
29. Making it Happen: Isolating Code for Testing
Note – Mock objects are a form of test double and the name is used interchangeably here
Successful TDD depends on dependency isolation – you need to separate the code to
be tested from the rest of the system.
– This is THE main technical challenge, esp. for database and integration points
– Must decouple classes with interface/implementation/mock object pattern
– Use a combination of dependency location and/or dependency injection for dependencies
(following slides)
– Typically mocking out a dependency on a clearly defined component, such as an object that
would call a web service.
– Difficult to do TDD without using test doubles (or using the Dependent Object Framework)
– Tip: minimize business logic in mock objects because you have to duplicate that logic in the real
implementation. Refactor code to minimize business logic in mock classes.
Dependency
Interface
Class To Test
(Runtime Object)
Dependency
(Test Double,
e.g. Mock Object)
29
30. Dependency Isolation: Static Location
Example: Class fetches data from the database or from a mock source. But
how does your code get a handle to the correct Component?
Dependency location: your class looks up the dependency (test
double or real instance) from a known location, typically a static
method call
Test setup code:
// note, setTaxRateProvider takes interface TaxRateProvider
GlobalContext.setTaxRateProvider(new MockTaxRateProvider());
Production setup code:
GlobalContext.setTaxRateProvider(new SoapTaxRateProvider());
Production Code sees interface does NOT know if real or
test double!!
class OrderProcessor Static method
public float getTaxRateForState(String state)
{ returns interface!
TaxRateProvider taxRateProvider =
GlobalContext.getTaxRateProvider();
return taxRateProvider.getSalesTax(state);
}
See: “Inversion of Control Containers and the Dependency Injection pattern”: http://www.martinfowler.com/articles/injection.html
30
31. Dependency Isolation: Constructor/Setter Injection
Dependency Constructor/Setter Injection: Pass a reference to
the dependency in the constructor (or a setter):
Test setup code:
// note interface TaxRateProvider
TaxRateProvider mockTaxRateProvider = new MockTaxRateProvider();
OrderProcessor orderProcessor = new OrderProcessor(mockTaxRateProvider)
Production code:
TaxRateProvider soapTaxRateProvider = new SoapTaxRateProvider());
OrderProcessor orderProcessor = new OrderProcessor(soapTaxRateProvider)
InvoiceComponent class does NOT know if it has a real or mock
persistence object
Member interface
class OrderProcessor { variable!
TaxRateProvider taxRateProvider;
// constructor injection
OrderProcessor (TaxRateProvider taxRateProvider ) {
this.taxRateProvider = taxRateProvider;
}
public float getTaxRateForState(String state)
{
return taxRateProvider.getSalesTax(state);
}
31
32. Dependency Injection versus Service Locator
“Inversion of control is a common feature of frameworks, but it's something that comes at
a price. It tends to be hard to understand and leads to problems when you are trying to
debug. So on the whole I prefer to avoid it unless I need it. This isn't to say it's a bad
thing, just that I think it needs to justify itself over the more straightforward alternative…
The key difference is that with a Service Locator every user of a service has a
dependency to the locator. The locator can hide dependencies to other
implementations, but you do need to see the locator. So the decision between locator
and injector depends on whether that dependency is a problem.
A lot of this depends on the nature of the user of the service. If you are building an
application with various classes that use a service, then a dependency from the
application classes to the locator isn't a big deal….The difference comes if the lister is a
component that I'm providing to an application that other people are writing. In this case
I don't know much about the APIs of the service locators that my customers are going to
use. Each customer might have their own incompatible service locators. I can get
around some of this by using the segregated interface. Each customer can write an
adapter that matches my interface to their locator, but in any case I still need to see the
first locator to lookup my specific interface. And once the adapter appears then the
simplicity of the direct connection to a locator is beginning to slip.
Martin Fowler
http://martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection
32
34. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
34
35. Basic Phases of an xUnit Test
Setup
Prepare Test Fixtures -- Get
xUnit Test
Application State Ready To Do
Something DB Objects and the DOF
Exercise
Do Something With Fixtures Prefer
Single Action
Verify
Check Something Happened: assertSomething()
Teardown
Restore application state for next test
35
36. Database Issues
Database is like a global variable that is slow to access!
Actually worse than a global variable because it lives between test
runs!
Stickiness of data collides with xUnit philosophy of
atomic/independent tests!
DB “Fixture” Setup without the DOF
– Java code
– SQL scripts
– DB Backups
Issues: Difficult, Monolithic, fragile, frustrating, slow
– Greater possibility of slow and erratically failing tests
Test fixtures often include objects that are persisted in the DB. I.e., in order to run
a test, some values must exist in the DB, and a test may create some new records
in the DB.
36
37. Why have xUnit tests hit the database?
Production code for end users will hit the DB, so you need to test it!
Catch 22: Refactor in order to isolate database dependencies to
build tests, but without tests, how safely can we do the refactoring?!
Mocks and stubs add (much) more code to maintain (and to fix
bugs!).
Reliance on mocks and stubs can mask errors with using the DB.
Annecdote: GuideWire Software used to depend heavily on stubs
and dependency isolation techniques, but now focuses exclusively
on tests against the database, using H2 in-memory DB to speed up
tests.
37
38. DOF: What Problem Does it Solve?
Setup of the DB in a “lazy” and “modular” fashion as
needed by each test
– Only populate what is needed for a test
– Test does not worry if DB already populated
Clarity of the DB setup required for a test
– See clearly DB objects needed for a test
Establishes a clear pattern for teamwork and reusability
behind the DB setup
38
39. Simple Example – Logical Model
Logical Object Model
Invoice Defines object
relationships behind the
DOF setup but the DOF
setup is about the
instances of these objects
Customer
Product that have representations
in the database
Manufacturer
39
40. Data Relationships and Object Creation
With DOF, test
Invoice 1001 only specifies top
level dependency!
Customer Jones
Objects Created
Product Orange Juice DOF takes care
ensuring these DB
Product Grape Juice backed objects are
ready for your test
Manufacturer
Ocean Spray
40
41. Object Dependency Processing: Java Reference Object
Your test code DOF plumbing Your ReferenceBuilder code
ReferenceBuilder rbOrangeJuice = new Product_OrangeJuice();
Product orangeJuice = (Product) DOF.require(productOJ);
Return Object “Orange Juice”
Object pk = rbOrangeJuice.getPrimaryKey()
Check cache for Orange Juice object OJ Found
in Cache
OJ Not Found
Call rbOrangeJuice.fetch() to check DB for primary key
“Orange Juice” OJ Found in
Database
OJ Not Found
Call rbOrangeJuice.create() You persist the
Orange Juice object
ReferenceBuilder rbTropicana = new Manufacturer_Tropicana(); with foreign key to
Tropicana
Manufacturer tropicana = DOF.require(rbManufacturerTropicana);
Check cache, maybe call
rbTropicana.fetch() Tropicana Found
Tropicana Not Found
Call rbTropicana.create() You persist the Tropicana object
41
42. Principles of Test Automation for the DOF
From Meszaros, “xUnit Test Patterns”
– Highly recommended!
“Keep Tests Independent” – Any test can run on its own or with other
tests in any order. Independent test failures easiest to reproduce and
fix!
– With the DOF, you can run test in any order and on a clean or existing DB
schema
– Absolute nightmare having giant DB setups for tests
“Communicate Intent” -- minimize code and avoid logic in tests (if
statements)
– DOF hides the messy setup of preparing objects
– Test reader sees only objects involved directly in a test
“Don’t Modify the SUT” -- prefer testing the production code
– SUT = System Under Test – your production code
– DOF supports tests against “production” code which hits the database
42
43. Technique: Scratch Objects
Motivation: Avoid erratic tests that have database dependencies
Cause: Multiple tests depend on and modify the same objects in the database, and
thus the tests sometimes fail because of uncertain database state
Solution: Always use a fresh primary key for objects created in a test
private Customer getNewUniqueCustomer()
{
Customer customer = new Customer();
// Example of pattern to create unique PKs
customer.setName(System.currentTimeMillis() + "");
customerComponent.insert(customer);
return customer;
}
43
44. In-Memory DBs for Testing
Fixture setup for test class (not per test!)
– Load frozen copy of DB
– Load data into tables
– Understand that test may interact with each other in the data for a given test class
Breaks the “atomic” model of JUnit tests
– This solution is a little like using lock striping, or maybe the way that conferences
might break up the registration lines by first letter of last name.
– All tests are not atomic
– But groups of tests together form one “batch” where they get the database to
interact with.
Options
– H2 – supposedly the fastest
– HSQLDB – Hibernate examples are based on HSQLDB
See article on blog: http://justingordon.org
44
45. Dependent Object Framework
Open source project with EPL License: http://sourceforge.net/projects/dof
Problem: setup of required persistent (DB) objects for JUnit tests
Objectives:
– Ease of test fixture setup:
– Simply list dependencies for a test.
– Support both “reference” objects and “scratch” objects
– Performance: Tests must run quickly—Cache reference objects in memory
– Tests are stable – run any number of times in any order
– Easily distinguish between reference (shared) objects and scratch (unique, non-
shared) objects
– Either use “files and associated handlers” or “Java” to define objects
– Support deletion/garbage collection of created objects
Author: Justin Gordon based on technique used in product MDM Server for
Product Information Management
45
46. Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
46
47. Making it Happen: Dealing with Legacy Code
If only we could write everything from scratch
again!
Expect islands of old, untested, and untestable
code
– UI code tends to be particularly problematic
– Most legacy code will be essentially untestable
Try piecewise remodeling
– Discard old modules one-by-one, replacing with TDD code
Try “encrapsulation”
– Wall off legacy junk behind a façade interface (if possible; sometimes not)
– Mock out legacy code when testing new modules
Clean
CRAP Interface
47
48. Pair Programming and TDD
Complementary: Pair Programming helps with TDD
Big aid in learning TDD
One person thinks strategically, encourages the “coder” to write tests before
the implementation.
– “Don't write a line of code that doesn't fix a failing test.”
Pairing allows collaboration on solving dependency isolation issues, along
with other issues in getting first tests to run.
Once there's a body of tests to use as examples, pair is less necessary.
Highly recommended when a team is learning TDD.
48
49. Making it Happen: Getting the Team Started
Get Beck’s “Test Driven Development”, start up your IDE
and do TDD! Or try to recreate my accounting example.
Solidify commitment at every level of the organization
– TDD slower for first 6 months; net speedup afterwards.
– Don’t expect to hold the same schedule and “just add testing”!
– Systematically discover and eliminate obstacles.
– TDD takes discipline. Align incentives, communication, work
environment – everything
Consider using the Dependent Object Framework
– Will avoid need to create too many mock objects
Start with a new project or pick a subproject
– TDD can be done against existing code, but MUCH harder
– Focus on lower levels of the system first
Allow extra time
– for learning: there are many new skills and patterns to pick up.
– for re-architecture: existing architecture probably doesn’t support testing
Expect discomfort at first – developers not used to working this way. Your best
– Start with a few respected “early adopters” and a trial run bet!
49
50. WASSUP? How enterprise projects without automated
tests can end up after 8 years!
http://www.youtube.com/watch?v=Qq8Uc5BFogE
50
51. Your Challenges?
What are some of the biggest hurdles your projects face in becoming “Test
Driven”?
E-mail me with your challenges with TDD and maybe I can help.
I’m not a formal consultant, but looking for material for my book in terms of
real world enterprise problems and solutions in this area, and maybe you can
benefit.
Try out the DOF and I’d be happy to help.
51
52. Conclusion
How would you choose between a project with awesome JUnit tests and a project
without JUnit, but lots of great architectural documents and other documentation? I’d
take the JUnit one hands down.
Documentation gets out of date quickly. Code without tests may be quite buggy, and
even if it’s not buggy, would I trust myself to join a project and not introduce bugs
without JUnit?
Having a comprehensive suite of JUnit tests is the most import piece of
intellectual property in a software project.
Why? First you have very few bugs. Second, developers, new and old, can change
the code with confidence because they know immediately if they break something.
This enables the two most important activities in a software project.
– Refactoring
– Performance Tuning
And TDD is the best way to get that suite of tests (with the DOF for DB testing)!
Thank you for listening!
http://sourceforge.net/projects/dof
http://justingordon.org
justingordon@yahoo.com
52
53. Resources
Justin Gordon
justin.gordon@gmail.com
http://www.tddtips.com
https://github.com/justin808/dof
Just Do It! You cannot just read books on it!
Just Do It!
Use Eclipse or IntelliJ and try doing TDD on
a simple example
Gerard Meszaros – “xUnit Test Patterns”
Kent Beck – “Test Driven Development”,
“Extreme Programming Explained”
Lasse Koskela – “Test Driven: TDD and
Acceptance TDD for Java Developers”
Martin Fowler, especially “Refactoring:
Improving the Design of Existing Code”
Michael Feathers “Working Effectively with
Legacy Code”
53
55. Persistent Test Fixture Strategy
Purpose of the DOF is Persistent Test Fixture
Setup
Keep the test clear by separating the “fixture”
(setup of test) from the “test”
Shared Test Fixture
– Shared among multiple tests
– Addresses Performance Issues of creating
objects for each test
– Must be immutable or else erratic tests
Fresh Test Fixture (Scratch Objects)
Scratch
– Uses unique identifier so no collisions
Objects
– Used for objects your test will modify
– String pk = System.currentTimeMillis() + “”;
55
56. “Reference” Objects versus “Scratch” Objects
“Reference Objects” are
– “Shared Persistent Test Fixtures”
Reference – Background data for a test
Objects
– Loaded once and cached SUPER for PERFORMANCE
– Must not be changed by tests – immutable!
– Reference and Scratch objects can “refer” to (have foreign keys)
to either other “reference” objects or “scratch” objects
“Scratch Objects”
– “Persistent Fresh Fixtures”
Scratch – These are what the test modifies
Objects – Only visible to a single test for a single test run
– Run test multiple times and new scratch objects are created
– Must be given a unique keys (i.e., timestamp string)
– Can refer to reference objects or scratch objects
– Ensures that tests don’t interfere with each other no erratic
test runs!
56
57. DOF Basic Concepts
One file (or Java class) per object definition.
DOF takes care of recursive object creation. Thus, object dependencies are
ONLY listed in an object’s definition file (or class), never in a test.
– I.e., if object A depends on object B, you should NEVER need to specify both
object B and object A in a test. Definition of object A specifies that it depends on
object B.
Best practices for use of the DOF
– Each developer has own database.
– Simple script for populating a clean schema.
Critical to avoid changing reference objects, otherwise tests become erratic
and unstable
You can choose “Java” or “Text Files” or any combination to define objects.
Deletion (garbage collection) of created objects is available, but discouraged
(see next slide).
57
58. Java or Text Definitions for Objects?
Java Builder Pattern
– Create one Java class per reference object of scratch object definition
– Implement interface ReferenceBuilder or ScratchBuilder
– Advantage:
– Full support of IDE for refactoring and navigation
– More flexibility with Java code for each object
– More explicit object creation
– Disadvantage: Can be very verbose compared to a properties file or XML
Text Files and Associated Handlers
– For each class (file type), write a handler class that implements interface
DependentObjectHandler
– Create one file per reference object or scratch object
– DOF takes care of the plumbing
– Advantage: More concise format
– Disadvantage: Can be awkward if changes are needed
Use of Java and Text are not exclusive! Interoperable!
58
59. DOF FAQ
How is this different from DBUnit?
– DBUnit focuses on DB rows. DOF focuses on object dependencies
Does the DOF only work with database dependencies?
– No. Dependencies can be anywhere, such as with web services.
Should I use an in-memory DB?
– Being able to switch your DB from Oracle or DB2 to an in-memory one for testing
is highly recommended for speeding up test execution. Check out H2 and
HSQLDB.
I work on a shared DB and I can’t simply recreate the schema. Help!
– No problem. Make sure that you run DOF.deleteAll(DOF.DeletionOption.all) when
you tear down your tests. If you do that, you will avoid adding any records to the
DB.
59
Notas del editor
Justin
Wikipedia: http://en.wikipedia.org/wiki/Refactoring In software engineering, the term refactoring means modifying source code without changing its external behavior, and is sometimes informally referred to as "cleaning it up". In extreme programming and other agile methodologies refactoring is an integral part of the software development cycle: developers alternate between adding new tests and functionality and refactoring the code to improve its internal consistency and clarity. Automated unit testing ensures that refactoring does not make the code stop working. Refactoring does not fix bugs or add new functionality. Rather it is designed to improve the understandability of the code or change its structure and design, and remove dead code, to make it easier for human maintenance in the future. In particular, adding new behavior to a program might be difficult with the program's given structure, so a developer might refactor it first to make it easy, and then add the new behavior. An example of a trivial refactoring is to change a variable name into something more meaningful, such as from a single letter 'i' to 'interestRate'. A more complex refactoring is to turn the code within an if block into a subroutine. An even more complex refactoring is to replace an if conditional with polymorphism. While "cleaning up" code has happened for decades, the key insight in refactoring is to intentionally "clean up" code separately from adding new functionality, using a known catalogue of common useful refactoring methods, and then separately testing the code (knowing that any behavioral changes indicate a bug). The new aspect is explicitly wanting to improve an existing design without altering its intent or behavior.
“ Normal” development cycle inhibits JUnit test creation Developers rewarded for getting something “working” ASAP Code like mad to meet a deadline (DCut?); testing is an afterthought Throw it over the fence to QA and fix bugs as they’re found Motivation to write tests for “already-working” code is low! Inertia : hard to change ingrained working habits
Ask about purity of TDD in practice.
Any time you write code that is not fixing a failing test. I.e., Writing code, then writing tests or intending to eventually write tests. No matter how good they are! Relying on QA to automate their manual tests. Especially using SilkTest or similar. Be honest when trying this. If it isn’t TDD, you won’t get the benefits What about the conventional approach of have the architects design the APIs, the developers code to the specs, and QA tests? Is that TDD?
You cannot do TDD without the right tools Emacs or VIM??? These do not support single test running and refactoring,
Note – this slide should be skipped in a short presentation. In a longer presentation, this material will be reviewed several times.
Note – this slide should be skipped in a short presentation.
The setup and teardown boxes are grey to indicate that the reader of the test should be able to focus on the logic of what is done and what is verified (orange boxes)
Production code for end users will hit the DB, so you need to test it! Conventional Wisdom: Real xUnit tests don’t use a DB! But, developers are working on existing codebases that have countless intricate ties to the DB. Common advice is to refactor in order to isolate database dependencies in order to build tests that don’t hit the database, but without tests, how safely can we do the refactoring? Catch 22! Mocks and stubs add (much) more code to maintain (and to fix bugs!). Reliance on mocks and stubs can mask errors with using the DB. Annecdote: GuideWire Software used to depend heavily on stubs and dependency isolation techniques, but now focuses exclusively on tests against the database, using H2 in-memory DB to speed up tests.
This slide tells its story via animation. If you’re reading this slide, then the “blinds” effect means “check” for this object and “pinwheel” means “create the object”
Discuss this slide first with the DOF plumbing and then as if there was no DOF plumbing.
Performance checks if the object with the path was already run, and if so, nothing is done. Then checks if given named object exists in the DB. The name is decoded from the path name. If the name exists, the script is not run.