xUnit and TDD: Why and How in Enterprise Software, August 2012
Upcoming SlideShare
Loading in...5
×
 

xUnit and TDD: Why and How in Enterprise Software, August 2012

on

  • 1,043 views

“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 ...

“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.

Statistics

Views

Total Views
1,043
Views on SlideShare
982
Embed Views
61

Actions

Likes
0
Downloads
18
Comments
0

2 Embeds 61

http://www.tddtips.com 60
http://www.slashdocs.com 1

Accessibility

Categories

Upload Details

Uploaded via as OpenOffice

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • 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.

xUnit and TDD: Why and How in Enterprise Software, August 2012 xUnit and TDD: Why and How in Enterprise Software, August 2012 Presentation Transcript

  • 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
  • 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
  • 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 – Theres a ton to learn about writing good tests. Lots of patterns. Lots of pitfalls. – Recommended: xUnit Test Patterns, but its 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
  • 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 - Cant we all just get along. 4
  • Presentation given at SD West, 2009JUnit and Test Driven Development:Why and How in Enterprise Software 5
  • RoadmapConventional versus AgileJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 6
  • Conventional versus AgileCONVENTIONALArchitects  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! 7
  • Premise“A comprehensive suite of JUnit tests is one of the most import aspects ofa software project because it reduces bugs, facilitates adding newdevelopers, and enables refactoring and performance tuning withconfidence. Test-driven development (TDD) is the best way to build a suiteof tests. And the Dependent Object Framework is the best way to testagainst database objects.”Justin Gordon 8
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 9
  • 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
  • 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
  • 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
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 13
  • What is Test Driven Development (TDD)?“Programming practice in which all production codeis 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
  • 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
  • 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
  • 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
  • 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
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 19
  • 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
  • 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
  • Exercise 1: Simple TDD: Compute Change Coins public class ChangeProblem: 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 VendingMachineExample: { // the number of coins leftAll 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
  • 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
  • Exercise 1: Steps for “Initial” example1. Run VendingMachineTestSuite to see all tests run2. Run the test suite with code coverage turned on to see code coverage3. Uncomment Change.toString() to see effect on the error message  implement toString() for better messages4. 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
  • 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 VendingMachine4. Complete the rest of the commented tests5. Handle the tougher cases where the vending machine can run out of a certain kind of coinQuestion: Can we exhaustively find all the solutions and check the code against these solutions? Possibly could write tests to use triangulation. 25
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 26
  • 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
  • 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
  • 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
  • Dependency Isolation: Static Location Example: Class fetches data from the database or from a mock source. Buthow does your code get a handle to the correct Component?Dependency location: your class looks up the dependency (testdouble or real instance) from a known location, typically a staticmethod 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 ortest double!!class OrderProcessor Static methodpublic 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
  • 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
  • Dependency Injection versus Service Locator“Inversion of control is a common feature of frameworks, but its 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 isnt to say its 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 isnt a big deal….The difference comes if the lister is a component that Im providing to an application that other people are writing. In this case I dont 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 Fowlerhttp://martinfowler.com/articles/injection.html#ServiceLocatorVsDependencyInjection 32
  • MockitoHighly recommended! First, you have to isolate dependencies. Then mock:http://code.google.com/p/mockito/ 33
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 34
  • Basic Phases of an xUnit Test Setup Prepare Test Fixtures -- Get xUnit TestApplication 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
  • 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
  • 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
  • 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
  • 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
  • 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
  • Object Dependency Processing: Java Reference ObjectYour test code DOF plumbing Your ReferenceBuilder codeReferenceBuilder 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 FoundCall rbOrangeJuice.fetch() to check DB for primary key“Orange Juice” OJ Found in Database OJ Not FoundCall rbOrangeJuice.create() You persist the Orange Juice objectReferenceBuilder rbTropicana = new Manufacturer_Tropicana(); with foreign key to TropicanaManufacturer tropicana = DOF.require(rbManufacturerTropicana);Check cache, maybe callrbTropicana.fetch() Tropicana FoundTropicana Not FoundCall rbTropicana.create() You persist the Tropicana object 41
  • 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
  • Technique: Scratch ObjectsMotivation: Avoid erratic tests that have database dependenciesCause: Multiple tests depend on and modify the same objects in the database, and thus the tests sometimes fail because of uncertain database stateSolution: Always use a fresh primary key for objects created in a testprivate Customer getNewUniqueCustomer() { Customer customer = new Customer(); // Example of pattern to create unique PKs customer.setName(System.currentTimeMillis() + ""); customerComponent.insert(customer); return customer; } 43
  • 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
  • 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
  • RoadmapA tale of two development groupsJUnit TestsTest Driven DevelopmentToolsTDD ArchitectureDatabase Techniques: DOFGetting Started 46
  • 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
  • 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. – “Dont write a line of code that doesnt fix a failing test.” Pairing allows collaboration on solving dependency isolation issues, along with other issues in getting first tests to run. Once theres a body of tests to use as examples, pair is less necessary. Highly recommended when a team is learning TDD. 48
  • Making it Happen: Getting the Team StartedGet Beck’s “Test Driven Development”, start up your IDEand 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
  • WASSUP? How enterprise projects without automatedtests can end up after 8 years!http://www.youtube.com/watch?v=Qq8Uc5BFogE 50
  • 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
  • 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/dofhttp://justingordon.orgjustingordon@yahoo.com 52
  • ResourcesJustin Gordonjustin.gordon@gmail.comhttp://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” 53
  • Extras… 54
  • 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
  • “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
  • 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
  • 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
  • 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