How the "Why Not" of unit testing fails


I've discussed why I consider unit testing to be a mandatory component of development efforts. In my travels I've encountered a number of arguments against unit testing. I've considered these arguments and they're all fatally flawed. Each argument is at its core prompted by one or more of three underlying problems:

  • Ignorance
  • Short Sightedness
  • Laziness

Ignorance in itself is not a crime. However a significant and essential component of being a professional software developer is identifying those areas in which you need to increase your knowledge or skills. My sympathy for ignorance therefore only runs to those who are willing to learn and improve.

Short sightedness is an unfortunately common human problem. Again it is my opinion that being a professional software developer involves considering more than just short term factors. Constant short term thinking leads to accumulation of enormous technical debt and greatly increases the cost of development over the project lifetime.

Laziness has been suggested as a virtue in software development. This virtuous laziness involves doing some work now to automate work to be done later, resulting in overall less work. This is not the kind of laziness to which I am referring. I'm referring to the traditional procrastination based laziness that ultimately results in more work overall. This is fine if it's related to doing your housework and no one but you is affected. It's highly unprofessional when you're doing it for a client and thus your laziness results in increased costs for them over the lifetime of the project.

The simplest argument I encounter is from people who claim that they don't unit test because they don't know how. This is a direct argument of ignorance. The attempt to make this acceptable implies that it's acceptable to be lazy in not learning how to write unit tests. I reject this assertion. Code that is not unit tested will on average be more costly to maintain, have more defects and will have inferior design. Allowing this because you choose not to take the effort to learn a technique fundamental to your profession is unacceptable.

A related argument is that it is somehow "too hard" to do unit testing. It must be acknowledged that there is a learning curve to doing unit testing effectively. I make no claim that I have mastered unit testing to the point where I have nothing left to learn. That some effort is required is undeniable. But I will not accept that it is "too hard". Anyone who is capable of writing software to the standard required to do it for a living is capable of learning how to write unit tests. If you honestly find it too hard to write unit tests then you are in the wrong field. Otherwise your protestations about unit testing are laziness.

It's very common to see arguments made that unit tests should not be written in cases of particular time pressure. This comes from the (incorrect) view that unit testing is a luxury that is to be dropped in order to focus on meeting short term goals. This argument is a form of short sightedness. Over anything but the smallest timeframes unit testing saves effort. Certainly unit testing is additional code to be written but this additional code provides a reduction in development effort that greatly exceeds the effort required to write it. This comes from such factors as reduced defects, but also from less obvious sources such as improved design resulting in reduced duplication and from increased maintainability making it easier to add or modify functionality.

It must also be considered that adding unit tests after the fact takes significantly more effort than writing them in conjunction with the code. This is compounded by tests that tend to be of lower quality, validating the current behaviour instead of specifying the desired behaviour, and the loss of design benefits gained from developing code to be testable.

Another popular argument from ignorance is that an individual can get more done when not writing unit tests. This claim of increased productivity fails for a few reasons. Firstly it fails to account for the risks of introducing defects into existing code. Adding 3 new features at the cost of breaking 4 others is hardly a net gain. Without automated testing you are relying solely on manual testing to discover the issues before you release, and human tests are valuable but fallible things.

In a team environment unit testing is a way of ensuring that the assumptions each team member is working with as to the operation of the code are captured in a fashion that provides significant confidence that violations will be detected. For instance if my code depends on a particular behaviour of a dependency and that behaviour is changed by another developer it is significantly easier to detect and correct this situation if there is a unit test that checks for the behaviour and will fail when it is modified.

