Agile Nirvana

MountaintopAn interesting topic came up in a discussion today, a phrase I’d never heard before: “Agile Nirvana”. As explained, Agile Nirvana is where Agile is done ‘perfectly’ and following all of the rules by the book. Where there is no room for improvement anymore.

As much as we all want that, it’s something that will never happen. If you think you have achieved that, then you are probably further away from it than you know.

A core focus of agile is continuous improvement. You can’t be Agile if you aren’t improving, so the job is never done. I don’t believe I will ever be on a team where things are so good, improvement isn’t needed anymore.

In fact, some of the best teams I’ve ever been on are teams that have deficiencies, but made great strides to improve. A successful Agile team is one that is always improving.

I’d maybe propose that Agile Nirvana is when the team is able to quickly resolve issues effectively, not when there are no problems at all – that’s not a realistic goal, and unachievable in many ways. Improvement can always use improvement as well:

How quickly is your team recognizing there is a problem?

I’ve been surprised several times before the team was in agreement there was even something that needed addressing in the first place.

What is the root cause?

If you went to a doctor with an infection, which would you rather be treated for, a fever set on by the infection, or the infection itself? The same is true when improving your Agile team – Is the root of the problem known, or just the symptoms? Identifying the root of the issue is difficult, and addressing it is more difficult, but it goes much further than patching the symptoms.

Are only problems addressed?

Often there are teams that spend most of their effort purely addressing what they identify as problems. A change doesn’t always have to address a problem. A change could be made to a previous one to make it even better, improve tools available to the team, or experimentation and running with ideas. If the change is unsuccessful, roll it back.

So the next time you think your Agile process is perfect, ask yourself again: “What can we do to improve?”

Testing the Untestable

testingWhen writing unit tests or doing TDD, it’s common to find excuses to not test something. It’s not that it may to too hard to test, but it might rely on some outside factor, such as an Internet connection, an Active Directory environment, or relies on some hardware configuration, such as the number of processors.

The common solution to all of these problems is to fake it, using dependency injection and a combination of mocking and stubbing.

But we know all of that. Those are practices that have been preached for a while now. How do we test what we are actually stubbing out – the actual dependencies?

One place where I see this coming up frequently is when a product might call home to information about the environment to provide a better experience, as well as understanding environment configurations, such as needing to know if old platforms still need to be supported.

Here is a little bit of code that might do that:

 

   1:  using System;
   2:   
   3:  namespace TestingTheUntestable
   4:  {
   5:      public class OperatingSystemInfo
   6:      {
   7:          public bool Is64Bit { get; set; }
   8:          public bool IsServerCoreEdition { get; set; }
   9:          public bool IsClientVersion { get; set; }
  10:          public Version OperatingSystemVersion { get; set; }
  11:      }
  12:   
  13:      public interface IOperatingSystemInfoProvider
  14:      {
  15:          OperatingSystemInfo GetInformation();
  16:      }
  17:   
  18:      public class OperatingSystemInfoProvider
  19:          : IOperatingSystemInfoProvider
  20:      {
  21:          public OperatingSystemInfo GetInformation()
  22:          {
  23:              return new OperatingSystemInfo
  24:              {
  25:                  Is64Bit = Environment.Is64BitProcess,
  26:                  IsClientVersion = false,//Omitted
  27:                  IsServerCoreEdition = false, //Omitted
  28:                  OperatingSystemVersion = Environment.OSVersion.Version
  29:              };
  30:          }
  31:      }
  32:   
  33:      public class OperatingSystemInfoSender
  34:      {
  35:          private readonly IOperatingSystemInfoProvider
  36:              _operatingSystemInfoProvider;
  37:   
  38:          public OperatingSystemInfoSender
  39:              (IOperatingSystemInfoProvider operatingSystemInfoProvider)
  40:          {
  41:              _operatingSystemInfoProvider = operatingSystemInfoProvider;
  42:          }
  43:   
  44:          public OperatingSystemInfoSender()
  45:              : this(new OperatingSystemInfoProvider())
  46:          {
  47:          }
  48:   
  49:          public void SendDetails()
  50:          {
  51:              var info = _operatingSystemInfoProvider.GetInformation();
  52:              //Send
  53:          }
  54:      }
  55:  }

You can probably see where this is going: we can test the OperatingSystemInfoSender by providing a mock IOperatingSystemInfoProvider and returning a stub OperatingSystemInfo. This is common and simple pattern for dependency injection.

So what about OperatingSystemInfoProvider? It’s these units of code that often go untested, because who knows how it will act on another machine. Even worse, some people won’t test the Sender either because they don’t recognize that there is a dependency there that can be mocked out.

Let’s assume we have the dependency separated as above and we can test the info provider independently.

