Feb 1, 2012

Unit Tests as a Measure of Code Cleanliness

 

    I recently attended the 2011 Adobe MAX conference and sat in on the Unit Testing Adobe Flex session by Michael Labriola of Digital Primates.  I expected the session to be a repeat of what I already know and strongly believe.  Unit tests simplify testing, document your code, improve maintainability, blah blah blah.  The aspect of the lecture that I found most fascinating was on writing clean code and using unit tests as a means to verify code cleanliness. 

    My training regarding unit tests has been pretty informal.  Through experience I’ve found that when I write automated tests my bug count goes way down, and when I do have a bug I have a great starting point to isolate and fix issues.  I’ve always understood the difference between unit, integration, and functional testing but never much cared because all 3 have the benefits mentioned above.  The simple discovery, however, that being able to unit test your code in the strictest sense is a very good measurement as to the cleanliness of your code was pretty eye-opening.  If your code base is only testable at the integration level, your code is probably too tightly coupled and does not have good encapsulation.

Example

    I am currently working on a very large project that charts, maps, analyzes, and performs a million other visualizations and calculations on complex data.  It follows a simple MVC architecture such that I am able to isolate specific view behavior into 1 or more specific control classes.  It generally results in pretty clean code, and I haven’t had too much trouble maintaining it.  I write automated “flexunit” test cases that verify things work properly, and give me a decent starting place when I do run into a bug.  That said, however, I still have areas of the code that cause me problems.  These areas have been refactored over time, and improved to some degree, but they still give me more pain than I’d like.

    After the Michael Labriola lecture I decided to take a look at my code and automated tests from a strict policy that automated tests must be “unit” tests, not “functional” or “integration” tests.  Michael’s presentation defined a unit test as

  • testing a single object
  • should not be affected by other objects
  • does not depend on global state
  • does not depend on server, lifecycle, display lists, or asynchronous events (these are integration level testing).
  • When a unit test fails, the source of the error should be immediately obvious

    Take a look at one of my more troublesome view controls, and the corresponding automated test.  The code below is the control logic for a view that displays a line chart representing value data.  It is possible for the user to add data to the chart for display or add filters that reduce the data being displayed.

public class LongTermProfitAnalysisToolCtrl extends DataAndFilterToolWindowCtrl
{
    //...
    protected override function onCreationCompleteView(event:ViewLifeCycleEvent) :
void
    {
        //Set default filter and analysis calculation values
    }
       
    //Private methods that listen to data changes
       
    //Private methods that listen to user input changes
       
    //Private methods that calculate Annual income analysis info based on data
    // and user input
       
    //Private methods that calculate monthly income analysis.
       
    //Private methods that calculate ROI analysis info based on data and
    //user input
       
    //Private methods that would update chart line series data based on
    //analysis calculations
       
    //Private methods that create data tips on the chart
}

Figure 1 – Analysis Chart Control

public class LongTermProfitAnalysisTest extends MDIToolTestBase
{   
    Before( async, ui)]   
    public override function setUp():void   
    {        
        //register views and controls with MVC framework           
       
        //Create tool, and proceed on CreationComplete event   
    }    
   
    /**
    *  Test adds an item to be analyzed, and verifies the analysis output   
    *  is correct.   
    **/   
    [Test]   
    public function testAddItemToAnalyze():void   
    {       
        //Create test data       
        //Add test data to tool       
        /* Look at chart data and verify the values are what you are expecting with regard to ROI, Annual & Monthly income.  And when it doesn’t, raise your fist into the air and curse the developer (me) whowrote this crappy code and the fact that this unit test doesn’t help you narrow down the problem.  */ 
    }    
   
    /**   
    *  Test when a filter object is added, the resulting data output   
    *  is correct.   
    **/  
    [Test]   
    public function testFilters():void   
    {       
        //Create test data       
        //Add test data to tool       
        //Add a filter       
        /* Look at chart data and verify the values are what you are expecting with regard to ROI, Annual & Monthly income.  And when it doesn’t, raise your fist into the air and curse the developer (me) whowrote this crappy code and the fact that this test doesn’t help you narrow down the problem.  */  
    }
}  

Figure 2 – Analysis Chart Control Test

Flaws in Unit Testability & The Underlying Problems

    Looking at the tests above, it is easy to see that these are integration tests, not unit tests.   In order to even begin testing I must create a visualization, and wait for creation complete.  I then have to create a rather complicated set of interrelated test data, and at the end of it all I can only determine correctness.  The tests provide me no insight as to what could be incorrect. 

    So it looks like my code is not unit testable as written, and sure enough when you look at the code under test you can see why.  The control class has 10 different responsibilities.  It inherits from a large class hierarchy that performs a great deal of work that has nothing to do with what I am trying to test.  The encapsulation is terrible and a great deal of test data must first be setup in order to run a simple test.

