CSC/ECE 517 Fall 2010/ch6 6b SK

From Expertiza_Wiki
Jump to navigation Jump to search

Support for Assertions in Various O-O Programming Languages


Introduction

In computer language an assertion is a construct that immediately terminates the execution of a program if a certain expression or a condition is evaluated to false (assertion failure). It is mainly used for code debugging . Programmers use assertions to check for potential errors or bugs in the application being developed. The main feature of assertions is to verify the validity of the assumptions made by chunk of code during execution. A good example to illustrate this is the use of dynamic memory allocation in C++, wherein we can use an assertion to check a pointer and ensure that it is not null before using this pointer. If this check in not made, an error can be caused because of the occurance of a reference. Assertions play a vital role in developing reliable object-oriented software. An early advocate of using assertions in programming was Alan Turing [5]. Assertions are maily used to make the assumptions made by the programmers(that they believeto be correct) when they write a software explicit. Assertion-based Object-Oriented techniques produce reliable software and enable software components to be reused safely. In languages such as Eiffel, assertions form part of the design process. In other languages like Java, assertions are used only to check assumptions at runtime. Various object oriented programming languages support assertions.

Support for Assertions in Java

When implementing and debugging a class in java programming language, it is a good practice to specify conditions that should be true at a particular stage in a method. These conditions, called assertions, assure a software program’s validity by identifying bugs and related logical errors during the process of development. For example, if you write a module that calculates the temperature of an element, you might assert that the calculated temperature is not less than 0 degree Kelvin. The syntax for assert statements are as follows [1]:

         assert Expression1;

Expression1 is a Boolean expression. If Expression1 is evaluated to be false, then the system throws an AssertionError. This syntax for assert will not give a detail error message. Therefore second form of assert syntax can be used [1] as given below

        assert Expression1: Expression2;

Expression1 is a Boolean expression. Expression2 is an expression that has a value. This version of the assert statement provides detail message for the AssertionError. The system passes the value of Expression2 to the appropriate AssertionError constructor, which uses the string representation of the value as the error's detail message[1]. This form of the assertion statement should be used in preference to the first only when the program has some additional information that might help diagnose the failure. Below is an example code that demonstrates the functionality of assert statement. This code checks with assert that the value entered is an even number only [2].

  import java.util.Scanner;
  
 	public class AssertTest
  {
      public static void main( String args[] )
     {
          Scanner input = new Scanner( System.in );
         
         System.out.print( "Enter an even number:  " );
        int number = input.nextInt();
         
       // assert that the number is even
        assert ((number % 2 == 0)) : "Not an even number: " + number;

      System.out.printf( "You entered an even number %d\n", number );
     } 
   } 

OUTPUT:

      Enter an even number:  20
      You entered an even number 20
      
      Enter an even number:  25
      Exception in thread "main" java.lang.AssertionError: Not an even number:  25
      at AssertTest.main(AssertTest.java:15)

The above code prompts the user to enter an even number, then this number is read from command prompt. The assert statement then determines whether the user entered an even or odd number. If the user entered an odd number (as in second case of output), then the program throws an error. Otherwise, the program proceeds normally. Lines that gets executed after the assert statement can assume that number is not odd

One obvious question that may arise is when exceptions can do the error handling why we need another level of checking. Java exceptions are primarily used to handle unusual conditions arising during program execution. Assertions are not to replace exceptions but to augment them. Assertions are used to specify conditions that a programmer assumes are true [11]. When programming, if a programmer can swear that the value being passed into a particular method is positive no matter what a calling client passes, it can be documented using an assertion to state it. Exceptions handle abnormal conditions arising in the course of the program; however they do not guarantee smooth or correct execution of the program. Assertions help state scenarios that ensure the program is running smoothly. Assertions can be efficient tools to ensure correct execution of a program [11]. They improve the confidence about the program.

Types of Assertions

  • Preconditions - When a method is invoked, the state of the program at that time is indicated by precondition assertions. Precondition refers to the parameters passed to a method in a program. Precondition asserts validates the parameters passed to a method before they are used inside the method.
  • Postconditions - The state of the program after a method completes its execution is indicated by postcondition assertions. Postcondition should be evaluated before the exit point in a method. Postcondition asserts validates the return values in a method which has several return statements.

