A in-depth description of how we split test InvisibleHand, our price comparison extension.
Archive for the 'programming' Category
A/B testing browser extensions
Focused unit tests in ruby 1.9
I was having a problem running focused unit tests with TextMate 1.5.9, ruby 1.9.1 and rails 2.3.5. TextMate passes the params to the script like this:
ruby unit/booking_test.rb --name=test_cost_is_calculated_correctly_when_theres_a_surcharge
This doesn’t work (all tests are run instead) because the parameter is not passed correctly to the script. This works, however:
ruby unit/booking_test.rb --name test_cost_is_calculated_correctly_when_theres_a_surcharge
On ruby 1.8.7 both ways of passing the argument work just fine, while on ruby 1.9.1 only the second way works.
Since we want to pass two arguments to the script (‘––name’, ‘test_foobar’) instead of one (‘––name=test_foobar’), let’s patch TextMate. Open
/Applications/TextMate.app/Contents/SharedSupport/Bundles/Ruby.tmbundle/Support/RubyMate/runscript.rb
and replace
if name and !name.empty?
args << "--name #{name}"
elsif test_name and !test_name.empty?
args << "--name test_#{test_name.gsub(/\s+/,'_')}"
elsif spec and !spec.empty? and context and !context.empty?
args << %Q{--name "/test_spec \\{.*#{context}\\} \\d{3} \\[#{spec}\\]/"}
with
args << "--name"
if name and !name.empty?
args << name
elsif test_name and !test_name.empty?
args << "test_#{test_name.gsub(/\s+/,'_')}"
elsif spec and !spec.empty? and context and !context.empty?
args << %Q{"/test_spec \\{.*#{context}\\} \\d{3} \\[#{spec}\\]/"}
This will make the focused unit tests work.
InvisibleHand, a Firefox plugin that checks alternative prices as you are shopping online and alerts you if a bargain is found, consists of client part (the extension itself, in Javascript) and server part (the database of products, in Ruby). In order for the extension to always stay up-to-date it downloads pieces of JS code with price scraping logic from the server when it starts. I look after the JS code updating it whenever the layout of pages on retailers’ websites changes.
The challenge was to test this stuff automatically because the JS code makes part of a Ruby project and I want to ‘rake test’ it all. Getting a JS engine in my project would be an overkill, so I resorted to Selenium.
So, how to test a piece of Javascript code stored in your Ruby project?
First, download Selenium
Then, launch the server
java -jar selenium-server.jar
Then, install selenium client
sudo gem install selenium-client
In your unit test, launch Selenium:
attr_reader :browser
def setup
@browser = Selenium::Client::Driver.new "localhost", 4444, "*firefox", "http://www.google.com/", 10000
browser.start_new_browser_session
end
def teardown
browser.close_current_browser_session
end
The base address that you use to launch Selenium (google in this example) doesn't matter as long as your code isn't doing cross-domain requests.
Then, in your test:
def test_js
str = "Ah, Satan sees Natasha".to_json
js = %Q[
function reverseString(str) {
var rstr = '';
for (var i=str.length-1; i>=0; i--)
rstr += str.charAt(i);
return rstr;
}
reverseString(#{str});
]
result = browser.js_eval(js)
assert_equal 'ahsataN sees nataS ,hA', result
end
A few comments:
- The conversion to json is not necessary in this example but if you are sending an array of data or strings with characters which should be escaped, use json.
- js_eval() function returns the result of the last line in the script.
- If you need to return anything more complex than a string or a number, consider converting your data to json before returning and then parsing it in ruby (that's what I'm doing in my tests but I omitted it here for clarity)
The problem with this code is that is damn slow because a new instance of Firefox is started for this test and it's not fast. However, it's better than no tests at all and if you don't need to start a new instance of firefox for every test, it's actually tolerable.
Loaded suite /Users/evgeny/invisiblehand/test/selenium/js_test
Started
.
Finished in 7.403807 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
Good luck!
Sometimes in TextMate I get exceptions like this when trying to run unit tests:
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/pathname.rb:420:in `lstat': No such file or directory - /Users/evgeny/Projects/project/test/unit/test (Errno::ENOENT) from
<... stack trace skipped ...>
The exception above is caused by the call to realpath() in path_to_url_chunk() in Bundles/Ruby.tmbundle/Support/RubyMate/run_script.rb.
def path_to_url_chunk(path)
unless path == "untitled"
file = Pathname.new(path).realpath.to_s
"url=file://#{e_url(path)}&"
else
''
end
end
There are two problems here. First, the file variable is not used, so the hyperlink to the failed method in textmate output would be broken as a result. Second, realpath() raises an exception because for some reason (I didn’t dig deeper) the current directory is ‘/path/to/test/unit’ and path is ‘test/unit/my_test.rb’, so realpath() can’t find the test.
The modified version of the function works better:
def path_to_url_chunk(path)
unless path == "untitled"
Dir.chdir "../.."
file = Pathname.new(path).realpath.to_s
"url=file://#{e_url(file)}&"
else
''
end
end
It’s not a proper solution because the either the file path or the working directory should be corrected before this function is called. If you know a better solution to this problem, please leave a comment.