Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing and Coding Design

In a lot of TDD tutorials I see code like this:

public class MyClass
{
    public void DoSomething(string Data)
    {
      if (String.IsNullOrWhiteSpace(Data))
        throw new NullReferenceException();
    }
}


[Test]
public DoSomething_NullParameter_ThrowsException()
{
   var logic = MyClass();

   Assert.Throws<NullReferenceException>(() => logic.DoSomething(null));
}

This all makes sense but at some point you are going to get to the class that actually uses MyClass and you want to test that the exception is handled:

public class EntryPointClass
{
  public void DoIt(string Data)
  {
     var logicClass = new MyClass();

     try
     {
        logicClass.DoSomething(Data);
     }
     catch(NullReferenceException ex)
     {

     }
  }
}

[Test]
public DoIt_NullParameter_IsHandled()
{
  var logic = new EntryPointClass()

  try
  {
    logic.DoIt(null);
  }
  catch
  {
    Assert.Fail();
  }
}

So why not put the try/catch in MyClass in the first place rather than throwing the exception and have the test that test's for null in the MyClass unit test class and not in the EntryPointClass unit test class?

like image 597
Steve Gates Avatar asked Apr 02 '12 11:04

Steve Gates


2 Answers

Typically, your exception handling will look like this:

public class EntryPointClass
{
  Logger _logfile;
  // ...
  public void DoIt(string Data)
  {
     var logicClass = new MyClass();

     try
     {
        logicClass.DoSomething(Data);
     }
     catch(NullReferenceException ex)
     {
         _logfile.WriteLine("null reference exception occured in method 'DoIt'");
     }
  }
}

(The Logger is just an example for a resource you need for proper exception handling not available at the place where MyClass lives. You could also add the display of a message box here or something like that.)

At the level of MyClass, you typically don't have the appropriate tools available for proper exception handling (and you don't want to add them there to keep the class decoupled from, for example, a specific logging mechanism).

Note that this design decision is independent from doing TDD. If you want your class MyClass actually catching the exceptions by itself, you have to write your tests differently, that is right. But that is only a good idea if catching the exceptions does not block your automatic tests. Try, for example, to write a good unit test for MyClass when MyClass shows a hardcoded warning dialog by itself.

Of course, the example above shows that unit testing EntryPointClass may get harder in reality when you first need something like a logger to get things working. In general, you can attack this problem by providing the logger at constrcution time utilizing an ILogger interface which allows the logger to be replaced by mock. Or in this simple case, it may be enough not to initialize the logger for your unit tests at all and code it like this:

 public class EntryPointClass 
 {
      // ....
      catch(NullReferenceException ex)
      {
          if(_logfile!=null)
              _logfile.WriteLine("null reference exception occured in method 'DoIt'");
      }    
      // ....
 }
like image 96
Doc Brown Avatar answered Oct 03 '22 06:10

Doc Brown


Why not put the try/catch in MyClass in the first place...

Exceptions are for decoupling the error detection and the error handling. If the error cannot be handled (in all cases) locally in MyClass, then You should throw an exception.

This allows a local error detection (MyClass) and an error handling by whoever catches the exception.


The test in EntryPointClass tests, if the method DoIt is null-safe. It has little to do with its use of MyClass.

In other words: The test in EntryPointClass does not and (for code stability and encapsulation issues) should not rely on the fact that MyClass is used.

like image 27
Black Avatar answered Oct 03 '22 08:10

Black