CS 3500 Fall 2013 Lecture 5: Designing tests and test harness; Exceptions ------------------------------------------------------- Topics: ------- Practice the design recipe on a simple example. Designing test harness for a given specification -- homework assignment Exceptions ---------------------------- Here is an example of an ADT and its algebraic specification: Signature: Public static methods (of the Abc class): defg : Abc x int --> int hijk : Abc x int --> Abc lmno : Abc x int --> Abc pqrs : int --> Abc tuvw : Abc --> int Algebraic Specification: Abc.defg (Abc.lmno (u, k), n) = Abc.defg (u, n) if n < Abc.tuvw (u) Abc.defg (Abc.lmno (u, k), n) = k if n == Abc.tuvw (u) Abc.defg (Abc.lmno (u, k), n) = n if n > Abc.tuvw (u) Abc.defg (Abc.pqrs (k), n) = 3 Abc.hijk (Abc.lmno (u, k), n) = Abc.lmno (Abc.hijk (u, n), k) if n < Abc.tuvw (u) Abc.hijk (Abc.lmno (u, k), n) = Abc.lmno (u, n + 1) if n == Abc.tuvw (u) Abc.hijk (Abc.lmno (u, k), n) = u if n > Abc.tuvw (u) Abc.hijk (Abc.pqrs (k), n) = Abc.lmno (Abc.pqrs (0), k) Abc.tuvw (Abc.lmno (u, k)) = 1 + Abc.tuvw (u) Abc.tuvw (Abc.pqrs (k)) = 0 ------------------------------ Rather than repeating the explanation of the design recipe again, we just write down the list of the eight steps discussed in the previous lecture: Step 1: Identify the basic creators. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Step 2: Define an abstract class named T. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Step 3: For each basic creator c of the ADT, define a concrete subclass of T. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Step 4: For each concrete subclass, declare instance variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Step 5: For each concrete subclass, define a Java constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Step 6: For each operation f of the ADT that is not a basic creator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ declare an abstract method f in the abstract class T. Step 7: For each operation of the ADT, define a static method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ within the abstract class as follows (creators vs others) Step 8: Now for each abstract method implement concrete methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ------------------------------- Designing test harness for the ADT with the given specification: The previous lecture notes described the general procedure for designing a test harness. We now focus on what test cases we need to include in our test suite. We are designing the 'black box' tests. That means we do not know anything about ho the specification is implemented, and can rely only on the information provided by the specification. First of all we need to test all basic creators. Doing so will also allow us to generate a collection of objects that can be used in further tests. If a method produces a value of the primitive type, we just test it on several sample objects, making sure we try at least one instance of every subclass - or one instance that has been generated for each basic creator. If the method definition contains decisions (if statements), then we have to make sure that we test every option. If the method definition produces another instance of our ADT, we need tests that verify the properties of the created instance. We can do it by invoking other methods on the created instance that check some of its properties. Of course, any of these tests that compare two different instances of out ADT rely on the proper implementation of the 'sals' method - and we have to make sure to test it on several examples. Finally, we need to do something about the 'hashCode' method. First of all we need to make sure that if two objects are 'equal' then they produce the same 'hashCode'. To check that the hashCode for different object is usually different is harder. A thorough test suite would test this feature a number of times, recording any matches in hashCode for objects that are not 'equal' and verify that the percentage of matches is sufficiently small. In our exercise we only require that such matches be reported, but we omit for now generating stress tests that would provide a statistically significant result. -------------------------------- Exceptions. ----------- Exceptions are 'thrown' when the program behaves in other than prescribed manner. Some are thrown by the Java runtime engine, when the program attempts to do an illegal operation (for example division by zero, index out of bounds, or invoking a method on a null object. Others are thrown by the programmer when some special condition occurs in the code. Exception types: -- RuntimeException and all of its derived classes of exceptions typically caused by unexpected flaw in the program, a breach of contract that has not been anticipated - and are probably the fault of the client. There do not have to be declared - i.e. the programmer does not have to announce that this part of the code could throw such exception. The client is not expecting these exceptions and does not provide for handling them. -- Exception - and the derived classes that are not subclasses of the Runtime Exception typically caused by a predictable flaw in the program, or possible flaws in the program inputs -- the programmer watches out for these and announces that these conditions may happen during the execution of this method (via 'throws' clause). The client of such procedure is required to wrap the method invocation within the 'try" clause and provide the matching 'catch' clause that describes the action to take should the expected exception be thrown. When should we throw exceptions and of what type? If the specification does not cover some cases, i.e. the behavior is not specified, we are not required to provide a reasonable behavior. In that situation, we should throw a RuntimeException. This alerts the client that some illegal operation has been attempted, but does not impose the burden of watching out for the possibility of the exception (using try-catch clauses). Throwing an exception that is not a subclass of RuntimeException should be done only if we know that the situation causing the exception can reasonably happen in a regular use of the method, and would prevent the method from working as desired. For example, if the method is processing user input and converting the input String into numeric values, you should throw exception if the String cannot be converted into a valid numerical value. The user of the method can then decide what to do with the failed cases. When should you protect yourself by using the try-catch clause? If the method you are invoking signals that it will throw an exception, you are required to use the try-catch clause when invoking it. When there is no notification that the method will grow an exception, but you are afraid it may happen, you should protect yourself with the try-catch clause. For example, if you are writing a test for user's method, it may happen that it will throw exception if it is not correctly implemented - but you would like to report on all tests - so using the try-catch clause would allow you to report that the method invocation threw an exception, and proceed with other tests.