Having argued that unit testing is essential for professional development and that arguments to not unit test are flawed, I shall now list cases where unit testing is not applicable. What is important to note here is that I'm not suggesting that these scenarios imply that unit testing should not be performed for the majority a system. Rather there is a valid argument that for specific scenarios within larger systems the cost of unit testing can outweigh the benefits. This is legitimate but does not imply that the majority of the system should be untested.

  • UI User interfaces are notoriously difficult to test. Larger organisations may use automated tools to validate the interfaces but at the smaller end the cost is generally far too high. Proper use of patterns such as MVC should ensure that the untested component of the code is extremely minimal and limited to presentation elements only. Separation of concerns would then ensure that the business logic and other system elements are properly tested.
  • Integration Points Some integration points simply don't lend themselves to proper unit testing. This is often because the API you are dealing with does not provide the ability to mock or stub it. As an example I once wrote an application that dealt with receiving requests via WCF then making raw TCP/IP calls to a legacy system. I was able to unit test every element of the system except for the code that dealt directly with the TCP/IP connections. This was due to a limitation in the .NET networking classes that prevented me from dealing with anything but concrete instances. Note that this point is different from integration testing, which deals with testing that system components interact correctly and as such is distinct from unit testing. In this point I am discussing unit testing of the code for integrating with other components.

This discussion applies to the particular class of systems that I work with, primarily line of business systems. It may not apply to the systems you work with. Some environments will not support unit testing and arguing that it is ignorant, short sighted or lazy to not unit test in such an environment is not applicable. This is unfortunately all too common with some customisable business applications which provide extensibility mechanisms that are so tightly coupled to the application that unit testing cannot be performed. I would argue that this would count as a reason to chose alternate applications, but this is a separate discussion.

In summary:

  • Arguments to not unit test systems as a whole are generally ignorant, short sighted or lazy
  • Professionalism in software development demands that developers learn to unit test and apply it to their work
  • Some specific tightly defined areas of a system may not be unit testable in a cost effective manner but the system should be constructed so as to minimise these elements

author: Colin David Scott | posted @ Sunday, August 31, 2008 4:27 AM | Feedback (0)

The "Why" of unit testing


It seems to be a popular misconception that the motivation for writing unit tests is the short term reduction of bugs in new code. This limited view makes it easy for developers to justify to themselves laziness in not writing properly tested code. What it ignores are the primary benefits and justifications for unit testing, what I call the "Why" of unit testing.

In my experience there are two primary and significant benefits to writing comprehensive unit tests: Design and Maintainability. These attributes are closely related in that good design supports maintainability and maintainability allows the design to be kept effective over time.

The Test Driven Development (TDD) practice of leading development with unit testing is fundamentally a design process. Writing unit tests closely in conjunction with the code under development provides immediate feedback which goes into improving the design of the code. Developers are working with their code in an immediate fashion that other styles of development do not provide. Issues with the code from the perspective of its clients are detected more quickly and therefore can be resolved faster and with significantly less effort (and hence cost)

Code developed using TDD will also tend to be better factored and easier to work with. The need to be able to effectively test the code drives important design principals such as separation of concerns that support the ability of code elements to be tested in isolation. Testability here provides incentives to producing good design, a rare win-win in a world of software engineering tradeoffs.

Maintainability, or the ability to support change in the codebase, is the key determinant of the cost of a software system over its lifetime. Systems that are highly maintainable can have defects resolved more quickly and cheaply and may be more effectively extended to include new functionality or adapt to new conditions. A comprehensive unit test suite is a critical element of ensuring the maintainability of a system.

Every change to a system is a risk. Even resolving an obvious defect may actually break other code that relies upon the defective behaviour. Adding new functionality will almost always require alteration or extension of at least some existing code, introducing the risk of defects being introduced into existing code that may not seem at all related to the new features.

Unit testing provides the first line of defence against the introduction of defects into existing code. It is therefore a risk mitigation strategy to make it safer to work with an existing codebase. A developer working on a codebase with solid unit tests can have significantly higher confidence that their changes are benign than if no tests are available (the problem of overconfident developers who underestimate their potential to do damage is left as an exercise for the reader).

Introducing new features often requires altering the design of the application to support the new functionality. This involves refactoring the codebase to meet the new design. The presence of solid unit tests are a necessity to be able to make the design changes with confidence (see Refactoring: Improving the Design of Existing Code by Martin Fowler).

