Blog from January, 2010

NDepend v3 is now 100% integrated in Visual Studio.
A Set of Unit Testing Rules - Don't let slow tests bog you down

By Michael Feathers; link: http://www.artima.com/weblogs/viewpost.jsp?thread=126923

A test is not a unit test if:

  • It talks to the database
  • It communicates across the network
  • It touches the file system
  • It can't run at the same time as any of your other unit tests
  • You have to do special things to your environment (such as editing config files) to run it.

Tests that do these things aren't bad. Often they are worth writing, and they can be written in a unit test harness. However, it is important to be able to separate them from true unit tests so that we can keep a set of tests that we can run fast whenever we make our changes.



The Tortoise and the Hare - doing right instead of doing fast

by Robert C. Martin

Link: http://www.artima.com/weblogs/viewpost.jsp?thread=51769

In software the race goes to those who go well, not those who go fast.

Have you ever been to a tractor pull? Imagine a huge arena filled with mud and churned up soil. Huge tractors tie themselves up to devices of torture and try to pull them accross the arena. The devices get harder to pull the farther they go. They are inclined planes with wheels on the rear and a wide shoe at the front that sits squarely on the ground. There is a huge weight at the rear that is attached to a mechnism that drags the weight up the inclined plane and over the shoe as the wheels turn. This steadily increases the weight over the shoe until the friction overcomes the ability of the tractor.

Writing software is like a tractor pull. You start out fast without a lot of friction. Productivity is high, and you get a lot done. But the more you write the harder it gets to write more. The weight is being dragged up over the shoe. The more you write the more the mess builds. Productivity slows. Overtime increases. Teams grow larger. More and more code is piled up over the shoe, and the development team grinds to a halt unable to pull the huge mass of code any farther through the mud.

Our industry is addicted to speed; and it's killing us. As we leap out of the starting gate on a new project, if we focus on speed over quality, we sew the cause of our eventual defeat. As an industry, as a profession, we need to seriously re-think the way we do software. In software, speed kills.

The way to go fast, is to go well. Going well means taking time to keep the code clean, to write tests, to refactor, to take care of the code. Going well means taking a deliberate and caring attitude for the code, and the system. Going well means working as a team, communicating frequently, reviewing and inspecting code and designs. Going well means never checking in code that fails unit tests or acceptance tests. Going well means integrating frequently, at least once per day. Going well means staying rested.

There is always time to write tests. There is always time to refactor and keep the code clean. There is always time to review and inspect and pair. There is always time to integrate frequently. Indeed, there is no time not to do these things, because these are the things that keep us going fast. These are the things that keep the weight back over the wheels rather than over the shoe. These are the things that keep our productivity high, and our code flexible and malleable.

Over the years I've published a lot about design principles, design patterns, and development practices that can help teams keep their code clean and flexible. However, these principles, patterns, and practices are only of use to the folks who take the time to use them. Interestingly enough, these are the same folks who will get done faster, and better, than those who don't take the time. Those who don't take the time just keep dragging the weight up over the shoe.

The next time you say to yourself: "I don't have time to do it right." – stop! Rethink the issue. You don't have time not to do it right!

Where did we get the idea that our companies wanted us to write crap? Aren't they paying a lot of money for this software? Aren't they going to depend upon this software for profits? Isn't this software worth a lot? If so, then why would our company want to pay for crap that slows us down? Wouldn't they rather have good clean code that keeps us running fast?

A few years ago I hired two apprentices. I gave them a project to do. They came back a day later and said they were done and asked if I'd like to see it. I knew this project should take more than a day, so I started asking them about how they had built the project. I asked them whether they had written tests, or whether they had separated concerns. I showed them alternate designs and different coding practices that were better than the ones they had used. They were incredulous. "We feel bad because you are paying us a lot and we want to get this done fast." they said. I explained to them that the software they were writing was intrinsic to my business, and that it was more important to me that it be done well, than done fast. I explained that if they didn't do it well, then they'd be maintaining a mess for years, and that maintenance would take much more time and energy than they'd saved by doing it fast. I told them to go back and do it well. And they did.

We often blame managers for schedule pressure. We often complain that our companies set unreasonable deadlines and have unrealistic expectations. This might be true, but it's only half the problem. The other half, the most important half, is within us. We consider our worth as programmers to be more associated with speed than with quality. And that's a tragedy; because it leads us to create messes. It leads us down the slower path. By rushing we ruin our chance to truly go fast.

