You’ve probably heard of the idea of a “code smell” – a hint that something in the code is not quite right and ought to be changed.
Just as there are code smells, there are “test smells”. The book xUnit Test Patterns describes a number of them.
One of the smells described in the book is Obscure Test. An Obscure Test is a test that has a lot of noise in it, noise that’s making it hard to discern what the test is actually doing.
Here’s an example of an Obscure Test I wrote myself:
context 'the element does not exist' do
before do
contents = %(
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>
<item></item>
</channel>
</rss>
)
xml_doc = Nokogiri::XML(contents)
episode_element = xml_doc.xpath('//item').first
@rss_feed_episode = RSSFeedEpisode.new(episode_element)
end
it 'returns an empty string' do
expect(@rss_feed_episode.content('title')).to eq('')
end
end
There’s a lot of noise in the contents
variable (e.g. <?xml version="1.0" encoding="UTF-8"?>
). All that stuff is irrelevant to what the test is actually supposed to be testing. All this test should really care about is that we have an empty set of tags.
Here’s a refactored version of the same test:
context 'the element does not exist' do
let(:rss_feed_episode) do
RSSFeedEpisodeTestFactory.create("<item></item>")
end
it 'returns an empty string' do
expect(rss_feed_episode.content('title')).to eq('')
end
end
Hopefully this is much more clear. The gory details of how to bring an RSS feed episode into existence are abstracted away into RSSFeedEpisodeTestFactory, a new class I created. Here’s what that class looks like:
class RSSFeedEpisodeTestFactory
def self.create(inner_contents)
@inner_contents = inner_contents
rss_feed_episode
end
def self.rss_feed_episode
RSSFeedEpisode.new(xml_doc.xpath('//item').first)
end
def self.xml_doc
Nokogiri::XML(contents)
end
def self.contents
%(
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
<channel>#{@inner_contents}</channel>
</rss>
)
end
end
Now I can use this factory class wherever I like. It not only helps me keep my tests more understandable but also helps cut down on duplication.
In the video below you can watch me refactor the “obscure” version into the more readable version as part of one of my free live Rails testing workshops.