views:

326

answers:

4

I'm currently on a co-op term working on a project nearing completion with one other co-op student. Since this project has been passed down from co-op to co-op, poor practices have been taken along the way and testing has been left until the end. I've decided I'd like to write unit-tests to learn something new while testing.

However, I'm working on a 3-tier, tightly coupled app that seems impossible to unit test in its current form. I don't want to throw off the other co-op student with no knowledge of any of these concepts by refactoring the code beyond recognition overnight. So what steps should I take to slowly pull the code towards unit-testability? Should I first implement a factory pattern and let the other student familiarize themselves with that before moving forward?

My apologies if my knowledge is flawed and there should be no issue whatsoever. I'm new to this :)

+1  A: 

It's very difficult to start new development practises mid way through a project. In the past when I've worked on projects that haven't been unit tested from the start, a good approach to take is to set down the rule that 'new code must have unit tests' but don't put pressure on unit tests being written for old code.

Of course, even this is difficult when the structure of the project is not suited to testability.

My best recommendation would be take it in small steps.

Start by creating your unit test assembly, (or project or whatever) with no tests in it. Then find a single small area of code that is fairly well defined and seperated, and write some unit tests for that area. Get your co-coder to take a look too and start getting some 'best practises' going, like running the unit tests every time any code is checked in (automatically if possible).

Once you have that working, you can slowly start to add more.

The key is slowly. And like I said, it's easier to make old code exempt from the testing to begin with. You can always return to it later once your team has grasped the idea of unit testing and has become better at writing them.

Simon P Stevens
seems like a solid idea that! Nice one! +1
FailBoy
This is a great idea. However, most of the functionality has already been implemented. I am mostly doing this for experience in unit-testing, as manual testing for the next couple of weeks would be quite dry.
Chris
From the sound of it, this is a student project yes? If you say most of the functionality is done then you can't have that long left, just take a backup and go ahead and make the changes. If your partner doesn't understand, it doesn't matter, the projects nearly over anyway. You get your experience, and the project is still OK. Put in your write up that you have learnt that unit testing would be best implemented from the beginning next time =:)
Simon P Stevens
This is correct! Kind of. It is a government co-op, so things move slowly. Until the boss reviews the app and tells us what changes he wants (which could take months), we are in testing mode. At this point, the system may turn inside out. Unit tests will not only help me learn, but also ease this process, hopefully. I simply don't want to be disrespectful and leave the other co-op in the dark :)
Chris
Ahh... If you are expecting potential changes then unit testing is invaluable. It allows you to have confidence that the changes you make don't break other things. I would talk to your co-worker and agree a plan between you. Explain why you want to do it, and get him on board with you. Then come up with a plan for implementing it, even if the plan is the same, at least you have his agreement.
Simon P Stevens
+6  A: 

Working Effectively with Legacy Code by Michael Feathers

Hard to know if implementing a factory pattern will do any good, depends on what the code is doing :)

Aidan
+1 Here's a summary of what you'd be getting in the book and how to create a "seam" that you can test safely: http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf
Even Mien
+1  A: 

How about writing a series of black-box tests around major pieces of functionality in your code? Since you mention that it's an ASP.NET project, you can use a framework such as WaitN or Selenium to automate a web browser. This gives you a baseline set of functionality that should remain constant no matter how much the code changes.

Once you have a comfortable number of tests testing the high-level functionality of your project, I'd then start diving into the code, and as Simon P. Stevens mentions, work slowly. Grab a (free!) copy of Refactor! for Visual Basic, so you'll be able to automatically perform some basic refactoring, such as Extract Method. You can drastically increase testability without changing any functionality just by splitting up larger chunks of code into smaller, more testable chunks.

Jeremy Frey
+1  A: 