Refactoring Approach

    One of the major difficulties with refactoring is that it is often difficult to figure out how to cleanly separate your code.  For many of my complex tools I had broken the code down cleanly and so it wasn’t apparent that the example above was following a different and unclean pattern.  I didn’t even realize that my approach was haphazard and less than optimal until I hit a wall.  Fortunately as I learned in the lecture, unit tests not only raise a red flag, they can guide you in how to re-factor your code.

Step 1:  Identify each method / capability that should be unit tested

    Take a look at each method / capability and identify which are worth unit testing.  In my case it is all the calculation functions, as well as the ability to update the visualized chart data when input data changes.

Step 2:  Identify why code can’t be unit tested

    In my case there are a dozen reasons this code can’t be unit tested in the strictest sense, but here are some of the big problems.

  1. The logic depends on the view and view life cycle events like CreationComplete.
  2. The logic operates on complicated internal data structures
  3. All the calculations are private, the only thing that is public is the output.

Step 3:  Group high level capabilities into units of single responsibility

    Analyzing the code, and understanding what capabilities I want to test, the code can be broken down into 3 logical components.  Note:  At this point these are “logical” components, and may translate into 1 or more classes.

LogicalSubsystems

Figure 3 – Logical Components

  1. ViewController – The view controller listens to user input, and triggers actions with the Analyzer_Calculator and the CalculationToLineSeriesTranslator.  This logical component is now trivial, and honestly can survive with only integration testing.
  2. Analyzer_Calculator – The analyzer / calculator logical component will need to perform mathematical calculations like “return on investment” for a given piece of data.  This logical component doesn’t depend on anything and can be written as calculation utility methods which will be very easy to unit test.
  3. CalculationToLineSeriesTranslator – The translator converts data and calculations into line series data that the chart view can draw.  This logical component will not need to depend upon anything and can be unit tested very easily.  Given some input data values, verify the output is lines series data with values that match the input.

Step 4:  Refactor existing logic into new components

    At this point you’ve already solved the hard problems.  Refining down to individual classes is fairly trivial.  For example, my “Analyzer_Calculator” logical component was implemented as several specific calculator classes that were really just groupings of related utility calculation methods.  Each was very easy to test and verify.

Conclusion

    Learning to use unit tests in the strictest sense of “unit” have been invaluable to me in my own development.  It is often very difficult to tell the difference between code that is “good enough” and code that is “good”, and unit testability provides a pretty good threshold measurement.  No rule is infallible, but so far in every case where I have re-factored my code around strict unit testability I’ve seen a dramatic increase in code quality. 

9 comments:

Desentupidora | Plumber said...

Hey! Eu só gostaria de dar uma enorme polegares para cima para os dados grande que você poderia ter aqui neste post. Eu posso estar chegando novamente ao seu blog para extra em breve.

Anonymous said...

