3. Hello World on Steroids

3.1. The Victim

In the last episode, we learned that we write tests to prove our code works properly. Let's create a really simple class for us to test.

# secret_agent.rb
class SecretAgent

  # simple public properties
  attr_accessor :username
  attr_accessor :password

  # our "constructor"
  def initialize(username,password)
    @username = username
    @password = password
  end

  # the logic to determine if the password is
  # good enough for the user to use
  def is_password_secure?
    return false if @password.nil?
    return false if @password.empty?
    return false if @password.size < 4
    return false if @password  'stirred'
    return false if @password  ‘password'
    return false if @password == @username
    true
  end
end

Ok. So, we've got a class which represents a secret agent. What we're about to do is test a user to make sure that their password is secure enough to use in our top-secret database.

The is_password_secure? method will answer that for us. If the user's password passes our stringent set of rules, then the function will return a true value and the secret agent granted access.

3.2. The Assault

Let's build upon the last test we wrote and add in a few more things, specifically, let's test out this brand new SecretAgent.

require 'test/unit'
require 'secret_agent'

class HelloTestCase < Test::Unit::TestCase

  def test_hello
    assert true
  end

  # our new test will exercise the new SecretAgent class
  def test_these_passwords
    # first, let's try a few passwords that should fail
    assert !SecretAgent.new("bond","abc").is_password_secure?
    assert !SecretAgent.new("bond","007").is_password_secure?
    assert !SecretAgent.new("bond","stirred").is_password_secure?
    assert !SecretAgent.new("bond","password").is_password_secure?
    assert !SecretAgent.new("bond","bond").is_password_secure?
    assert !SecretAgent.new("bond","").is_password_secure?
    assert !SecretAgent.new("bond",nil).is_password_secure?

    # now, let's try passwords that should succeed
    assert SecretAgent.new("bond","goldfinger").is_password_secure?
    assert SecretAgent.new("bond","1234").is_password_secure?
    assert SecretAgent.new("bond","shaken").is_password_secure?
  end
end

In this example, we've expanded our test case by adding another test called test_these_passwords with 10 assertions. Let's run it, cross our fingers, and hope it passes.

Started
..
Finished in 0.01 seconds.

2 tests, 11 assertions, 0 failures, 0 errors

Sweet! It passed!

A couple of new things to notice about the results. See the line right underneath the word Started? Notice it has 2 dots instead of only 1 before? Each . represents a test. You can have 1 of 3 values where the dots are.

  • . means successful test (pass)

  • F means failed test

  • E means an error has occurred

3.3. Failure, Error and General Discomfort

Let's add another in 2 more tests. This time, we'll make one of them fail and the other throw an error. Yes, on purpose. Yes, I'm a trained professional.

...
# this will result in a failure because the assertion fails... plus everyone
# knows batman really exists
def test_bam_zap_sock_pow
  batman = nil
  assert_not_nil batman
end

# this will result in an error because it contains an undefined symbol
def test_uh_oh_hotdog
  assert_not_nil does_this_var_speak_korean
end
...

Ok… Let's run this puppy.

Started
F..E
Finished in 0.07 seconds.

1) Failure:
test_bam_zap_sock_pow(HelloTestCase) [/example/test.rb:62]:
<nil> expected to not be nil.

2) Error:
test_uh_oh_hotdog(HelloTestCase):
NameError: undefined local variable or method 'does_this_var_speak_korean' for
#<HelloTestCase:0x28c59a0> /example/test.rb:68:in 'test_uh_oh_hotdog'

4 tests, 12 assertions, 1 failures, 1 errors

Wow. Nasty.

Take a look at the 2nd line. This time we have F..E which means failure, pass, pass, error. In the details underneath the total elapsed time, you see what exactly went wrong and where.

The incredibly observant will notice that even though the two new methods were added as the 3rd and 4th tests, they showed up in the results as 1st and 4th. That's because the tests are sorted alphabetically.

So, that's what errors and failures look like. The difference is, a failure represents an assertion attempt that gave us the wrong results whereas an error is a Ruby problem.

3.4. This Side Up ^

Another thing about assertions. They're fragile. If the test finds an assertion that fails, it will stop execution of that method and move on to the next test.

If I had a test with 4 assertions, of which numbers 2, 3, and 4 were all going to fail, you'd only see #2 as the cause of the failed test. If you were to correct that failure, then rerun the test, it would be #3 thats causes grief.

Got it? Good, there will be a pop quiz on Monday.