Working Effectively with Legacy Code by Michael Feathers (also available in Safari if you have a subscription) is an excellent resource for your task. The author defines legacy code as code without unit tests, and he gives practical walkthroughs of lots of conservative techniques—necessary because you're working without a safety net—for bringing code under test. Table of contents:

  • Part: I The Mechanics of Change
    • Chapter 1. Changing Software
      • Four Reasons to Change Software
      • Risky Change
    • Chapter 2. Working with Feedback
      • What Is Unit Testing?
      • Higher-Level Testing
      • Test Coverings
      • The Legacy Code Change Algorithm
    • Chapter 3. Sensing and Separation
      • Faking Collaborators
    • Chapter 4. The Seam Model
      • A Huge Sheet of Text
      • Seams
      • Seam Types
    • Chapter 5. Tools
      • Automated Refactoring Tools
      • Mock Objects
      • Unit-Testing Harnesses
      • General Test Harnesses
  • Part: II Changing Software
    • Chapter 6. I Don't Have Much Time and I Have to Change It
      • Sprout Method
      • Sprout Class
      • Wrap Method
      • Wrap Class
      • Summary
    • Chapter 7. It Takes Forever to Make a Change
      • Understanding
      • Lag Time
      • Breaking Dependencies
      • Summary
    • Chapter 8. How Do I Add a Feature?
      • Test-Driven Development (TDD)
      • Programming by Difference
      • Summary
    • Chapter 9. I Can't Get This Class into a Test Harness
      • The Case of the Irritating Parameter
      • The Case of the Hidden Dependency
      • The Case of the Construction Blob
      • The Case of the Irritating Global Dependency
      • The Case of the Horrible Include Dependencies
      • The Case of the Onion Parameter
      • The Case of the Aliased Parameter
    • Chapter 10. I Can't Run This Method in a Test Harness
      • The Case of the Hidden Method
      • The Case of the "Helpful" Language Feature
      • The Case of the Undetectable Side Effect
    • Chapter 11. I Need to Make a Change. What Methods Should I Test?
      • Reasoning About Effects
      • Reasoning Forward
      • Effect Propagation
      • Tools for Effect Reasoning
      • Learning from Effect Analysis
      • Simplifying Effect Sketches
    • Chapter 12. I Need to Make Many Changes in One Area. Do I Have to Break Dependencies for All the Classes Involved?
      • Interception Points
      • Judging Design with Pinch Points
      • Pinch Point Traps
    • Chapter 13. I Need to Make a Change, but I Don't Know What Tests to Write Characterization Tests
      • Characterizing Classes
      • Targeted Testing
      • A Heuristic for Writing Characterization Tests
    • Chapter 14. Dependencies on Libraries Are Killing Me
    • Chapter 15. My Application Is All API Calls
    • Chapter 16. I Don't Understand the Code Well Enough to Change It
      • Notes/Sketching
      • Listing Markup
      • Scratch Refactoring
      • Delete Unused Code
    • Chapter 17. My Application Has No Structure
      • Telling the Story of the System
      • Naked CRC
      • Conversation Scrutiny
    • Chapter 18. My Test Code Is in the Way
      • Class Naming Conventions
      • Test Location
    • Chapter 19. My Project Is Not Object Oriented. How Do I Make Safe Changes?
      • An Easy Case
      • A Hard Case
      • Adding New Behavior
      • Taking Advantage of Object Orientation
      • It's All Object Oriented
    • Chapter 20. This Class Is Too Big and I Don't Want It to Get Any Bigger
      • Seeing Responsibilities
      • Other Techniques
      • Moving Forward
      • After Extract Class
    • Chapter 21. I'm Changing the Same Code All Over the Place
      • First Steps
    • Chapter 22. I Need to Change a Monster Method and I Can't Write Tests for It
      • Varieties of Monsters
      • Tackling Monsters with Automated Refactoring Support
      • The Manual Refactoring Challenge
      • Strategy
    • Chapter 23. How Do I Know That I'm Not Breaking Anything?
      • Hyperaware Editing
      • Single-Goal Editing
      • Preserve Signatures
      • Lean on the Compiler
    • Chapter 24. We Feel Overwhelmed. It Isn't Going to Get Any Better
  • Part: III Dependency-Breaking Techniques
    • Chapter 25. Dependency-Breaking Techniques
      • Adapt Parameter
      • Break Out Method Object
      • Definition Completion
      • Encapsulate Global References
      • Expose Static Method
      • Extract and Override Call
      • Extract and Override Factory Method
      • Extract and Override Getter
      • Extract Implementer
      • Extract Interface
      • Introduce Instance Delegator
      • Introduce Static Setter
      • Link Substitution
      • Parameterize Constructor
      • Parameterize Method
      • Primitivize Parameter
      • Pull Up Feature
      • Push Down Dependency
      • Replace Function with Function Pointer
      • Replace Global Reference with Getter
      • Subclass and Override Method
      • Supersede Instance Variable
      • Template Redefinition
      • Text Redefinition
  • Appendix: Refactoring
    • Extract Method
Greg Bacon