Planning the Spontaneous

It's more than just a blueprint.

Because testing_isnt_all_bad

Posted by Robert Chow on 26/03/2010

I took a course module last year that didn’t end so well. My results were fine, but the teaching was plain appalling.  One of our assignments wanted us to try testing, yet the lesson hadn’t been covered at the time – it was covered after the assignment deadline. The reason behind this was so we had to do the research ourselves instead of being taught it textbook style.  I guess that way does work better – most of the stuff I’m doing now is all research I’ve done myself.  Still, it doesn’t excuse them for the time they handed an assignment out 2 days before the deadline, despite it being a 2 week workload.

My point is, testing is important.  As mundane as it is, it’s necessary.  We all try to avoid it.  We all seem to think we’re already doing it.  But that’s a lie. Unless we are actually devoting time and effort into it, we’re not actually doing it.  And fortunately for myself, I found this the easy way.

First glance tells me it’s fine

I have 3 methods, each an extension method for an integer.  These are:

bool IsPowerOf2();

int NextPowerOf2Up();

int NextPowerOf2Down();

I think from the names they’re pretty much self-explanatory.

When writing these methods, they worked the way they should do.

2.IsPowerOf2(); // returns true

7.IsPowerOf2(); // returns false

5.NextPowerOf2Up(); // returns 8

21.NextPowerOf2Down(); // returns 16

Or at least that’s the way I thought.

I’ve learnt over the few years of my experience that if you’re not thinking testing, then you’re not thinking outside the box.  All you see are the cases that work, and that should work.  Fine.  But then what if someone else thinks outside of the box?

(-1).NextPowerOf2Down();

Logically, no power of 2 should ever exist less than zero, and no integer power of 2 should exist less than 1.  Lucky for me, I already thought of this case, so I had it covered by throwing an exception.  And that’s basically it.

Second glance tells me different

Around Christmas time, I remember blogging that I wanted to look into using a tool called Machine.Specifiations, more commonly known as M.Spec.  M.Spec provides a handy library designed for writing and implementing tests in a fluent manner.  My supervisor asked me to write some of my own tests to cover the extension methods I’ve written, and that was when I started to think testing.

The idea behind testing is to try to cover every single case, without actually testing every single case.  Taking NextPowerOf2Up(), I can identify the different cases; these are values less than 1, and values equal to or greater than 1.  This is because the pivotal point in this method is 1 itself – it’s is the smallest possible power of 2.  We expect that any case less than 1 will result in 1, and any case that is equal to or greater than 1 will run as expected.  I can also split the latter into two more sections – those that are already a power of 2, and those that are not.  This is to ensure that the result does end in the next power of 2 and not the current one.

Sounds fairly simple.  But I did find one case that actually forced me to change the method code. This case covers all numbers equal to and greater than the highest possible power of 2 supported by an integer value.   Trying to ask for a power of two greater than that supported by int should throw an exception.

It was because of this case that I really saw the benefit of testing.  I knew it was important, but just not entirely how.  Sure I knew it was beneficial to see if your method works as expected.  But that’s pretty much really.  No one expects to use the corner cases, but it still needs to be covered.  And it’s using testing that’s helped me to do that.

M.Spec

Anyway, here’s the M.Spec code that I used to write my tests.  Like I said, it’s fairly fluent – M.Spec provides types and extension methods to aid this.

