0

Doing Roman Number converter kata using Bellwares SpecUnit

by klh 17. marts 2009 03:53

I was reading Scott Bellwares article on Behavior-Driven Development, and was inspired to try out his framework called specunit-net"

So I took a kata from tddproblems and tried it out, I took the Roman number conversion problem:

So first I implemented a simple spec base class:

[TestFixture]
    [Concern("base test")]
    public abstract class behaves_like_context_with_Converter : ContextSpecification
    {
        protected RomanNumberConverter _converter;

        protected override void Context()
        {
            _converter = new RomanNumberConverter();
        }

        protected void ShouldReturn(string romanNumber, int integerNumber)
        {
            int result;
            result = _converter.Convert(romanNumber);
            result.ShouldEqual(integerNumber);
        }
    }

 

Then I added specs for one char at a time, I started with I (one) and moved up, but reSharper re-structures this stuff:

[Concern("passed only one character")]
    public class when_passed_one_character : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_fifty()
        {
            ShouldReturn("L", 50);
        }

        [Observation]
        [Test]
        public void should_return_five()
        {
            ShouldReturn("V", 5);
        }

        [Observation]
        [Test]
        public void should_return_five_hundred()
        {
            ShouldReturn("D", 500);
        }

        [Observation]
        [Test]
        public void should_return_one()
        {
            ShouldReturn("I", 1);
        }

        [Observation]
        [Test]
        public void should_return_one_hundred()
        {
            ShouldReturn("C", 100);
        }

        [Observation]
        [Test]
        public void should_return_one_thousand()
        {
            ShouldReturn("M", 1000);
        }

        [Observation]
        [Test]
        public void should_return_ten()
        {
            ShouldReturn("X", 10);
        }
    }

So I started with the simplest implementation to make it work (unfortunately I didn’t save all the intermediate states so I can only show the end result), and followed that pragma all the way through, only implementing the simplest possible code to make it work.

Here is the end result, I cannot say that it is perfect, but i behaves like the spec says it should:

public class RomanNumberConverter
    {
        public int Convert(string s)
        {
            s = s.Trim();
            int result = 0;
            for (int position = 0; position < s.Length; position++)
            {
                if (position > 0 && !IsPreviousCharSmallerThanOrEqualToCurrent(s, position))
                {
                    result += ConvertOneCharacter(s[position]) - ConvertOneCharacter(s[position - 1]);
                    result -= ConvertOneCharacter(s[position - 1]);
                }
                else
                    result += ConvertOneCharacter(s[position]);
            }   
            return result;
        }

        private bool IsPreviousCharSmallerThanOrEqualToCurrent(string s, int position)
        {
            char previous = s[position - 1];
            char current = s[position];
            if (ConvertOneCharacter(previous) >= ConvertOneCharacter(current))
                return true;
            return false;
        }

        public int ConvertOneCharacter(char s)
        {
            Dictionary<char, int> romanValues = new Dictionary<char, int>
                                                    {
                                                        {'I', 1},
                                                        {'V', 5},
                                                        {'X', 10},
                                                        {'L', 50},
                                                        {'C', 100},
                                                        {'D', 500},
                                                        {'M', 1000}
                                                    };
            if (romanValues.ContainsKey(s))
            {
                return romanValues[s];
            }
            throw new ArgumentException(s + " is not a roman number");
        }
    }

At the bottom you find all the rest of the specs.

 

 

 [Concern("passed only two simple characters")]
    public class when_passed_two_simple_character : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_II()
        {
            ShouldReturn("II", 2);
        }

        [Observation]
        [Test]
        public void should_return_XV()
        {
            ShouldReturn("XV", 15);
        }
    }

    [Concern("passed only three simple characters")]
    public class when_passed_three_simple_character : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_XVI()
        {
            ShouldReturn("XVI", 16);
        }
    }

    [Concern("passed only values below ten")]
    public class when_passed_values_below_ten : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_IV()
        {
            ShouldReturn("IV", 4);
        }

        [Observation]
        [Test]
        public void should_return_IX()
        {
            ShouldReturn("IX", 9);
        }


        [Observation]
        [Test]
        public void should_return_VI()
        {
            ShouldReturn("VI", 6);
        }

        [Observation]
        [Test]
        public void should_return_VIII()
        {
            ShouldReturn("VIII", 8);
        }
    }

    [Concern("passed only one characterwhen passed spaces")]
    public class when_passed_spaces : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_trim()
        {
            ShouldReturn("II ", 2);
        }
    }

    [Concern("when passed invalid character")]
    public class when_passed_invalid_character : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        [ExpectedException(typeof (ArgumentException))]
        public void should_throw()
        {
            _converter.Convert("P");
        }
    }

    [Concern("when passed large numbers")]
    public class when_passed_large_numbers : behaves_like_context_with_Converter
    {
        [Observation]
        [Test]
        public void should_return_1900()
        {
            ShouldReturn("MCM ", 1900);
        }

        [Observation]
        [Test]
        public void should_return_1974()
        {
            ShouldReturn("MCMLXXIV", 1974);
        }

        [Observation]
        [Test]
        public void should_return_1998()
        {
            ShouldReturn("MCMXCVIII", 1998);
        }

        [Observation]
        [Test]
        public void should_return_2008()
        {
            ShouldReturn("MMVIII", 2008);
        }
    }

Tags:

Kommentarer

Tilføj kommentar


(Viser dit Gravatar icon)  

  Country flag

biuquote
  • Kommentar
  • Eksempel
Loading



Powered by BlogEngine.NET 1.4.5.0
Original Design by Laptop Geek, Adapted by onesoft