Slide 1
Justin GordonEnterprise TDD EvangelistSenior Software EngineerIBM, Master Data Management Collaboration [email protected]
http://www.tddtips.com
https://github.com/justin808/dof
JUnit and Test Driven Development:
Experiences in MDM CS 2003 to 2012
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
Success Factors
Nature of the projectExisting, legacy, and not designed for testing?
New codeHow many dependencies and how containable?
Motivation of the teamTDD is hard work!
LearningThere'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 keyContinuous Integration including Code Coverage Reports
BenefitsQuality!
Sanity for the overall team, including developers, QA, and managers
Test First CodeReally affects the overall structure of the code, as code is written to be testable.
Most important overall is to have automation tests!
Summary of MDM CS JUnit
Check out my blog articles, especially this one:
JUnit and Test Driven Development for MDM CS -- Fixtures and FactoriesSerialization development was perfect for jUnit
High algorithmic complexity, limited outside dependencies
Rest of the product for jUnitChallenging 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.
Presentation given at SD West, 2009
JUnit and Test Driven Development:
Why and How in Enterprise Software
Roadmap
Conventional versus Agile
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
Conventional versus Agile
CONVENTIONALArchitects 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!
AGILEStories 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!
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
Justin
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
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);}
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. Cant do that with manual QA! Cant 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
Wikipedia: http://en.wikipedia.org/wiki/RefactoringIn 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.
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: Its QAs job to find my bugs. I dont 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!
Normal development cycle inhibits JUnit test creationDevelopers rewarded for getting something working ASAPCode like mad to meet a deadline (DCut?); testing is an afterthoughtThrow it over the fence to QA and fix bugs as theyre foundMotivation to write tests for already-working code is low!
Inertia: hard to change ingrained working habits
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
What is Test Driven Development (TDD)?
Programming practice in which all production code is written in response to a failing test.
Read Kent Becks: Test Driven Development By Example
List Requirements
Write One Test
Run Test toMake Sure It Fails
Add or modify just enough code to make new test pass and all previous tests pass
Refactor to eliminate code smells (e.g., duplicated code)
Ask about purity of TDD in practice.
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!
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 isnt TDD, you wont 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?
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!
Shooting hoops? Practice Makes Perfect
Developers improve skills because of the immediate feedback from the tests, rather than months later from QA!
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!
Write Test
Write Code
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!
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
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
You cannot do TDD without the right tools
Emacs or VIM??? These do not support single test running and refactoring,
Code Coverage Inside Eclipse
Code Coverage in Eclipse provided by EclEmma: http://www.eclemma.org/index.html
Green lines indicate coverage by unit tests.
Pink lines indicate code not covered by unit tests
Exercise 1: Simple TDD: Compute Change Coins
Problem: Compute Change Coins
Coins include: pennies, nickels, dimes, quarters
Machine may be out of any coin except pennies
Give least number of coins
Example:All coins available:
37 cents: 1 quarter, 1 dime, 2 pennies
80 cents: 3 quarters, 1 nickel
No nickels:
80 cents: 2 quarters, 3 dimes
No dimes:
85 cents: 3 quarters, 2 nickels
public class Change{ public int pennies, nickels, dimes, quarters;public Change(int pennies, int nickels, int dimes, int quarters) {this.pennies = pennies; this.nickels = nickels;this.dimes = dimes;this.quarters = quarters; }
public class VendingMachine{// the number of coins leftpublic int pennies, nickels, dimes, quarters;public Change getChange(int cents){ }}
public class VendingMachineTest extends TestCase {public void testGetChangeReturnsNoChangeIfNoCents){ }}
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?
Method names print out in test failures
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
Exercise 1: Steps for Initial example
Run VendingMachineTestSuite to see all tests run
Run the test suite with code coverage turned on to see code coverage
Uncomment Change.toString() to see effect on the error message implement toString() for better messages
Uncomment Change.equals() to see VendingMachineTest tests pass.
Put @Ignore in front of failing test and run all tests to see that no failures and 2 ignored tests
Exercise 1b: Steps for Intermediate
Check out the triangulation method of verification.
Fix the test testGetChangeReturnsThreePenniesIfTwoCents()
Check out the use of the @Before setup of the variable VendingMachine
Complete the rest of the commented tests
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.
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
Architectural benefits of TDD
With TDD
Better abstractions and better decoupling of classes.
Loose coupling, otherwise impossible to test individual components.
When using Test Doubles (e.g., test stubs and mock objects), you want to mock out smaller APIs or else youll work too hard! Keep classes small and methods tight.
Loose Coupling: TDD!
A
B
D
C
e
cc
bb
dd
B
A
D
E
C
Tight Coupling!
interface
class
Without TDD, you often see tight coupling between classes and throughout to all dependencies, making the implementation of new tests prohibitively painful.
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!
Dont mock out the database!
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.
Class To Test
Dependency(Runtime Object)
Dependency(Test Double, e.g. Mock Object)
Interface
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 TaxRateProviderGlobalContext.setTaxRateProvider(new MockTaxRateProvider());
Production setup code:
GlobalContext.setTaxRateProvider(new SoapTaxRateProvider());
Production Code sees interface does NOT know if real or test double!!
class OrderProcessor public float getTaxRateForState(String state) { 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
Static method returns interface!
Note this slide should be skipped in a short presentation. In a longer presentation, this material will be reviewed several times.
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 TaxRateProviderTaxRateProvider 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
class OrderProcessor { TaxRateProvider taxRateProvider;// constructor injection OrderProcessor (TaxRateProvider taxRateProvider ) { this.taxRateProvider = taxRateProvider;}
public float getTaxRateForState(String state) { return taxRateProvider.getSalesTax(state); }
Member interface variable!
Note this slide should be skipped in a short presentation.
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
Mockito
Highly recommended! First, you have to isolate dependencies. Then mock: http://code.google.com/p/mockito/
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
Basic Phases of an xUnit Test
SetupPrepare Test Fixtures -- Get Application State Ready To Do Something
TeardownRestore application state for next test
Exercise Do Something With Fixtures Prefer Single Action
VerifyCheck Something Happened: assertSomething()
xUnit Test
DB Objects and the DOF
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)
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.
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.
Production code for end users will hit the DB, so you need to test it!
Conventional Wisdom: Real xUnit tests dont 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 dont 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.
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
Simple Example Logical Model
Invoice
Customer
Product
Manufacturer
Logical Object ModelDefines object relationships behind the DOF setup but the DOF setup is about the instances of these objects that have representations in the database
Data Relationships and Object Creation
Invoice 1001
Customer Jones
Product Orange Juice
ManufacturerOcean Spray
Product Grape Juice
Objects CreatedDOF takes care ensuring these DB backed objects are ready for your test
With DOF, test only specifies top level dependency!
This slide tells its story via animation. If youre reading this slide, then the blinds effect means check for this object and pinwheel means create the object
Object Dependency Processing: Java Reference Object
Call rbOrangeJuice.fetch() to check DB for primary key Orange Juice
Return Object Orange Juice
Call rbOrangeJuice.create()
ReferenceBuilder rbTropicana = new Manufacturer_Tropicana();Manufacturer tropicana = DOF.require(rbManufacturerTropicana);
Object pk = rbOrangeJuice.getPrimaryKey() Check cache for Orange Juice object
OJ Found in Database
OJ Foundin Cache
OJ Not Found
OJ Not Found
Check cache, maybe call rbTropicana.fetch()
Call rbTropicana.create()
You persist the Tropicana object
You persist the Orange Juice object with foreign key to Tropicana
Tropicana Found
ReferenceBuilder rbOrangeJuice = new Product_OrangeJuice();Product orangeJuice = (Product) DOF.require(productOJ);
Tropicana Not Found
Your ReferenceBuilder code
DOF plumbing
Your test code
Discuss this slide first with the DOF plumbing and then as if there was no DOF plumbing.
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
Dont 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
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; }
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
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 quicklyCache 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
Performance checksif 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.
Roadmap
A tale of two development groups
JUnit Tests
Test Driven Development
Tools
TDD Architecture
Database Techniques: DOF
Getting Started
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 faade interface (if possible; sometimes not)
Mock out legacy code when testing new modules
CRAP
CleanInterface
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.
Making it Happen: Getting the Team Started
Allow extra time
for learning: there are many new skills and patterns to pick up.
for re-architecture: existing architecture probably doesnt support testing
Expect discomfort at first developers not used to working this way.
Start with a few respected early adopters and a trial run
Get Becks 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.
Dont 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
Your best bet!
WASSUP? How enterprise projects without automated tests can end up after 8 years!
http://www.youtube.com/watch?v=Qq8Uc5BFogE
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.
Im 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 Id be happy to help.
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? Id take the JUnit one hands down.
Documentation gets out of date quickly. Code without tests may be quite buggy, and even if its 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/dofhttp://[email protected]
Resources
Justin [email protected]://www.tddtips.comhttps://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
Extras
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)
Uses unique identifier so no collisions
Used for objects your test will modify
String pk = System.currentTimeMillis() + ;
ReferenceObjects
ScratchObjects
Reference Objects versus Scratch Objects
Reference Objects are
Shared Persistent Test Fixtures
Background data for a test
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
These are what the test modifies
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 dont interfere with each other no erratic test runs!
ReferenceObjects
ScratchObjects
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 objects 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).
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!
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 cant 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.