Note: SpecWithResult<> and FailingSpec are very simple containers designed by my supervisor.  SpecWithResult<> holds a result, and FailingSpec holds an exception.

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_0 : SpecWithResult<int>
{
Because of = () => result = 0.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_1 : SpecWithResult<int>
{
Because of = () => result = 1.NextPowerOf2Up();
It should_equal_2 = () => result.ShouldEqual(2);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_2 : SpecWithResult<int>
{
Because of = () => result = 2.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_3 : SpecWithResult<int>
{
Because of = () => result = 3.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_4 : SpecWithResult<int>
{
Because of = () => result = 4.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_5 : SpecWithResult<int>
{
Because of = () => result = 5.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_1 : SpecWithResult<int>
{
Because of = () => result = (-1).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_127 : SpecWithResult<int>
{
Because of = () => result = 127.NextPowerOf2Up();
It should_equal_128 = () => result.ShouldEqual(128);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_128 : SpecWithResult<int>
{
Because of = () => result = 128.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_129 : SpecWithResult<int>
{
Because of = () => result = 129.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_128 : SpecWithResult<int>
{
Because of = () => result = (-128).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_min_value : SpecWithResult<int>
{
Because of = () => result = int.MinValue.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_max_value : FailingSpec
{
Because of = () => exception = Catch.Exception(() => int.MaxValue.NextPowerOf2Up());
It should_throw_an_exception = () => exception.ShouldBeOfType<ArgumentException>();
It should_throw_an_exception_with_an_inner_exception = () => exception.InnerException.ShouldBeOfType<OverflowException>();
}

Here are the results after running the library with M.Spec

Int32Extensions .NextPowerOf2Up, when given 0
» should equal 1

Int32Extensions .NextPowerOf2Up, when given 1
» should equal 2

Int32Extensions .NextPowerOf2Up, when given 2
» should equal 4

Int32Extensions .NextPowerOf2Up, when given 3
» should equal 4

Int32Extensions .NextPowerOf2Up, when given 4
» should equal 8

Int32Extensions .NextPowerOf2Up, when given 5
» should equal 8

Int32Extensions .NextPowerOf2Up, when given negative 1
» should equal 1

Int32Extensions .NextPowerOf2Up, when given 127
» should equal 128

Int32Extensions .NextPowerOf2Up, when given 128
» should equal 256

Int32Extensions .NextPowerOf2Up, when given 129
» should equal 256

Int32Extensions .NextPowerOf2Up, when given negative 128
» should equal 1

Int32Extensions .NextPowerOf2Up, when given min value
» should equal 1

Int32Extensions .NextPowerOf2Up, when given max value
» should throw an exception
» should throw an exception with an inner exception

Contexts: 13, Specifications: 14

Oh, and for the record, we managed a petition against that particular course and got over 75% backing from the students who took it, including some who took it in previous years too.

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_0 : SpecWithResult<int>
{
Because of = () => result = 0.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_1 : SpecWithResult<int>
{
Because of = () => result = 1.NextPowerOf2Up();
It should_equal_2 = () => result.ShouldEqual(2);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_2 : SpecWithResult<int>
{
Because of = () => result = 2.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_3 : SpecWithResult<int>
{
Because of = () => result = 3.NextPowerOf2Up();
It should_equal_4 = () => result.ShouldEqual(4);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_4 : SpecWithResult<int>
{
Because of = () => result = 4.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_5 : SpecWithResult<int>
{
Because of = () => result = 5.NextPowerOf2Up();
It should_equal_8 = () => result.ShouldEqual(8);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_1 : SpecWithResult<int>
{
Because of = () => result = (-1).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_127 : SpecWithResult<int>
{
Because of = () => result = 127.NextPowerOf2Up();
It should_equal_128 = () => result.ShouldEqual(128);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_128 : SpecWithResult<int>
{
Because of = () => result = 128.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_129 : SpecWithResult<int>
{
Because of = () => result = 129.NextPowerOf2Up();
It should_equal_256 = () => result.ShouldEqual(256);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_negative_128 : SpecWithResult<int>
{
Because of = () => result = (-128).NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_min_value : SpecWithResult<int>
{
Because of = () => result = int.MinValue.NextPowerOf2Up();
It should_equal_1 = () => result.ShouldEqual(1);
}

[Subject(typeof(Magpie.Core.Extensions.Int32Extensions), “.NextPowerOf2Up”)]
public class when_given_max_value : FailingSpec
{
Because of = () => exception = Catch.Exception(() => int.MaxValue.NextPowerOf2Up());
It should_throw_an_exception = () => exception.ShouldBeOfType<ArgumentException>();
It should_throw_an_exception_with_an_inner_exception = () => exception.InnerException.ShouldBeOfType<OverflowException>();
}

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

«
 
%d bloggers like this: