Testing Javascript from Ruby (part 2)

The previous part introduced a technique for testing client-side code from Ruby.

This part presents more tests and ends with some benchmarks.

Should the tests cover the internals?

The tests so far cover the sorting as a black box. They call the bubbleSort method with different test cases and check that the results equal the expected values.

The tests do not check if the sorting follows the bubble sort algorithm. Obviously, code using the bubbleSort method does not care about the internals as long as the produced array is sorted. However, as the example site shows how the sorting takes place, testing all the swaps is necessary. Another reason, even more important, is to check that the callback mechanism does work.

Test an empty array performs no swap

test_bubble_sort.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def test_an_empty_array_performs_no_swap
  
    actual = @context.exec <<-JS
    swaps = [];
    
    bubbleSort([], function(i,j) {
      swaps.push([i, j]);
    });
    
    return swaps;
    JS
  
    assert_equal [], actual
        
  end
$> $HOME/.rvm/wrappers/ruby-2.2.0@execjs/ruby  test_bubble_sort.rb
Loaded suite /tmp/release/test_javascript_from_ruby/test_bubble_sort
Started
......

Finished in 0.012312902 seconds.

6 tests, 6 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

487.29 tests/s, 487.29 assertions/s

The test executes Javascript code that passes an anonymous callback function (lines 6-8). The callback method stores in an array named swaps all the swaps. The strange part is the return statement on line 10 that enables to retrieve the result in the Ruby world.

Test a sorted array performs no swap

test_bubble_sort.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def test_a_sorted_array_performs_no_swap
  
    actual = @context.exec <<-JS
    swaps = [];
    
    bubbleSort([2,11,17,19], function(i,j) {
      swaps.push([i, j]);
    });
    
    return swaps;
    JS
  
    assert_equal [], actual
        
  end
$> $HOME/.rvm/wrappers/ruby-2.2.0@execjs/ruby  test_bubble_sort.rb
Loaded suite /tmp/release/test_javascript_from_ruby/test_bubble_sort
Started
.......

Finished in 0.011758991 seconds.

7 tests, 7 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

595.29 tests/s, 595.29 assertions/s

Test sorting swaps

test_bubble_sort.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def test_sorting_waps
  
    actual = @context.exec <<-JS
    swaps = [];
    
    bubbleSort([11,17,1,19], function(i,j) {
      swaps.push([i, j]);
    });
    
    return swaps;
    JS
  
    assert_equal [[1,2],[0,1]], actual
        
  end
$> $HOME/.rvm/wrappers/ruby-2.2.0@execjs/ruby  test_bubble_sort.rb
Loaded suite /tmp/release/test_javascript_from_ruby/test_bubble_sort
Started
........

Finished in 0.013542534 seconds.

8 tests, 8 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

590.73 tests/s, 590.73 assertions/s

How fast are the tests?

The technique presented here requires to run a full Javascript interpreter. Such a thing can slow the calling code and therefore the tests.

A comparison of two different runtime layers show that choosing the runtime is important.

Running the above tests with Node.js (V8) produced next results:

$ ruby test_bubble_sort.rb
Loaded suite /tmp/release/test_javascript_from_ruby/test_bubble_sort
Started
........

Finished in 1.860497025 seconds.

8 tests, 8 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

4.30 tests/s, 4.30 assertions/s

While running the same tests with therubyracer (V8) produced next results:

$ruby test_bubble_sort.rb
Loaded suite /tmp/release/test_javascript_from_ruby/test_bubble_sort
Started
........

Finished in 0.013115584 seconds.

8 tests, 8 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed

609.96 tests/s, 609.96 assertions/s

The difference is big! The second implementation is about 140 times faster… and as the report says, you could execute 600 tests per second while using the call to Node.js can only execute 4.3 tests per second.

Notice it does not mean that Node.js is slow, part of the reason is due to the fact that execjs delegates the execution to the runtime by launching an external command.

The Ruby runtime was

$> $HOME/.rvm/wrappers/ruby-2.2.0@execjs/ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-linux]

Conclusion

The important thing is once again that structuring the code is very important because breaking it into several files has many advantages:

The tests do not require complex configurations neither a dedicated system.