Good design derived from TDD also supports maintainability through reduced coupling and improved cohesion. This isolates the codebase as a whole from the effects of modifications in specific areas. Additionally good design will tend to produce less code overall. Good design will remove duplication which is a leading cause of maintenance costs and introduced defects. A good design will also fit the problem domain more closely resulting in more natural and hence more succinct expression of the solution.

In a future post I shall examine some of the arguments I've heard against writing unit tests and discuss why they are fundamentally flawed.

author: Colin David Scott | posted @ Friday, August 22, 2008 2:03 AM | Feedback (0)

Well that was pointless


I just tried to upgrade to the latest Subtext version but ran into issues. As this version apparently has problems with medium trust environments anyway I'm not going to keep pushing. Apologies to all none of you who would have noticed the downtime.

I am however strongly resisting the urge to write my own blog engine (again) as an excuse to learn ASP.NET MVC. And by strongly resisting I mean I've downloaded the ASP.NET MVC Preview 4 release.

author: Colin David Scott | posted @ Wednesday, August 20, 2008 9:40 PM | Feedback (0)

Further issues with .NET 3.5 SP1


For most people the issues with .NET 3.5 SP1 are likely manageable. You can install it and work around the problems. Or you can hold off until they fix the issues (and they really had better fix the issues).

And then there's the guy in this thread who must install .NET 3.5 SP1 to use SQL Server 2008, but can't install it because it breaks what he's doing with WPF (running it in a service). I'm really really glad I'm not him. Moreover I'm disappointed and annoyed that Microsoft hasn't paid any attention to people using their stuff in unusual ways. In an industry with such a rich history of taking things and using them in places their original creators never imagined; for a vendor the size of Microsoft to never consider people may not use their tools exactly as they expect is fairly damning. Improvement required.

author: Colin David Scott | posted @ Thursday, August 14, 2008 7:26 PM | Feedback (0)

.NET 3.5 SP1 doesn't seem ready for release


.NET 3.5 SP1 has only just been released any I've already seen three distinct reports that it's introduced bugs that break applications. See here and here for examples, and at least one vendor who's update list I'm on has sent a warning email to notify people .NET 3.5 SP1 breaks their product.

Fortunately I've only installed it on my home system which I don't use for anything but personal development. Given the nature of .NET 3.5 (such as the shared runtime with .NET 2.0/3.0) this has the potential to break applications running on earlier .NET versions. Solid testing before rolling it out looks essential.

author: Colin David Scott | posted @ Thursday, August 14, 2008 10:29 AM | Feedback (0)

Archery? Over Top Gear? Really? Bad SBS


Apparently this evening SBS will be broadcasting Olympic archery in the slot that should be occupied by Top Gear. This is wrong on so many levels. Not least of which is use of medieval weapons being considered an Olympic sport.

If you really want to get me to watch, combine medieval weapons and cars. Bring back jousting. With cars. Until then can we please stop pretending that any of the Olympic events are interesting to any but the terminally dull? Hopefully one day as a species we'll get over the idiotic nationalism that drives this overly expensive stupidity. I'm not holding my breath.

author: Colin David Scott | posted @ Monday, August 11, 2008 2:14 PM | Feedback (0)

Nobody expects the SQL Inquisition!


Our chief weapon is T-SQL...and an almost fanatical devotion to Microsoft!

Farewell Bwian, may you eventually become the messiah.

author: Colin David Scott | posted @ Monday, August 11, 2008 9:50 AM | Feedback (0)

Running things from within Visual Studio


It's often useful to be able to run files in a solution from within Visual Studio. I use this to do things such as run batch files to build or deploy the solution. I can't claim to have invented this method which I found many years ago on a website I no longer recall, but I thought I'd pass it on due to its utility when working with build scripts.

To set it up, add an external tool (Tools -> External Tools). Set it up as shown below.

image

You'll now have an entry on the Tools menu called Run Script that will run whatever you have selected in the Solution Explorer. To be extra fancy you can add it to a toolbar or assign a keyboard shortcut.

