Archive for the ‘Unit Testing’ Category
Ruby Tuesday #15 : Ruby Round Up
There’s a few things I’ve noticed over the last week in the Rubyverse that I thought it was worth highligting. In no particular order, here we go.
In my Ruby doodlings, I’ve spent quite a bit of time with REXML. GIven that I’m focussed on learning Ruby rather than deploying code, there’s no issue. However, this post from RubyInside shows there are alternatives such as libxml-ruby.
Testing, both TDD and BDD style, are prominent in the Ruby community. This post examines the reasons to unit test, questions the emphasis placed on unit testing and highlights the importance of remembering why you test. Well worth reading and relevant regardless of your preferred language.
Finally, I came across a couple of resources that those who, like me, are learning Ruby may find valuable. The first is the news that there is a new chapter in the Book of Ruby. The second is a couple of posts about learning Ruby for C#ers. These posts also mention IronRuby, so well worth reading.
Ruby Tuesday #13 : Testing
Having completed a rudimentary Twitter client, I thought it was high time I figured out how unit testing works in Ruby. The good news is that there’s a framework (called Test::Unit) distributed with Ruby. To create a class of tests, simply create a new class and inherit from Test::Unit::TestCase. Any method in the class that begin with test will be executed as tests. Here’s a simple test for the translator in my Twitter client:
require 'test/unit'
require 'Twitter'
require 'rexml/document'
include REXML
class Test_Translator < Test::Unit::TestCase
def test_element_to_user
xml = '<user>
<id>99999999</id>
<name>Username</name>
<screen_name>Screen Name</screen_name>
<location>Location</location>
<description>Description</description>
<profile_image_url>image_url</profile_image_url>
<url>url</url>
<protected>false</protected>
<followers_count>404</followers_count>
</user>'
translator = Translator.new
element = get_user_element(xml)
user = translator.element_to_user(element)
assert_equal('99999999', user.id)
assert_equal('Username', user.name)
assert_equal('Screen Name', user.screen_name)
assert_equal('Location', user.location)
assert_equal('Description', user.description)
assert_equal('image_url', user.image_url)
assert_equal('url', user.url)
assert_equal('false', user.protected)
assert_equal('404', user.followers_count)
end
private
def get_user_element(xml)
xml_document = Document.new(xml)
xml_document.root
end
end
Run that test and it tells me there are 8 assertions in 1 test. And there’s an error. The error is a NoMethodError – tells me that there is no method followers_count on the User instance. And looking at the class it’s clear why:
class User attr_reader :id, :name, :screen_name, :location, :description, :image_url, :url, :protected def initialize(id, name, screen_name, location, description, image_url, url, protected, followers_count) @id = id @name = name @screen_name = screen_name @location = location @description = description @image_url = image_url @url = url @protected = protected @followers_count = followers_count end end
There’s an instance variable that gets set when an instance is created, but there’s no property defined. All that’s needed is to update the first line of the class like so:
attr_reader :id, :name, :screen_name, :location, :description, :image_url, :url, :protected, :followers_count
And the test passes. Lots more to learn in Test::Unit, but that feels like a good start.
When I started this series of Ruby Tuesdays, Matt recommended ZenTest, so I thought I’d look at that, too. You can install it as a gem. Once it’s installed, I called it with the following command:
zentest Twitter.rb >Test_Twitter.rb
That creates a file called Test_Twitter.rb that contains tests for the methods in Twitter.rb. Here’s a brief excerpt:
class TestClient < Test::Unit::TestCase def test_download_friends_timeline raise NotImplementedError, 'Need to write test_download_friends_timeline' end
As you can see, for each method an error is raised to prompt you to write the test code. There’s some other goodness in there like the autotest daemon that automatically runs your tests as you make changes. And if you like to have your tests running automatically and you use Growl – this might be for you.
Test Driven Debate
I ran into this post the other day. You’ve got to agree that’s an eye catching title for a blog post. Reading a title like that you might expect an anti unit testing rant, whereas it actually makes the case for a pragmatic approach at the heart of which is testing. And that’s important – the focus should be on delivering working software, not on making sure you’re doing a before b.I see a lot of value in unit testing and high unit test coverage – I think it encourages better design along with more confidence in the code base (although I’d like to see more capability a la Eiffel built into languages, which would allows us to junk a bunch of the grunt tests and would make the intention of the code clearer.) I’m less sure about TDD. There’s no doubt it has its passionate followers, but the focus on the how may be at the expense of the why, and that’s a niggling doubt.
Testing changes
As you start to adopt unit testing, you’ll notice that you start to design your code differently. Being able to test your code thoroughly becomes the imperative. And, maybe counter-intuitively, that imperative promotes good design. Some of that good design may not be what you thought was good design before. This post, by Tim Ottinger, is an excellent description of the changes you’ll observe.
Executable XML
Earlier I posted about Synapse. My biggest concern about Synapse is that there is a lot of XML to write. To be fair, this is hardly a unique characteristic of Synapse. nspectre is primarily configured in XML – although you can write your own implementation of IConfigurationReader if you prefer something else. With the advent of .NET 3.0 XAML is a development that can’t be ignored.
So, all this XML is a great thing, right? Er, well, no. I don’t think so. In a lot of cases, we are using XML as the basis of a DSL – which means that the XML is code. And that means it gets executed. And here’s the issue – how do you test all that code? The unit testing support that is standard fare in most modern languages is absent in this space. We know what happens when you don’t unit test. And this gets even worse when the XML is generated by a tool, because in addition to the unit testing issue, it can become difficult to diff – which makes bug detection even harder. (I should just add a quick note about nspectre here. Since it generates code at runtime for use by .NET code, it is relatively straightforward to unit test the logic expressed in the configuration – which means you can write unit tests that express the intent and if you change the config in a way that breaks the tests then you know you have a problem.)
I also get the feeling that sometimes XML is being used to add a dynamic capability to static languages. If that is the case, then shouldn’t we be using languages that have the features we want?
I like flexibility as much as the next person, but I’m concerned about anything that is a block to change. And executable XML often falls into this category. Proceed with caution.
You don’t want to do it like that
James Carr has compiled a useful list of TDD Anti Patterns. Worth reading. And if you want to read more about unit testing, may I recommend Michael Feather’s book, Working Effectively with Legacy Code? This is a great book that describes refactoring techniques you can apply to apply unit testing to existing, untested code – the sort of code that often feels untestable. After reading this book I felt that in addition to being able to tackle legacy code, I had a much more instinctive feel for how to design code to be testable. James’ post helps to keep the tests you’re writing clean.
Ghosts in the machine
On Roy Osherove’s blog, I found this post about NSpec. Obviously, the name piqued my interest. It (NSpec – and also Specter, which is mentioned in the comments) seems to be addressing a different part of the development cycle compared to nspectre. Whereas nspectre is a run-time framework for business logic (or behaviour) in the form of specifications, NSpec focuses on test time. Specifications written in nspectre are easy to test – but maybe a compelling extension to nspectre would be auto-generation of tests of the specifications or something along those lines.
Limits to Independence
The solution I described yesterday has a drawback – it increases the scope of the created class. This may be acceptable – it may even be desirable – in some instances, but there are situations where you have to think of something else. In the case of nspectre, a reference ValidatorFactory needs to held for the lifetime of an application because it compiles the specifications once when they are first required. So, if we were to hold references at class level in ValidatorFactory, they would never be eligible for garbage collection.
Let's say we have a class called, uninspiringly, Foo that creates an instance of class called – you guessed correctly – Bar. One solution is to create a protected method in Foo that returns a new instance of Bar. In our unit tests we subclass Foo and override the newly created protected method. Then we can either extract the interface from Bar and make Bar implement this interface (IBar) and mock Bar in the unit tests. It would also be possible to return a fake Bar – maybe by subclassing Bar.
More Independence
Having put in a few logging messages into nspectre to prove the principle, it's time to test the logging messages. Given that the design relies on an ILogger interface, this is fairly easy with mocks. What I noticed fairly quickly was a class that created another class – which meant that testing logging would mean testing it in more than the target class.
The solution is to extract an interface from the class being created by the target class and pass it to the target class in the constructor. Now I can also mock the class that was being created. In this instance, the code that results is more elegant. All tests have been updated and they all pass. Once more the tests have proven their worth – this kind of refactoring would have been scary without them.