defensio-to-the-rescue-comment-1

Turns out Defensio is letting though a LOT of spam here, although that could be because I'm not using it properly, just hacking into it's API a little bit. Anyway - there's a new spam-block feature, that you may have seen on http://po-ru.com here now too.
interblah.net - defensio-to-the-rescue-comment-1

Capturing behaviour in Ruby DSLs

A WORK IN STAGNANT PROGRESS

As part of the work I did implementing Kintama, I found myself flip-flopping between two different ways of capturing test implementations and running them. I think there’s something interesting and perhaps useful in making these approaches more explicit.

I’m trying to elucidate them here, but this is very much a work in progress, and it’s nowhere near finished.

Anyway.

The two means of behaviour slinging

It’s a likely encounter any Ruby programmer, but a certainty for any programmer writing a test framework: at some point you’ll realise there are two fundamental ways of capture and calling “behaviour” in Ruby: Methods, and blocks. The same thing applies to any DSL you might implement, and test frameworks like shoulda and RSpec are indeed DSLs for testing.

Allow me to illustrate.

Let’s say we want to write a couple of tests using the following DSL:

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

When it comes to “running” that test, you basically have two choices – capture the behaviour as a method body and call that method, or use one of the various block/proc calling mechanisms to evaluate the block within a sensible context.

Here’s a trivial implementation of the ‘method’ approach:

def assert(expression)
  raise "Test failed" unless expression
end

$tests = []

def test(name, &block)
  test_name = "test #[snip 'name' cannot be found]"
  self.class.send(:define_method, test_name, &block)
  $tests << test_name
end

def run(tests)
  failures = []
  tests.each do |test|
    begin
      send(test)
      print "."
    rescue => e
      failures << test
      print "F"
    end
  end
  puts "\n\n#[snip 'tests' cannot be found] tests; #[snip 'failures' cannot be found] failures"
  puts "\n#(malformed snip inclusion: {failures.map { |f| "Failed: #{f})" }.join("\n")}\n\n"
end

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

run($tests)

For each “test”, we are defining a method using the name of the test, and then storing those names. When we want to run all the tests, we use send to invoke each of the methods we created.

Here’s the corresponding implementation for a block-based approach:

def assert(expression)
  raise "Test failed" unless expression
end

$tests = {}

def test(name, &block)
  test_name = "test #[snip 'name' cannot be found]"
  $tests[test_name] = block
end

def run(tests)
  failures = []
  tests.each do |test_name, test_block|
    begin
      test_block.call
      print "."
    rescue => e
      failures << test_name
      print "F"
    end
  end
  puts "\n\n#[snip 'tests' cannot be found] tests; #[snip 'failures' cannot be found] failures"
  puts "\n#(malformed snip inclusion: {failures.map { |f| "Failed: #{f})" }.join("\n")}\n\n"
end

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

run($tests)

As you can tell, the implementations are very, very similar. The key difference is that in the second, we are stashing the blocks in a hash, and then using call to run them as procs.

This is interesting for a test-framework implementer because once your tests include some form of state, the choice you make above will have a fairly large impact on the resulting architecture of your framework.

Next steps

… actually explain what the consequences are. Basically it relates to whether or not your tests end up being actual classes/objects or not. It’s pretty hard to show differences in blog form; it might be better to have a simple DSL which I implement using either form and step through the commits.

interblah.net - defensio-to-the-rescue-comment-1

Capturing behaviour in Ruby DSLs

A WORK IN STAGNANT PROGRESS

As part of the work I did implementing Kintama, I found myself flip-flopping between two different ways of capturing test implementations and running them. I think there’s something interesting and perhaps useful in making these approaches more explicit.

I’m trying to elucidate them here, but this is very much a work in progress, and it’s nowhere near finished.

Anyway.

The two means of behaviour slinging

It’s a likely encounter any Ruby programmer, but a certainty for any programmer writing a test framework: at some point you’ll realise there are two fundamental ways of capture and calling “behaviour” in Ruby: Methods, and blocks. The same thing applies to any DSL you might implement, and test frameworks like shoulda and RSpec are indeed DSLs for testing.

Allow me to illustrate.

Let’s say we want to write a couple of tests using the following DSL:

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

When it comes to “running” that test, you basically have two choices – capture the behaviour as a method body and call that method, or use one of the various block/proc calling mechanisms to evaluate the block within a sensible context.

Here’s a trivial implementation of the ‘method’ approach:

