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.