I was with a team the other day, and the manager of the team said to them: "I want this code to be something you'd be willing to hang on your grandmother's refrigerator. I want it to be high quality. Remember, two years from now all people will see is the mess, they won't see how fast you got it done. Nobody remembers the speed, they only remember the mess. So do it right". The programmers looked at him skeptically. Their boss just told them to go well, instead of going fast, and they didn't believe him. What does it take?

Take my advice: go well, not fast. Care about your code. Take the time to do things right. In software, slow and steady wins the race; and speed kills.

Cyclomatic complexity

I ran a simple CQL query in NDepend on the DeltShell codebase showing the top 25 libraries with a cyclomatic complexity higher then 100. Here are the results:
 

Memoization

Here is a nice example of a C# memoization function to speed up calculations.

public static class Memoization
{
    public static Func<T, TResult> Memoize<T, TResult>(this Func<T, TResult> function)
    {
        var cache = new Dictionary<T, TResult>();
        var nullCache = default(TResult);
        var isNullCacheSet = false;
        return parameter =>
        {
            TResult value;

            if (parameter == null && isNullCacheSet)
            {
                return nullCache;
            }

            if (parameter == null)
            {
                nullCache = function(parameter);
                isNullCacheSet = true;
                return nullCache;
            }

            if (cache.TryGetValue(parameter, out value))
            {
                return value;
            }

            value = function(parameter);
            cache.Add(parameter, value);
            return value;
        };
    }
}

Unit test

[TestFixture]
public class MemoizationTest
{
    [Test]
    public void MemoizeFunctionWithOneArgument()
    {
        Func<double, double> sinFunc = System.Math.Sin;
        var sin = sinFunc.Memoize();

        // Build sin(x) lookup table
        for (int i = 0; i < 100; i++)
        {
            sin(i);
        }

        Assert.AreEqual(-0.99975517335861985, sin(55), 0.001);
    }
}
Where's my cross-thread exception!?

In an attempt to create a 'System.InvalidOperationException: Cross-thread operation'
I came accros the strange behaviour that i would get an exception only in debug mode.

My test is like this:

  1. Create a view for some data with data binding to a model
  2. Create a separate thread (2) changing the model
  3. Databinding bubbles from the second thread to the UI control and the control throws an exception because it is being changed by a thread that did not create
    it.

Now this only was the case in Debug-mode. In release mode everything seemed fine. Seems there is a switch controlling this behaviour

Control.CheckForIllegalCrossThreadCalls that defaults to Debugger.IsAttached. For
a running application this exception is not thrown which might result in unexpected behaviour. By setting

Control.CheckForIllegalCrossThreadCalls = true 

the exception does occur.

See also: http://weblogs.asp.net/justin_rogers/archive/2004/10/08/240077.aspx

The Big Ball of Mud

While much attention has been focused on high-level software architectural patterns, what is, in effect, the de-facto standard software architecture is seldom discussed. This paper examines this most frequently deployed of software architectures: the BIG BALL OF MUD. A BIG BALL OF MUD is a casually, even haphazardly, structured system. Its organization, if one can call it that, is dictated more by expediency than design. Yet, its enduring popularity cannot merely be indicative of a general disregard for architecture.

These patterns explore the forces that encourage the emergence of a BIG BALL OF MUD, and the undeniable effectiveness of this approach to software architecture. What are the people who build them doing right? If more high-minded architectural approaches are to compete, we must understand what the forces that lead to a BIG BALL OF MUD are, and examine alternative ways to resolve them.

A number of additional patterns emerge out of the BIG BALL OF MUD. We discuss them in turn. Two principal questions underlie these patterns: Why are so many existing systems architecturally undistinguished, and what can we do to improve them?

PCRaster documentation

Here the PCRaster documentation can be found.

This doesn't include the error codes (sad)

DeltaShell Setup

There is a solution for the generation of the setup files for DeltaShell
delft-tools\src\DeltaShellSetupProjects.sln
The setup is build using WIX
see:
http://wix.sourceforge.net/
http://wix.codeplex.com/releases/
download and install the toolset which will also register a new project type to Visual Studio 2008

log4net configuration

Configures logging within test classes as shown below, in case of log4net reads log4net section from app.config file.
Configure: Initializes the log4net system with a default configuration.
ResetConfiguration: Resets all values contained in this repository instance to their defaults.

[TestFixtureSetUp]
public void SetUpFixture()
{
     LogHelper.ConfigureLogging();
}

[TestFixtureTearDown]
public void TearDownFixture()
{
     LogHelper.ResetLogging();
}
Copy Paste in the project treeview.

Copy/Paste is tightly related to Clone. This is how it can be done :