To add it to a toolbar right click on the toolbar area and select the Customise option. The dialogue shown below appears. Find the External Commands in the Tools list as shown. The command name you assign is not shown so you'll have to count which number it is. In my case it's my only entry which makes the counting relatively simple. Now just drag the relevant External Command onto the toolbar you wish it to appear in. When you close the dialogue the entry will switch to the name you've selected.

image

To assign keyboard shortcut use the options dialogue (Tools -> Options). I describe this in more detail here.

image

author: Colin David Scott | posted @ Thursday, August 07, 2008 11:56 AM | Feedback (0)

Building Using MSBuild: Part 1


Many .NET projects are built entirely within Visual Studio. This is generally the easiest method when developing. However there are a number of cases where this overhead isn't desirable. These include obvious cases like build servers where Visual Studio may not even be available. It also includes less obvious cases such as having multiple solutions to be built. Opening a solution, especially a large one, in Visual Studio is a significant overhead you generally don't want to incur just to built the latest version of a supporting project. This is where build systems come into play.

There are a number of build tool options for .NET. NAnt is one of the original tools for the platform, inspired by the Ant from the Java world. This eventually prompted Microsoft to develop their own build tool: MSBuild. As of Visual Studio 2005 the project file format is MSBuild, although solution files annoyingly continue to be a different format which MSBuild can process but which does not support the same kind of extensibility. NAnt tends to be more feature rich but the standard case MSBuild setup is easier (it's already done for you by Visual Studio) and will likely be an easier sell in many environments.

If you simply wish to build a single standalone project then having MSBuild build the standard solution file is probably good enough for you. However in more complex cases you probably want a separate build file to give you more control. For instance one project with which I am familiar has numerous solutions for the different services that make up the system. These services have message projects that are referenced by each of the other solutions. The initial solution to this problem is to check in a binary version of each message assembly into the dependency directory associated with every solution that uses those messages. This is annoying and error prone to manage.

What we want is a system whereby we can be more sophisticated with the build. It'd be nice if we could build the messages from each solution, copy them to a common location from which they can be referenced, then build everything else. It'd also be nice to be able to run the unit tests for each solution.

Over the next few posts I'm going to demonstrate a set of MSBuild scripts that can do this, leveraging as much of what Visual Studio generates for me as is reasonable. The goal of these build files is ultimately to allow a single action to build an entire series of interrelated solutions.

As a side note: Don't have circular dependencies between solutions unless you really need them. Don't ever attempt circular dependencies between projects (within or between solutions). Not for any reason.

We start with the solution structure. You have a couple of options:

  • Organise the projects into directories based on their build groups. More on this below,
  • Don't structure the projects around the build. You may do this because you have an existing solution that you don't want to mess with, or because you'd prefer the file structure to be mapped to the another scheme, or at least not mapped to build requirements. This requires a little more configuration but is a workable option.
  • Have partial structure and use exclusions. For instance keep the main projects in the solution directory and have a Tests directory for test projects. This will likely work well right up until the moment you add a project than violates the assumptions of your exclusion. It's probably not worth it but if you have an existing structure may be your least effort option.

I'm going with a solution that is organised based on build groups. I generally identify three types:

  • The standard group is the main projects of the solution. If it doesn't belong to the other groups it goes here. Build as part of the standard build process.
  • The tests group contains projects specifically related to unit testing.
  • The common group contains projects that are dependencies of other solutions.

What we therefore need is a build process that builds all the common group projects and makes them available to all solutions. It then builds the standard group projects for each solution and optionally builds and runs the tests.

Because common group projects are built before everything else we need to put some restrictions on them:

  • Common group projects may not have dependencies on any non-common group projects (the reverse is not true).
  • Dependencies between common group projects are generally acceptable within a solution but should be minimised when between solutions.
  • Cyclical common group dependencies between solutions are forbidden.

Ideally we wish to avoid dependencies between solutions so that we do not have to be concerned with their build order. Where these dependencies are unavoidable cycles must be avoided. Cycles would require having multiple common groups in some or all solutions to allow building of the dependencies to occur in the necessary order. That way madness lies. One possible solution is to have a single designated framework solution that is always built first. This is likely significantly easier to manage and in many significant projects already exists in some form. Such a solution obviously cannot have any dependencies on other solutions in the project (it may share third party components as necessary of course).

My initial build script will target such a framework project. I want two options; build and clean. Build will build the entire framework, clean will remove all the non-source files. These options map to the Rebuild All and Clean All options provided by Visual Studio. I would also like the framework assemblies to be copied to a common directory to allow them to be referenced by other solutions. Initially I shall have this happen every time the solution is built, although later scripts will allow build without deploy. For this first pass I shall ignore unit tests.

My build script looks like:

   1:  <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
   2:      <PropertyGroup>
   3:          <DeployDirectory>$(MSBuildProjectDirectory)\..\bin</DeployDirectory>
   4:      </PropertyGroup>
   5:      <ItemGroup>
   6:          <ProjectFiles Include="Src\**\*.csproj"/>
   7:      </ItemGroup>
   8:      <Target Name="Clean">
   9:          <MSBuild Projects="@(ProjectFiles)"
  10:              Targets="Clean"/>
  11:      </Target>
  12:      <Target Name="Build">
  13:          <MSBuild Projects="@(ProjectFiles)"
  14:              Targets="Rebuild">
  15:              <Output TaskParameter="TargetOutputs" ItemName="BuildOutput"/>
  16:          </MSBuild>
  17:   
  18:          <Copy SourceFiles="@(BuildOutput)" DestinationFolder="$(DeployDirectory)" />
  19:      </Target>
  20:  </Project>
 

There are a number of elements to this file.

  • On line 3 we set a property called DeployDirectory to the location where we want the files deployed to.
  • On line 6 we find all the .csproj (C# project files) in the Src directory. This directory is the location for all the standard group projects (the name is not significant, just that it exists). These project files are gathered in an item group called ProjectFiles.
  • Starting line 8 we have a target that cleans the solution. It does this by running MSBuild on all the project files with a target of Clean.
  • Starting line 12 is the build target. This runs MSBuild on the projects with a target of Rebuild. We gather the TargetOutputs of each build into an item group called BuildOutput
  • On line 18 we take the items in BuildOutput and copy then to the deploy directory

Invoking this script from MSBuild requires a number of environment variables and command line parameters. To simplify this I've created a couple of batch files. The build script looks like:

call "c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86
%FrameworkDir%\%Framework35Version%\msbuild.exe /property:Configuration=Release Framework.build
 

The clean script looks like:

call "c:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86
%FrameworkDir%\%Framework35Version%\msbuild.exe /target:Clean /property:Configuration=Release Framework.build
 

The scripts in this case rely on the vscarsall.bat batch file installed with Visual Studio. They are also framework version specific (.NET 3.5 in this case). This is generally acceptable as the scripts will mostly be used by developers (build systems should invoke MSBuild directly) and solutions are almost always targeted to a specific .NET version. Feel free to be more sophisticated if you wish.

These scripts may be run without any parameters or interaction to build and clean the entire framework. As the build file does not contain any references to specific projects new projects in Src will be built automatically (provided they're C# projects, but the extension to other project types is trivial). As such the build scripts will not require alteration on most project additions. If there is not a file structure identifying build groups projects may need to be specified individually which requires developers to modify the build file whenever a new project is added or a project is removed. This is not a significant overhead but may be overlooked.

The result of all this is that we can build the framework and automatically copy its outputs into a common directory. Most of this Visual Studio does automatically, and the copy of outputs we could add as build events. This solution doesn't require adding a build event to every project, but this is not by itself very compelling. In future posts I will expand on the above to produce a set of build scripts that provide more of the capabilities described above, including building of multiple solutions and running of unit tests.

author: Colin David Scott | posted @ Tuesday, August 05, 2008 2:29 PM | Feedback (0)

How to determine that you may be excessively geeky #1


You get depressed that reality doesn't support rollback because you want a do over. What kind of unreliable system doesn't have transactional support?

author: Colin David Scott | posted @ Monday, July 28, 2008 6:03 PM | Feedback (0)