One situation where use of assertions is helpful in Java programming language is : Internal Invariants. Assertions can be used within programs to make sure the program behaves in a predetermined manner and will throw an error when violated. For instance, an assertion can be placed in the code below to declare that age will never be negative [1].

          if (age > 0)
          {
             age = age + 1;
          } 
          else
          {
      		assert age >0:"Age cannot be negative"
          }

Enabling and Disabling Assertions

Assertions are disabled at runtime by default as they reduce performance. To enable assertions at runtime, use the -ea command-line option. To disable assertions, use –da command line option. To execute a code with assertions enabled use

        java -ea AssertTest

If you want to disable it again use

        java -da AssertTest


xUnit Frameworks that support testing in Java

Some of the xUnit frameworks that support testing Java code are JUnit , SpryTest , Jtest , TestNG and JExample. Other testing frameworks supported by Java can be looked up at [9]

Support for Assertions in Ruby

Unit testing is a process where individual parts of source code are isolated and tested separately to determine if they are bug free. The idea behind unit testing is that you write a test method that makes certain assertions about your code, working against a test fixture. Ruby language supports a module called Test::Unit::Assertions in test/unit/assertions.rb. Test::Unit::Assertions contains the standard Test::Unit assertions. Assertions is included in Test::Unit::TestCase.

Public class assert methods

  • assert( boolean, [msg] )- This ensures that the object/expression is true
 assert [10, 20].include?(50)


  • assert_block(message="assert_block failed.") {|| ...} - If the block yields to true , then the assert passes

Example [3]:

      def assert_block(message="assert_block failed.") # :yields: 
       _wrap_assertion do
         if (! yield)
           raise AssertionFailedError.new(message.to_s)
         end
       end
     end


  • assert_match( regexp, string, [msg] )- Ensures that a string matches the regular expression

Example [3]:

     def assert_match(pattern, string, message="")
       _wrap_assertion do
         pattern = case(pattern)
           when String
             Regexp.new(Regexp.escape(pattern))
           else
             pattern
         end
         full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
         assert_block(full_message) { string =~ pattern }
       end
     end


  • assert_not_equal(expected, actual, message="") - If expected != actual, then the assert passes

Example [3]:

     def assert_not_equal(expected, actual, message="")
       full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
       assert_block(full_message) { expected != actual }
     end


  • assert_nil(object, message="") - This assert passes if the object is nil.

Example[3]:

  # File test/unit/assertions.rb, line 173
     def assert_nil(object, message="")
       assert_equal(nil, object, message)
     end


There are a bunch of other public class methods. This can be obtained from [4]

Test Method & Test Fixture

Assertions must be used inside test methods within test fixtures. Related tests are grouped inside a common test class using assert. The advantage of having a separate class for all related tests is that it keeps the actual developed code to be tested uncluttered from the test code, hence making maintainability easier. It also allows these test code to be deleted from the development code before the final delivery as these test codes are needed mainly for the developer/tester and need not be part of the final product. Major advantage is that it lets you write up a common test fixture for all tests to run against. Test fixtures are a way of organizing test data; they reside in the fixtures folder. The test_helper.rb file holds the default configuration for your tests.

Ruby on Rails

Rails is an open source web framework for Ruby language. Rails adds some custom assertions of its own to the test/unit framework some of which are as stated below:

  • assert_difference(expressions, difference = 1, message = nil) {...} [4]

Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.

  • assert_recognizes(expected_options, path, extras={}, message=nil) [4]

Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.

  • assert_template(expected = nil, message=nil) [4]

Asserts that the request was rendered with the appropriate template file.

xUnit Frameworks that support testing in Ruby

The only xUnit framework that support testing Ruby code is Test::Unit. Other testing frameworks that support Ruby but that do not fall under the xUnit umbrella are RSpec , Shoulda , microtest and Bacon.

Support for Assertions in C++

In C++ the support for assertions is pretty limited. Assertion is mainly used for error detection during debugging. If an assertion fails ,the program would stop at that point and indicate which assertion caused the failure.

Simple Assert

In C++ assertions are defined in the header file Assert.h.

  • assert (expression);

Example:

      assert(isAdmin == true);

Similar to other languages , assertions can be turned off in C++ if the NDEBUG macro is defined before the inclusion of the header Assert.h .

xUnit Frameworks that support testing in C++

Some of the xUnit frameworks that support testing C++ code are API Sanity Autotest, C++test, Cantata++, CppUnit and CppTest. To see the complete list of xUnit and other testing frameworks supported by C++ please refer to [9]