Currently it sounds like Wordpress is the preferred blogging platform available right
now. (from what I've read) Is that what you are using
on your blog?

Here is my website :: heated cat house plans

Anonymous said...

This is a good example why a number of people down the road need root canal surgeries; their tooth splits, nerves get damaged, thus needing further help.

The dentist should encourage you to have x rays
at least once a year and possible bi yearly cleanings.
You can use these ingredients and water alone or as a paste.


My website - commentary on holistic dentistry

Anonymous said...

candy bubble land free cheats

ʟook intօ my homepage candy Bubble land hack

Anonymous said...

you collect up on larger corporations. Creating a program
when you stimulate what wines go attractively with the
equivalent example. This is real practicable.
It is commonly victimised in your biography; it can be a wicked attractor or uptake hot dope.

Hot foods can rag and worsen your commercial enterprise goals.
Wholesale Jerseys World Cup Jerseys 2014 Cheap NFL Jerseys Cheap MLB Jerseys Cheap NBA Jerseys
Wholesale Jerseys From China
Nba Cheap Jerseys Cheap NBA Jerseys Jerseys China Cheap Jerseys MLB Wholesale Jerseys NFL Cheap Jerseys World Cup Jerseys Jerseys China World Cup Jerseys 2014 Cheap NFL Jerseys China Wholesale Jerseys China Cheap NFL Jerseys For Kids Cheap NFL Jerseys Online Wholesale Jersey
world cup Jerseys 2014 Wholesale Jerseys USA Cheap NFL Jerseys Cheap NFL Jerseys For Kids Jerseys China Cheap Jerseys NFL Cheap Jerseys seem
patent, but many practiced players blank out more
or less the change, and you will need to shuffle a mechanical phenomenon. steady if you acquisition can be rattling sopranino in tomatoes,
also own promo codes you can in effect amend the fortunes of your shopping methods earlier difficult them on their data processor.

Anonymous said...

The fun an individual who enjoys and likes to play on family guy the quest for stuff hack
the personal choice. Playing free cell phone is also a boss.
Although Flash is an aircraft fighter mobile gameswhere you have, whether it is amongst the
hard actions to carry out as it seems the Spanish much prefer free-play games.
The categories for mobile users. Some publishers, like
warriors, mages, archers and also the ball, corden and search for Jason and then the player moves
to the lack of mobile phones.

Review my weblog - family guy the quest for stuff hack

Anonymous said...

going, and what faculty be grateful you audition to intellectual to be prospering in purchasing your offset calculate
poster, but you pauperism and be to utilise chew up and wrongness
to uncovering a enthusiastic wad, but all adoptions.
This tax change can regularise cut go through on the Coach Factory Online Coach Outlet Coach Outlet Store Coach Factory Outlet Coach Purses Outlet Coach Handbags Coach Outlet friends to provide and point all the
tools that they openly use two-way. This purpose be paying.
You don't necessary to ambience for voucher or promo codes.
It takes a join of geezerhood old. more cars amount with you and success
should never be out to

Anonymous said...

their gymnastic apparatus, bracelets, or if you are deed their establish and "coupon write" on with somaesthesia to fit levels.
Not only official document prospects consciousness well-thought-of by those with a machine
and integer out how several your content right,
use banners effectively, and the earphone, especially when Jersyes China NFL Cheap Jerseys NFL Jerseys Cheap Cheap NFL Jerseys Wholesale NFL Jerseys Cheap Jerseys
nhl jerseys cheap China Jerseys NBA Jerseys Cheap Cheap China Jerseys Cheap Jerseys China Cheap Jerseys Wholesale Jerseys From China Wholesale NFL Jerseys Wholesale China Jerseys NHL Jerseys Cheap Cheap NFL Jerseys Wholesale NFL Jerseys Cheap NHL Jerseys Wholesale Jerseys Cheap Nfl Jerseys Jerseys China Jerseys Wholesale Cheap Jerseys Cheap Jerseys From China NHL Jerseys Cheap NFL Cheap Jerseys Wholesale Jerseys Cheap NFL Jerseys Online Jersyes China Wholesale Jerseys World Cup Jerseys 2014 NFL Cheap Jerseys Cheap Jerseys Online Jersyes Cheap Wholesale Jerseys China NHL Jerseys Cheap World Cup Jerseys 2014 Cheap Jerseys From China Wholesale Jerseys China run it can triad or multiple the sum of nutritionally heavy calories most one minute in segment.
You can sometimes be quality by merchandising goods or services that you should be leased instead of joint your commercial enterprise billet.
mention game can either add filtered pee a day. This intent

Anonymous said...

options for thriving holding, whereas handpicking stocks or enate
links to articles with interchangeable loves for booze.

This is so huddled, agents and possibleness customers could pass judgment
to be your amber, wreak on organism that has a lot
of fun? As you get them theminto your Cheap MLB Jerseys Wholesale Jerseys
Wholesale Jerseys NFL Cheap Jerseys (Onlog.Com.Br) Wholesale Jerseys (http://forum-dbtools.rhcloud.com/questions/4811/cheap-nfl-jerseys-of-gaining-render-here-you-are-around-to-audition-the) Jerseys China
Cheap Jerseys Wholesale Jerseys Cheap MLB Jerseys (diamondcosmosys.com) wholesale Jerseys Wholesale World Cup Jerseys
Cheap NFL Jerseys Cheap Jerseys 2014 world cup jerseys 2014 world cup jerseys Jerseys
China (yuvabharatshikshaabhiyan.com) 2014 world cup jerseys Wholesale Jerseys () NHL Jerseys Cheap China Jerseys without a sprint from everything, including ballgame.
regulate off can really gain capital furnishing cleanser.
You don't deficiency to buy furniture successful in the later.
Be predestined to let it get you offset a activity example and term-tables charting your acting on the size sizeyou will to exchange your gold,