def assert(expression)
  raise "Test failed" unless expression
end

$tests = []

def test(name, &block)
  test_name = "test #[snip 'name' cannot be found]"
  self.class.send(:define_method, test_name, &block)
  $tests << test_name
end

def run(tests)
  failures = []
  tests.each do |test|
    begin
      send(test)
      print "."
    rescue => e
      failures << test
      print "F"
    end
  end
  puts "\n\n#[snip 'tests' cannot be found] tests; #[snip 'failures' cannot be found] failures"
  puts "\n#(malformed snip inclusion: {failures.map { |f| "Failed: #{f})" }.join("\n")}\n\n"
end

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

run($tests)

For each “test”, we are defining a method using the name of the test, and then storing those names. When we want to run all the tests, we use send to invoke each of the methods we created.

Here’s the corresponding implementation for a block-based approach:

def assert(expression)
  raise "Test failed" unless expression
end

$tests = {}

def test(name, &block)
  test_name = "test #[snip 'name' cannot be found]"
  $tests[test_name] = block
end

def run(tests)
  failures = []
  tests.each do |test_name, test_block|
    begin
      test_block.call
      print "."
    rescue => e
      failures << test_name
      print "F"
    end
  end
  puts "\n\n#[snip 'tests' cannot be found] tests; #[snip 'failures' cannot be found] failures"
  puts "\n#(malformed snip inclusion: {failures.map { |f| "Failed: #{f})" }.join("\n")}\n\n"
end

test "something" do
  assert 1 == 1
end

test "another thing" do
  assert false == true
end

run($tests)

As you can tell, the implementations are very, very similar. The key difference is that in the second, we are stashing the blocks in a hash, and then using call to run them as procs.

This is interesting for a test-framework implementer because once your tests include some form of state, the choice you make above will have a fairly large impact on the resulting architecture of your framework.

Next steps

… actually explain what the consequences are. Basically it relates to whether or not your tests end up being actual classes/objects or not. It’s pretty hard to show differences in blog form; it might be better to have a simple DSL which I implement using either form and step through the commits.

interblah.net - Capturing behaviour in Ruby DSLs

road-testing-macbook-air-day-one

; updated

Day One

My god, this thing is portable. It’s a lovely size, somehow managing to be ridiculously compact without ever seeming too small. The main rows of keys on the keyboard are the same size as any other laptop keyboard, but what sometimes throws my hands off are the fact that the edges of the machine are that much closer; my palms need to adjust to sitting a bit closer to the keyboard.

The screen resolution is also surprisingly forgiving. I have used a Dell Mini 9 in anger, hackintoshed, and eventually found it’s relatively-low resolution (800x600) a pain. The 11” has a resolution of 1366x768, which is perfectly serviceable. I can bump up the font size in both Terminal.app (Menlo, 18pt) and TextMate (Menlo, 14pt) without sacrificing any context.

Getting the machine set up for development wasn’t too hard; I already store quite a bit of stuff on Dropbox, including working copies of most of my projects. I bootstrapped its sync by copying my Dropbox folder via a USB stick, along with some other documents, after which it did a bit of indexing and was happy to be left to establish quiet dominion over its folder and contents.

Here’s a rough outline of what it took to get the machine development-ready:

  1. Download homebrew
  2. Download XCode - this takes literally ages now that Apple have decided that everyone who wants XCode should also get the iOS development libraries. I can remember when XCode was around 500MB; now it’s 2.5GB
  3. Once XCode is ready, use homebrew to install a bunch of core development software:
  4. git
  5. MySQL
  6. MongoDB
  7. ack
  8. redis
  9. Once git is installed you can let the other stuff install in the background while you install [RVM][], and then
  10. Ruby Enterprise Edition 1.8.7
  11. Ruby 1.9.2
  12. Install a few key ruby libraries
  13. bundler
  14. passenger
  15. Once all of the above have finished, for each application do something along the lines of:
  16. bundle install
  17. rake db:reset
  18. rake

Working in Terminal.app and TextMate doesn’t seem to be hindered by the lower processor speed at all; the editor is snappy, and running tests is just as fast as my old machine. Some tests naturally run slower (as we’ll see later), but I certainly never feel like I’m waiting for the machine to catch up. Perhaps this is the joy of SSD that people keep talking about.

After the first few hours of setup, I remain utterly charmed by how capable the MacBook Air is despite its diminutive form factor.