Archive for the ‘Unit Testing’ Category
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.
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.
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.
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.
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.
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.
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.
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.