ProjectService.CopyTo(IFolderItem sourceItem ,IFolderItem targetContainer)
{
   if (!targetContainer can accept sourceItem)
      return;//should never get here we try to paste a folder in a model or something
   
   //clone the sourceItem
   var target = sourceItem.DeepClone();
   var fileItems = target.GetAllItemsRecursive<IFileBased>();
   foreach (IFileBased fileBasedItem in fileItems)
   {
      //create a copy of the file if it is projectdata to prevent unwanted relations
      if (item is projectdata/internal)
      {
          string newPath = GetSomeNewGuidName();
          //this will copy internal and reconnect the file to the new source
          fileBasedItem.RelocateTo(newPath);
      }
   }
}

A few things to note:
  • If a a function is Cloned() it should only cal SetValues/GetValues if the source store is memory
  • A clone of a filebased functionstore (netCdf/GDal) is a new functionstore of the same type pointing at the same file
  • CopyTo should be called when paste is done
The hard part/possible issues:
  • Clone of function needs knowlegde about IFilebase stores (do we want to move this knowledge to IFunctionStore.ShouldSetValueIfCloned?)
  • Relocate should work in all possible states of the store.
  • If we do this we NetworkCoverage (Sobek) the reference to the branches will be shallow (no deepcopy here), this problem will be fixed later on
Export summary.

Export (for a grid) can be done as follows:

GdalFileExporter.ExportTo(string path,IFunction source)
{
   //get store for given path. The store should be ready to get values from the target
   IFunctionStore targetStore = GetStoreForPath(path);
   //clone to should take care of set values in the file.
   source.CloneTo(targetStore);

}

In IFunction we should add a CloneTo method that creates a clone using the given store. The Clone() method uses the memory store. The CloneTo method is the hard part since there we should time the SetValues call after the store is set.

About use of external libraries in Delta Shell, legal issues

The Rules of Delta Shell related to the use of external code in the library form (*.dll) or in a source code form (*.cs ...):

  • DON'T include any pice of source code which is not properly licensed into the Delta Shell codebase.
  • Double check if code that is added to the repository is licensed properly, if you're not sure about the license - check with me and I will figure it out. Even when you see that library is free - it does not mean that we can use it, not all open-source licenses are compatible with each other!
  • Isolate use of ANY commercial component in DelftTools.Controls.Swf and use them in the application ONLY via interfaces. In this case we get both functionality and possibility to isolate the whole codebase from use of closed-source components.

For now we own the following components:

  • DotNetBar
  • TeeChart
  • XtraSuite
  • PCRaster - only in HABITAT
  • USP.Express.Pro - only in HABITAT

All of them must be isolated in DelftTools.Controls.Swf and should remain there. DotNetBar is also used in a top-level gui (MainWindow). The rest of the sources should not reference any other commercial components. It is not yet achieved, some projects still use commercial components outside of the DelftTools.Controls.Swf.

The reason I'm writing this is because I've found a code which does not belong to us in our sources: MACTrackBar.cs, ColorHelper.cs, DrawMACStyleHelper.cs, MACTrackBarDesigner.cs. Luckily it was not used in any critical parts yet. I'm removing it from our repository because I don't think we satisfy the following condition:

THE SOURCE CODE CONTAINED HEREIN AND IN RELATED FILES IS PROVIDED THE REGISTERED DEVELOPER FOR THE PURPOSES OF EDUCATION AND TROUBLESHOOTING. UNDER NO CIRCUMSTANCES MAY ANY PORTION OF THE SOURCE CODE BE DISTRIBUTED, DISCLOSED OR OTHERWISE MADE AVAILABLE TO ANY THIRD PARTY WITHOUT THE EXPRESS WRITTEN CONSENT OF ECONTECH JSC.

Did anyone got permission from ECONTECH JSC. to use it? Don't think so.