There are different philosophies to this problem, I’ll outline a few of them:

  • “Don’t test it. The platform is different between workstations, build servers, etc. It’ll be too complicated to maintain these tests.”
    This tends to be the more common of the situations. It’s unfortunate, and it doesn’t follow TDD principles.
  • “We want to test it, but we don’t know how, so we gave up.”
    or
    “We use to test it, but it was too brittle and failed too often.”
    This is also common, but it might be a knowledge limitation.
  • “Have a dedicated environment that this test passes on. For instance, the build server. Don’t worry if the test fails on dev. stations, just make sure the build server says it’s green.”
    This is a better solution, at least the functionality is tested, but it removes some comfort for developers. For XP and some Scrum shops, tests are the documentation, and having documentation that is wrong on a development station might be a bad thing.
  • “Test the bare minimum. Assert that there are no exceptions raised, and that some form of data is returned that looks correct by checking ranges or regular expressions.”
    I prefer this approach. It’s a comfy feeling seeing all tests green on a local development station, as long as the tests are meaningful.
  • “Use compiler directives to control assertions. Have a symbol defined for “BUILDENV”, and only do assertions if BUILDENV is defined. The build environment will compile it’s code using that symbol.”
    It’s still better than nothing, it depends on how much work and precision you are expecting from your tests. It this granularity is required, then there might not be a better alternative.”
  •  

    We know we want to test it, and we can start with the simplest approach: checking if there is no exception.

       1:  [TestFixture]
       2:  public class OperatingSystemInfoProviderTests
       3:  {
       4:      [Test]
       5:      public void ShouldNotThrowException()
       6:      {
       7:          var infoProvider = new OperatingSystemInfoProvider();
       8:          Assert.DoesNotThrow(() => infoProvider.GetInformation());
       9:      }
      10:  }

    If your testing framework doesn’t have an equivalent for DoesNotThrow, just calling it directly and an exception occurring will also fail the test. However, that lacks clarity of, “What are we really checking with this test?”

    This is certainly better than nothing, and a step in the right direction. I think we can do more though, such as the operating system version being greater than 0.0.0.0.

       1:  [Test]
       2:  public void ShouldReturnValidVersionNumber()
       3:  {
       4:      var infoProvider = new OperatingSystemInfoProvider();
       5:      var info = infoProvider.GetInformation();
       6:      Assert.IsNotNull(info);
       7:      Assert.Greater(info.OperatingSystemVersion,
       8:          new Version(0, 0, 0, 0));
       9:  }

    You see where this is going. Assertions and tests don’t have to be exact if they still provide meaning. I would be challenged to find code where no tests could be written at all. It just might require a slight change in the details of what a test is.

    Code Coverage Debt–How Much Are You In?

    dominosA colleague of mine and I were looking at the test coverage of an existing application. The end result was 65%, to which he replied, “That’s not too awful”. My first thought was, “It was probably lower, something close to 45%”.

    These type of metrics are used by a coverage tool. I’ve blogged dotCover from Jetbrains, one that I prefer because of the amazing ease of using it and integration into Resharper, despite a glaring bug, I still recommend it. These are just tools, and are limited to what I would call covered.

    A coverage tool is used to identify which portions of an application are tested, and those that aren’t. It’s an important tool for someone that takes Unit Testing and/or TDD seriously. If Test Driven Development is done properly, it’s easy to stay above 85% covered. Once you reach 90%, it becomes difficult sometimes without making some exemptions, such as generated code (Web Services, LINQ to SQL, EF, etc), and occasionally, things that are just flat out too hard to test sometimes, some people would put COM Interop, Platform Invoke, etc. in that category. Whether or not you agree is a decision left to you. If you start keeping track of these metrics early on, the more reliable the metric is. But how do you get unreliable results?

    Take an example of an old application that hasn’t had these metrics run on it before. Given the example of the Repository pattern, a call is made to the repository to delete an object. If the deletion is successful, write an audit record.

    Here is a simple pseudo code for it:

    var repository = Container.Resolve<IDbObjectRepository>();
    if (repository.Delete(dbObject))
    {
        Container.Resolve<IAuditService>().AuditDelete(dbObject);
    }

    And a test to go along with it:

    [Test]
    ShouldRemoveObjectFromRepository()
    {
        var dbObject = GetDbObjectByName();
        var repository = Container.Resolve();
        repository.Delete(dbObject);
        Assert.IsNull(GetDbObjectByName());
    }
    

    This isn’t how I’d do it either, but it’s a clear enough example. If we have a test around this code that ensures the object is deleted, then the audit code is run too. (Pretend there are transactions, etc. here) However, the test has no check in place to ensure the audit actually happened. The audit functionality is tested as a side effect. If the audit code were removed, would the test actually fail?

    I call this Code Coverage Debt. It’s a symptom of improper Test Driven Development. If the test was written first that was failing, it would has been impossible for the audit code to even get in there without a failing test, which would be meaningful coverage.

    Most applications have some form of Code Coverage Debt, even the most diligent TDD’ers incur 0.5 or 1 percent of debt. A typical application with good TDD principles that ‘slip’ every now and than may have up to 2%, and in places where TDD is a ‘sounds cool, but we don’t really do it’, can have 10%, 20%, or even more. At that point, all bets are off.

    I would encourage those that do run these metrics to ask yourself, “How meaningful are these results that I am getting?” You can do a little exploratory work as well; run coverage on a method that touches several different places, and see what the result is. Coverage tools like dotCover and NCover can tell you what it thought was tested, and see if you agree.

    Ultimately, the only way to solidly pay back this debt is by taking the time to refactor and review tests as you revisit areas of an application, and ensure TDD practices are followed.