- Good old 'if ()' checking with throwing exceptions
- Assertions, from the System.Diagnostics.Debug library
- Code contracts, form the System.Diagnostics.Contracts library, shipped with .NET framework 4
I've been playing around with it and I'm trying to make up my mind if I prefer any of the System.Diagnostics ways over the 'good-old' IF and exceptions.
Everyone uses division in order to demonstrate illegal argument handling, since division by 0 cannot be done, so why should I do it any differently? Let's look at a method:
public static int Divide(int numerator, int denominator) { return numerator/denominator; }
Now, what we want to do is make sure that the denominator is larger than 0. Using the three mentioned methods, it would look something like this:
// CASE 1: Throws exception in case pre-condition ( denominator > 0 ) is not met public static int DivideWithExceptionThrowing(int numerator, int denominator) { if (denominator == 0) { throw new DivideByZeroException("Denominator should be larger than zero"); } return numerator / denominator; } // CASE 2: Fails assertion if pre-condition is not met public static int DivideWithAssert(int numerator, int denominator) { Assert.IsTrue(denominator > 0, "Denominator should be larger than zero"); return numerator / denominator; } // CASE 3: Fails to meet contract requirements in case pre-condition is not met public static int DivideWithContract(int numerator, int denominator) { Contract.RequiresIn case the last way of checking the pre-condition is new to you, check out this excellent guide. Although the namespaces are included in .NET framework 4.5, you do need to download the Visual Studio add-on for code contracts ( even if you are using VS2012 ) from here. After that you have to open solution properties and go to the code-contracts tab in order to set things up.(denominator > 0, "Denominator should not be larger than zero"); return numerator / denominator; }
Also - I needed to set CONTRACTS_FULL compilation symbol in order for my ReSharper to stop fading out my Contract.Requires line, saying that it will be skipped ( as described here ).
Note that I can set the exception that needs to be thrown in case the pre-condition is not met, using Contract.Requires<ExceptionType>() syntax.
So now, how should we unit-test our three methods? I've set up three tests that supply a zero as denominator. Therefore - I'm checking for the exceptions using the ExpectedException attribute for all of these cases:
[TestMethod] [ExpectedException(typeof(DivideByZeroException))] public void Divide_DividesByZero_ThrowsException() { const int numerator = 1; const int denominator = 0; var actual = MathematicalFunctions.DivideWithExceptionThrowing(numerator, denominator); } [TestMethod] [ExpectedException(typeof (AssertFailedException))] public void Divide_DividesByZero_FailsAssertion() { const int numerator = 1; const int denominator = 0; var actual = MathematicalFunctions.DivideWithAssert(numerator, denominator); } [TestMethod] [ExpectedException(typeof(DivideByZeroException))] public void Divide_DividesByZero_FailsToMeetContractRequirement() { const int numerator = 1; const int denominator = 0; var actual = MathematicalFunctions.DivideWithContract(numerator, denominator); }
Note here that if you have set up static checking ( see the code contracts tab in project properties ) - you will see that the contract checking will point out that things are going to cause problems. Be sure to do this on the calling assembly - that is the test project in my case:
Now - it's happy testing from this point:
Bottom line...
Obviously the contract checking is more advanced and the other two:- The most important benefit is of course compile time feedback on the written code. The static contract checking. You can detect an error even before running a test or the application.
- It's less verbose than the IF-Exception way, because it takes up a third of the number of lines.
- It provides more control over the exception that will be thrown than Assertions. As far as I know, either an AssertionFailedException is thrown or not.
I cannot find any downsides at this point. Feel free to leave any feedback about aspects I might have overlooked.
No comments:
Post a Comment