#region Copyright (c) 2002-2006 EConTech JSC., All Rights Reserved
/* ---------------------------------------------------------------------*
*                           EConTech JSC.,                              *
*              Copyright (c) 2002-2006 All Rights reserved              *
*                                                                       *
*                                                                       *
* This file and its contents are protected by Vietnam and               *
* International copyright laws.  Unauthorized reproduction and/or       *
* distribution of all or any portion of the code contained herein       *
* is strictly prohibited and will result in severe civil and criminal   *
* penalties.  Any violations of this copyright will be prosecuted       *
* to the fullest extent possible under law.                             *
*                                                                       *
* THE SOURCE CODE CONTAINED HEREIN AND IN RELATED FILES IS PROVIDED     *
* TO THE REGISTERED DEVELOPER FOR THE PURPOSES OF EDUCATION AND         *
* TROUBLESHOOTING. UNDER NO CIRCUMSTANCES MAY ANY PORTION OF THE SOURCE *
* CODE BE DISTRIBUTED, DISCLOSED OR OTHERWISE MADE AVAILABLE TO ANY     *
* THIRD PARTY WITHOUT THE EXPRESS WRITTEN CONSENT OF ECONTECH JSC.,     *
*                                                                       *
* UNDER NO CIRCUMSTANCES MAY THE SOURCE CODE BE USED IN WHOLE OR IN     *
* PART, AS THE BASIS FOR CREATING A PRODUCT THAT PROVIDES THE SAME, OR  *
* SUBSTANTIALLY THE SAME, FUNCTIONALITY AS ANY ECONTECH JSC. PRODUCT.   *
*                                                                       *
* THE REGISTERED DEVELOPER ACKNOWLEDGES THAT THIS SOURCE CODE           *
* CONTAINS VALUABLE AND PROPRIETARY TRADE SECRETS OF ECONTECH JSC.,     *
* THE REGISTERED DEVELOPER AGREES TO EXPEND EVERY EFFORT TO             *
* INSURE ITS CONFIDENTIALITY.                                           *
*                                                                       *
* THE END USER LICENSE AGREEMENT (EULA) ACCOMPANYING THE PRODUCT        *
* PERMITS THE REGISTERED DEVELOPER TO REDISTRIBUTE THE PRODUCT IN       *
* EXECUTABLE FORM ONLY IN SUPPORT OF APPLICATIONS WRITTEN USING         *
* THE PRODUCT.  IT DOES NOT PROVIDE ANY RIGHTS REGARDING THE            *
* SOURCE CODE CONTAINED HEREIN.                                         *
*                                                                       *
* THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE.              *
* --------------------------------------------------------------------- *
*/
#endregion Copyright (c) 2002-2006 EConTech JSC., All Rights Reserved
Test projects on TeamCity

I noticed that they were changed to 20 days on TeamCity - can't see the history anymore!! Don't change clean-up policies for those configurations. They don't take relatively little space on HDD. The only project configuration which need to be cleaned up are those containing large artifacts, but even in those projects general history / statistics should remain.

Testing Windows.Forms views

A bit ugly but we can write integration tests involving Windows.Forms controls (smile)

        [Test]
        [NUnit.Framework.Category("Windows.Forms")]
        public void CreateAndShowTransectDataView()
        {
            var app = mocks.Stub<IApplication>();
            var gui = mocks.Stub<IGui>();
            var project = new Project();
            app.Project = project;
            gui.Application = app;

            var provider = new DurosPlusModelViewProvider {Gui = gui};

            var transectData = DurosPlusModelMockHelper.CreateSampleTransectData();

            project.RootFolder.Add(transectData); // <-- project must contain TransectData in order to create DurosPlus model

            var view = (Control)provider.CreateView(typeof (TransectDataView), transectData);

            // show a message box when item is added to the project
            project.RootFolder.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs e)
            {
                log.DebugFormat("New item has been added: {0}", e.Item);

                // assert
                e.Item.Should("check if newly added item is Duros+ model").Be.OfType<DurosPlusModel>();
            };

            // select 2 checkboxes and click on a "Create Duros+ Models" button.
            var groupBoxSelection = (GroupBox)view.Controls["groupBoxSelection"];
            var checkedListBoxTransect = (CheckedListBox)groupBoxSelection.Controls["checkedListBoxTransect"];
            var checkedListBoxYear = (CheckedListBox)groupBoxSelection.Controls["checkedListBoxYear"];
            var buttonCreateDurosPlusModels = (Button)view.Controls["buttonCreateDurosPlusModels"];

            // do something after form is displayed
            Action<Form> formShownAction = delegate 
            {
                checkedListBoxTransect.SetItemChecked(0, true);
                checkedListBoxYear.SetItemChecked(0, true);
                buttonCreateDurosPlusModels.PerformClick();
            };

            WindowsFormsTestHelper.ShowModal(view, formShownAction);
        }

After test runs:

  • 2 checkboxes are checked
  • button is clicked
  • model is created and added to the project
  • assert checks if added item is of a proper type

Some tips on how to use it:

  • Make sure to name all controls on the form correctly
  • Use Document Outline in Visual Studio to check names of controls, make sure names are readable and intuitive!

Probably we should refactor WindowsFormsTestHelper a little to make it more usable. Add any ideas as comments to the current blog post.