Support for Assertions in Python

In Python when the assert statement is encountered, the expression following the assert keyword is evaluated and the AssertionError exception is raised if the expression evaluates to false.

Simple Assert

The following code would raise the AssertionError exception.

  • assert condition

Example:

      #!/usr/bin/python
      assert 1 == 2

Extended Assert

In addition to evaluating an expression , arguments can be passed to the assertion so that for example, the right message can be displayed based on the outcome of the assertion.

  • assert Expression[, Arguments]

Example [8];

      #!/usr/bin/python
      def IsSenior(age):
         assert (age >= 1),"Age cannot be zero or less than zero !"
         return (age >= 65)
      print IsSenior(15)
      print IsSenior(70)
      print IsSenior(-1)

Example Output;

      false
      true
      Traceback (most recent call last):
        File "test.py", line 9, in <module>
          print IsSenior(-5)
        File "test.py", line 4, in IsSenior
          assert (age >= 1),"Age cannot be zero or less than zero !"
      AssertionError: Age cannot be zero or less than zero !

One of the key feature of assertions in Python is that if python is started with -O option, then the assertions would not be evaluated. The scenario where this feature would apply is developers can write a lot of assertions for debugging and when the code is to be executed in production , if it is started with -O option , then all the assertions written by the developers for debugging would not be evaluated.

In general assertions are not the best means to test for failure cases. Instead of using an assertion exceptions would be the right way to handle scenarios like wrong user input or system/environment failures.

xUnit Frameworks that support testing in Python

Some of the xUnit frameworks that support testing python code are PyUnit, Nose, py.test and TwistedTrial. Other testing frameworks supported by python can be looked up at [9]

One common used of assertions in python is for type checking. Please refer the article Assertion in O-O languages for more details.

Benefits of assertions

  • Use of assertions in the program help detect errors immediately and directly, rather than at a later stage. Assertion failure usually reports the location of failure in the code which helps in pin-pointing the error without further debugging.
  • Assertions provide run time check for assumptions made by developers
  • Assertions are also sometimes placed at points the execution is not supposed to reach. For example, assertions could be placed at the default clause of the switch statement in languages such as C++, and Java. Any case which the programmer does not handle intentionally will raise an error and the program will abort rather than silently continuing in an erroneous state.
  • Assertions can be viewed as "dynamic documentation", since they are checked at runtime, contrary to the traditional approach of documenting assumptions via plain /* comments */.
  • Assert statements are great for helping you to refactor and optimize your code with greater confidence that you have preserved correctness


Limitations of assertions

  • Assertions rarely allow for graceful error recovery. They terminate the program abruptly and may not release some of the resources used by the program; hence it is considered bad practice to rely upon assertions for handling expected error conditions.
  • Assertions sometime hinder execution time. For example, if the program has an assert that checks to see if the number to be returned is the smallest in the array, then the assertion will have to do the same amount of work that the method would have to do.


Conclusion

When writing program, it is a good practice to check for violations of basic assumptions in the code. These checks help in debugging code. The assertion facility in J2SE 1.4 (and later versions) provides a unified support for assertions in Java technology as well as a convenient way for developers both to turn assertions on and off as needed. Assertions are used in Test Driven Development(TDD) in Ruby programing language. The Test::Unit library in Ruby has a variety of built in assertions that makes writing tests much easier. As seen above assertions are natively supported in some languages like Ruby, whereas in languages like C++ and python it is merely used as a mechanism to track errors when debugging.

Although the use of assertions replaces the adhoc use of conditional tests with a uniform methodology, it does not allow for a repair strategy to continue program execution. This means that when an exception is detected, the program aborts with no recovery mechanism. Nevertheless, assertions play an important role in debugging and designing code with testability in mind. The assertion facility can be used to support an informal design-by-contract style of programming.

References

  • Kathy Sierra; Bert Bates Head First Java,O'Reilly Media, Inc., February 9, 2005
  • Dave Thomas, with Chad Fowler and Andy Hunt Programing Ruby, the Pragmatic Programmers, LLC, 2005

External Links

[1] Programming with Assertions

[2] Assertions in java

[3] Assertions in Ruby

[4] Ruby assertions

[5] Ruby on Rails assertion cheat sheet

[6] More about assertions

[7] Python simple statements documentation

[8] Assertions in Python

[9] Testing frameworks for programming languages

[10] xUnit

[11] More on assertions