RetrofittingAutomatedTesting

October 17, 2003
From the XP list:
“How do you migrate a heavy project to XP?”

My answer:
It's usually impossible to stop down and retrofit tests for everything. So test what you're working on today. When you find a bug, fix it test-first. Eventually, you'll improve your test coverage.

What's hard about this is writing the fixtures (setup/teardown) required for testing. That could be a very eye opening experience into how much of a big ball of mud your app is. So, anticipate a frustrating amount of time to stop down and write fixtures, but even here, DTSTTCPW.

When you're adding or fixing code, consider starting a new class, even though it seems like you should add this tiny change to the existing structure -- break this new piece off so it's easy to test, and have the production code use this new class. Refactor as you go, in small steps, only what you need for today, because you simply can't afford big refactorings.

Once you get to your next major release, everything you touched in between releases should have good test coverage, and the rest you hope is still okay because it's been okay since you last released, right? (If it wasn't you would've bug fixed it and that would've gotten a test) That's a thin hope, of course, but it's more than what you'll get if you don't start testing.

GregVaughn wrote me asking for something along the lines of an example, and this is what I dolled out:
This wiki (clWiki) is my own, and I recently needed to inject a bit of functionality. The thing is fairly well tested, but not the most refactored thing in the world. Like any lousy short example that comes to mind, it needs a rather large setup.

My wiki supports [used to support] FullHierarchy and what I call global links. If I have a page called //TheArts/MusicBlog/ChristmasSample, without global links, if I'm in a different part of the hierarchy, I have to have an absolute link: //TheArts/MusicBlog/ChristmasSample. But with global links, I can just put in the page name: ChristmasSample, and the index will identify a match and under the covers link to the absolute page underneath.

What if I have the same page name in two different parts of the hierarchy? What if I have //ComputerTech/BurningCds and //MusicStuff/BurningCds and then I try to link to just BurningCds? In this case, I have the link go to the find results page, doing a title search on BurningCds, since the wiki shouldn't pick. Fine.

Then I started having a lot of pages like this:
//AllProjects/ThisProject
//AllProjects/ThisProject/TaskOne
//AllProjects/ThisProject/TaskTwo
//AllProjects/ThisProject/TaskThree
Then I'd be on a page like //ProgressLog/Oct13_Oct17 and link to ThisProject.

In this case there's more than one hit in the index -- every one of the four example pages gets returned, so it's a link back to the find results page. But this sucks, because, at least for myself, it's obvious I just wanted a direct link to //AllProjects/ThisProject, not to the search results including every child page.

So I wanted to add in this change. The existing code that processed the index hits to determine a direct link or a find results link wasn't too long:

hits = wikiIndex.search(pageName, titles_only)
case hits.length
when 0
result = pageName
when 1
result = "#{pageName}"
else
result = "#{pageName}"
end


but was in the middle of a longer method which itself was in a chain of calls.

I started adding some code here:

hits = wikiIndex.search(pageName, titles_only)
# new code to eliminate non-'exact' matches.
case hits.length
when 0
result = pageName
when 1
result = "#{pageName}"
else
result = "#{pageName}"
end

I put in my first shot of code -- and it didn't work at all. Not only did I still get find results links for multiple hits, I no longer got any link at all for pages that already only had one match. So immediately I knew I had two simultaneous cases working against each other, and testing this the old fashioned way was going to be a pain. I'd fix one case, break the other and go back and forth. I didn't really have an existing test for this stuff anyway ... so what to do? No existing fixture, no test, not well refactored.

I saw that I really only had two pieces of data I needed to work with -- the hits array and the pageName string. (pageName is the WikiWord I'm trying to link up).

So I wrote the following test:

class TestGlobalHitReducer < TestBase
def test_global_hit_reducer
# for global links, we want matches to be limited to exact matches.
# If //RootPage/SubPage has seven children:
#
# //RootPage/SubPage/ChildOne
# //RootPage/SubPage/ChildTwo
# ...
#
# and the content is SubPage, we don't want a global link to the
# find results of SubPage, but a direct link to //RootPage/SubPage.
# If there's also a //OtherPage/SubPage, then the global link in this
# case will still go to Find Results.

hits = ['//RootPage/SubPage', '//RootPage/SubPage/ChildOne']
reduced = GlobalHitReducer.reduce_to_exact_if_exists('SubPage', hits)
assert_equal(['//RootPage/SubPage'], reduced)

reduced = GlobalHitReducer.reduce_to_exact_if_exists('SubPa', hits)
assert_equal(hits, reduced)

hits = ['//RootPage/SubPage', '//OtherPage/SubPage']
reduced = GlobalHitReducer.reduce_to_exact_if_exists('SubPage', hits)
assert_equal(hits, reduced)
end
end

Then started writing a GlobalHitReducer class to fulfull my needs, and here ‘tis:

class GlobalHitReducer
def GlobalHitReducer.reduce_to_exact_if_exists(term, hits)
reduced = hits.dup
reduced.delete_if do |hit|
parts = hit.split('/')
exact = (parts[-1] =~ /^#{term}$/i)
!exact
end

if !reduced.empty?
reduced
else
hits
end
end
end


Feel free to proof this, this code ain't that old and is bound to have glitchiness happening...

An example of adding something new that got tested in and of itself. Add this into production:

hits = wikiIndex.search(pageName, titles_only)
hits = GlobalHitReducer.reduce_to_exact_if_exists(pageName, hits)

case hits.length
when 0
result = pageName
when 1
result = "#{pageName}"
else
result = "#{pageName}"
end



tags: ComputersAndTechnology AgileDevelopment
comments powered by Disqus