Thursday, January 20, 2011

Web Application Testing in Ruby: What is Ecxeption in Ruby??

Web Application Testing in Ruby: What is Subroutines in Ruby??
ExceptionsGrowing up with C, I wrote code for every possible error condition. Or, when I was too lazy to write code for error conditions, my code was less robust.

The modern method of error handling is with exceptions, and Ruby has that feature. Use them.

There are two things you can do: handle an exception, and raise an exception. You raise an exception by recognizing an error condition, and then associating it with an exception type. You usually don't need to raise an exception because most system calls already raise exceptions on errors. However, if you've written a new bit of logic, and encounter a forbidden state, then you would raise an exception.

You handle an exception that gets raised -- typically by system calls but possibly by your code. This handling is only for protected code starting with begin and ending with end. Here's a simple example:
#!/usr/bin/ruby
begin
input = File.new("/etc/resolv.conf", "r")
rescue
print "Failed to open /etc/fstab for input. ", $!, "\n"
end
input.each {
|i|
puts i;
}
input.close()

The preceding code produces the following output:
[slitt@mydesk slitt]$ ./hello.rb
search domain.cxm
nameserver 192.168.100.103

# ppp temp entry
[slitt@mydesk slitt]$

However, if the filename in File.new() is changed to the nonexistent /etc/resolX.conf, the output looks like this:
[slitt@mydesk slitt]$ ./hello.rb
Failed to open /etc/fstab for input. No such file or directory - /etc/resolX.conf
./hello.rb:7: undefined method `each' for nil:NilClass (NoMethodError)
[slitt@mydesk slitt]$

Global variable $!i had the value "No such file or directory - /etc/resolX.con", so that printed along with the error message in the rescue section. This exception was then passed to other exception handlers, that wrote additional messages and eventually terminated the program.

Exceptions are implemented as classes (objects), all of whom are descendents of the Exception class. Some have methods over and above those of the Exception class, some do not. Here is a list of the exceptions I was able to find in documentation on the web:
• ArgumentError
• IndexError
• Interrupt
• LoadError
• NameError
• NoMemoryError
• NoMethodError
• NotImplementedError
• RangeError
• RuntimeError
• ScriptError
• SecurityError
• SignalException
• StandardError
• SyntaxError
• SystemCallError
• SystemExit
• TypeError

The following is a more generic error handling syntax:
begin
# attempt code here
rescue SyntaxError => mySyntaxError
print "Unknown syntax error. ", mySyntaxError, "\n"
# error handling specific to problem here
rescue StandardError => myStandardError
print "Unknown general error. ", myStandardError, "\n"
# error handling specific to problem here
else
# code that runs ONLY if no error goes here
ensure
# code that cleans up after a problem and its error handling goes here
end
In the preceding, variables mySyntaxError and myStandardError are local variables to store the contents of global variable $!, the exception that was raised.
Retry
There's a retry keyword enabling a retry on error. This is handy when performing an activity that might benefit from a retry (reading a CD, for instance):
begin
# attempt code here
rescue
puts $!
if EscNotPressed()
print "Reload the CD, or press ESC\n"
retry
else
puts "User declined to retry further"
end
end
Raising an Exception
Sometimes the neither the system nor the language detect an error, but you do. Perhaps the user input someone 18 years old for Medicare. Linux doesn't know that's wrong. Ruby doesn't know that's wrong. But you do.

You can raise a generic exception (or the current exception if there is one) like this:
raise if age < 65
#!/usr/bin/ruby
age = 18
raise if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
[slitt@mydesk slitt]$ ./hello.rb
./hello.rb:3: unhandled exception
[slitt@mydesk slitt]$

To raise a RuntimeError exception with your own message, do this:
raise "Must be 65 or older for Medicare"
#!/usr/bin/ruby
age = 18
raise "Must be 65 or older for Medicare." if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
[slitt@mydesk slitt]$ ./hello.rb
./hello.rb:3: Must be 65 or older for Medicare. (RuntimeError)
[slitt@mydesk slitt]$

To raise a RangeError exception (you wouldn't really do this), you'd do this:
raise RangeError, "Must be 65 or older for Medicare", caller
#!/usr/bin/ruby
age = 18
raise RangeError, "Must be 65 or older for Medicare", caller if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
[slitt@mydesk slitt]$ ./hello.rb
./hello.rb:3: Must be 65 or older for Medicare (RangeError)
[slitt@mydesk slitt]$

Perhaps the best way to do it is to create a new exception class specific to the type of error:
#!/usr/bin/ruby
class MedicareEligibilityException < RuntimeError
end

age = 18
raise MedicareEligibilityException , "Must be 65 or older for Medicare", caller if age < 66
print "Age is ", age, ". This happens after the exception was raised\n"
[slitt@mydesk slitt]$ ./hello.rb
./hello.rb:6: Must be 65 or older for Medicare (MedicareEligibilityException)
[slitt@mydesk slitt]$

Now let's combine raising and handling, by creating a subroutine called signHimUp(), which raises the exception, and the calling main routine, which handles. In this particular, rather contrived program, information about the person whose information raised the exception is stored in the exception itself, by the initialize() method, which assigns its arguments to the class's instance variables, so that this call:
myException = MedicareEligibilityException.new(name, age)
creates an instance of class MedicareEligibilityException whose instance variables contain the person's name and age for later reference. Once again, this is very contrived, but it illustrates some of the flexibility of exception handling:
#!/usr/bin/ruby
class MedicareEligibilityException < RuntimeError
def initialize(name, age)
@name = name
@age = age
end
def getName
return @name
end
def getAge
return @age
end
end

def writeToDatabase(name, age)
# This is a stub routine
print "Diagnostic: ", name, ", age ", age, " is signed up.\n"
end

def signHimUp(name, age)

if age >= 65
writeToDatabase(name, age)
else
myException = MedicareEligibilityException.new(name, age)
raise myException , "Must be 65 or older for Medicare", caller
# raise MedicareEligibilityException , "Must be 65 or older for Medicare", caller
end
end

# Main routine
begin
signHimUp("Oliver Oldster", 78)
signHimUp("Billy Boywonder", 18)
signHimUp("Cindy Centurinarian", 100)
signHimUp("Bob Baby", 2)

rescue MedicareEligibilityException => elg
print elg.getName, " is ", elg.getAge, ", which is too young.\n"
print "You must obtain an exception from your supervisor. ", elg, "\n"

end

print "This happens after signHimUp was called.\n"

In the preceding code, the main routine calls subroutine signHimUp for each of four people, two of whom are underage. The begin/rescue/end structure in the main routine allows exceptions of type MedicateEligibilityException to be handled cleanly, although such exceptions are raised by the called subroutine, signHimU(). , signHimU(). routine tests for age 65 and older, and if so, calls dummy writeToDatabase() and if not, creates a new instance of MedicateEligibilityException containing the person's name and age, and then raises that exception, with the hope that the calling routine's exception handling will be able to use that information in its error message.

The MedicateEligibilityException definition itself is a typical class definition, with instance variables beginning with @, an initialize() constructor that assigns its arguments to the instance variables, and get routines for the instance variables. All of this will be covered later when we discuss classes and objects.

Here is the result:
[slitt@mydesk slitt]$ ./hello.rb
Diagnostic: Oliver Oldster, age 78 is signed up.
Billy Boywonder is 18, which is too young.
You must obtain an exception from your supervisor. Must be 65 or older for Medicare
This happens after signHimUp was called.
[slitt@mydesk slitt]$

As you can see, the first call to signHimUp() successfully ran the stub write to database routine, as indicated by the diagnostic line. The next call to signHimUp() encountered an exceptio MedicateEligibilityException exception, and the code in the rescue block got the patient's name and age from the exception, and wrote it. At that point the begin block was terminated, and execution fell through to the line below the end matching the exception handling's begin. If we had wanted to, we could have terminated the program from within the rescue block, in many ways, including ending that block with a raise command, or to bail immediately, an exit command.

No comments:

Post a Comment