Not sure where to start? Start simple with TDD.

I’m new to Ruby and to Test Driven Development (TDD). Good thing the latter is helping me learn the former and write better code in the process.

I recently went through the Roman Numeral Kata shown by Jim Weirich here to get more acquainted with TDD. This kata showed me that through TDD I could write pretty, complex (hey, for me this is complex) code without much difficulty.

Let me start with the Roman Numeral Kata challenge: Turn modern numbers into the correct roman numeral. For example, 1 should equal I, 2 = II, 10 = X, 38 = XXXVIII, 63 = LIII, and so on.

As a beginner software engineer, it’s overwhelming at first to try and think of a complete solution. How will I handle 4’s? How will I be able to write clean code without 50 different if statements? Where should I start?

Luckily, TDD makes the answer to the most important question “Where should I start?” so simple. Start at the very beginning.

Start by setting up roman_numeral_converter.rb and roman_numeral_converter_spec.rb in your text editor like so:

roman_numeral_converter_spec.rb

require_relative 'roman_numeral_converter'
require 'rspec/given'

RSpec::Given.use_natural_assertions

describe RomanNumeralConverter do
end

roman_numeral_converter.rb

#It's blank!

Then run rspec. You’ll receive the fun error ‘uninitialized constant RomanNumeralConverter’ so go ahead and set that up.

roman_numeral_converter.rb

class RomanNumeralConverter 
end

Next, add your first test test and try to think of the simplest solution you can to make the test pass:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
end

roman_numeral_converter.rb

class RomanNumeralConverter
	def convert(n)
		"I"
	end
end

See… starting with “I” is something I can do. That was easy. Now write a second test that is just a little more complex:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
end

roman_numeral_converter.rb

class RomanNumeralConverter
	def convert(n)
		"I" * n
	end
end

Now, we’re on a roll! That small line of code will succesfully get us from 1 to 3. Since 4 is a little bit complicated, let’s move on to 5:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(5).should == "V"}
end

roman_numeral_converter.rb

class RomanNumeralConverter
	def convert(n)		
		result = ""
		if n == 5 
			result = "V"
		else
			result = "I" * n
		end
	result
	end
end

That looks great, but it won’t really help us get to 6, 7, or 8 so let’s add a little more code:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(5).should == "V"}
	Then {converter.convert(6).should == "VI"}
end

roman_numeral_converter.rb

class RomanNumeralConverter
	def convert(n)		
		result = ""
		if n >= 5 
			result << "V"
			n -= 5
		end
		result << "I" * n
	result
	end
end

Finally we're starting to get somewhere. It's becoming clear that you have to append the the lower roman numerals to the end of the result. Now let's tackle 10.

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(5).should == "V"}
	Then {converter.convert(6).should == "VI"}
	Then {converter.convert(10).should == "X"}
end

roman_numeral_converter.rb

def convert(n)		
	result = ""
	if n >= 10
		result << "X"
		n -= 10
	end
	if n >= 5 
		result << "V"
		n -= 5
	end
	result << "I" * n
result
end

Okay we are starting to see a pattern by looking at 5 and 10, but I'm not sure we're quite there. If we try to convert 20 we should get "XX", but right now we would get "XVIIIII" so our test would fail. We want to keep adding X's as long as n is greater than 10, right? So this is what that looks like:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(5).should == "V"}
	Then {converter.convert(6).should == "VI"}
	Then {converter.convert(10).should == "X"}
	Then {converter.convert(20).should == "XX"}
end

roman_numeral_converter.rb

def convert(n)		
	result = ""
	While n >= 10
		result << "X"
		n -= 10
	end
	if n >= 5 
		result << "V"
		n -= 5
	end
	result << "I" * n
result
end

Woo-hoo! All our tests pass. Now, let's tackle 4 and 9, or "IV" and "IX" by copying our patten for 5.

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(4).should == "IV"}
	Then {converter.convert(5).should == "V"}
	Then {converter.convert(6).should == "VI"}
	Then {converter.convert(9).should == "IX"}
	Then {converter.convert(10).should == "X"}
	Then {converter.convert(20).should == "XX"}
end

roman_numeral_converter.rb

def convert(n)		
	result = ""
	While n >= 10
		result << "X"
		n -= 10
	end
	if n >= 9 
		result << "IX"
		n -= 9
	end
	if n >= 5 
		result << "V"
		n -= 5
	end
	if n >= 4
		result << "IV"
		n -= 4
	end
	result << "I" * n
result
end

We now have a working roman numeral converter, but that code is not very pretty. Let's look for patterns and simplify (i.e., refactor). All of our if statements can actually be while statements, similar to the chunk of code that converts 10 to X. If we look even closer it appears that the final piece of code that converts 1 to I can also be turned into a while statement. Then we can just use variables and pass them through. See the final outcome here:

roman_numeral_converter_spec.rb

describe RomanNumeralConverter do 
	Given (:converter) {RomanNumeralConverter.new}
	Then {converter.convert(1).should == "I"}
	Then {converter.convert(2).should == "II"}
	Then {converter.convert(4).should == "IV"}
	Then {converter.convert(5).should == "V"}
	Then {converter.convert(6).should == "VI"}
	Then {converter.convert(9).should == "IX"}
	Then {converter.convert(10).should == "X"}
	Then {converter.convert(20).should == "XX"}
end

roman_numeral_converter.rb

def convert_to_roman_numeral(n)
	result = ""
	numbers = [ [10,"X"], [9,"IX"], [5,"V"], [4,"IV"], [1,"I"] ]
	numbers.each do |num, letter|
		while n >= num
			result << letter
			n -= num
		end
	end
	result
end

Yeah, that just happened. As you can see, the while statement is the same as the one we were using before. Now we are just passing sets of modern numbers and roman numerals through it. We can add more sets in our numbers array to convert higher numbers such as [50, "L"] and [40, "XL"].

I really had no idea how this converter would turn out when I first started. Luckily, I didn't have to spend too much time thinking about it. All I had to do was start testing with the simplest test I could think of and then go from there. And before I knew it, I ended up with a well-written Roman Numeral Converter.

Thanks TDD!

Leave a Reply

Your email address will not be published. Required fields are marked *