5_Specifications_Exceptions_and_Testing

Understand what an exception is, and what they are useful for Use exceptions in combination with a try—catch block Create and use a custom exception class Define and give examples of preconditions and postconditions Write complete and correct method specifications Partition inputs to design tests for a method that cover all expected behaviours Write unit tests against a method
展开查看详情

1.Exceptions, Specifications, and Testing 1 Lecture 5: Exceptions , Specifications, and Testing Learning Goals Understand what an exception is, and what they are useful for Use exceptions in combination with a try — catch block Create and use a custom exception class Define and give examples of preconditions and postconditions Write complete and correct method specifications Partition inputs to design tests for a method that cover all expected behaviours Write unit tests against a method © Paul Davies, C. Antonio Sanchez. Not to be copied, used, or revised without explicit written permission from the copyright owner.

2.Exceptions Exceptions, Specifications, and Testing 2 Exceptions are special objects used to signal bugs in code, allowing us to track down coding errors more effectively special conditions which should be handled differently terminate called after throwing an instance of std::out_of_range what(): vector::_M_range_check: __n (which is 10) >= this->size() (which is 5) int main() { std :: vector < int > data = { 1 , 2 , 3 , 4 , 5 }; int x = data.at( 10 ); std ::cout << "exit" << std ::endl ; return 0 ; }

3.void add_student( int id, const std :: string & name, std :: map < int , std :: string >& database) { if ( database.find(id) != database.end() ) { throw StudentIdAlreadyExists (); } else { database.insert({id, name}); } } Exceptions Exceptions, Specifications, and Testing 3 Exceptions are special objects used to signalling bugs in code special conditions which should be handled differently

4.Exceptions Exceptions, Specifications, and Testing 4 Exceptions can by thrown in code using a throw statement (actually, anything can be thrown, but good programming practice dictates that they should be some child of std::exception ). Exceptions can be caught in a try – catch block. If an exception is not caught, it propagates up the call stack, exiting each function call at the current location, destroying all local variables in the process. If left uncaught, it will eventually trigger the program to terminate.

5.Exceptions Exceptions, Specifications, and Testing 5 int main() { try { std :: vector < int > data = { 1 , 2 , 3 , 4 , 5 }; int x = data.at( 10 ); } catch ( std :: out_of_range & oor) { std ::cout << oor.what() << std ::endl; } std ::cout << "exit" << std ::endl; return 0 ; } Output: vector::_M_range_check: __n (which is 10) >= this->size() (which is 5) exit

6.Exceptions Exceptions, Specifications, and Testing 6 Exceptions: Can be thrown and caught in code Stop execution of current function at throw statement Propagate upwards through call stack until caught All local variables are destroyed as exception propagates Useful for: Special handling of unexpected or detrimental events Separation of normal and error-handling code #include <exception> #include <iostream> double div( double x, double y) { if (y == 0 ) { // division by zero throw std ::exception(); } double p = x/y; std ::cout << "Div is " << p << std ::endl; return p; } double percent( double x, double y) { double p = div(x, y); p = p* 100 ; std ::cout << "Percent is " << p << std ::endl; return p; } double grade( double x, double y) { double g = 0 ; try { g = percent(x, y)/5; } catch ( std :: exception & ex) { std ::cout << "An exception was caught" << std ::endl; } return g ; } int main() { double g = grade( 10 , 0 ); std ::cout << "Grade is " << g << std ::endl; return 0 ; }

7.Custom Exception in C++ Exceptions, Specifications, and Testing 7 #include <exception> class StudentIdAlreadyExists : public std :: exception { public : const char * what() noexcept { return "Id already exists" ; } }; Create a class that inherits from std::exception , override the what() method to return a description. Feel free to add any other useful members/methods for extracting useful information about the exception.

8.Multiple Catch Statements Exceptions, Specifications, and Testing 8 int main() { std :: map < int , std :: string > database; try { add_student( 1234567 , "Antonio" , database); add_student( 1234567 , "Sanika" , database); } catch ( StudentIdAlreadyExists & siae) { std ::cout << siae.what() << std ::endl; } catch ( std :: exception & ex) { std ::cout << ex.what() << std ::endl; } catch (...) { std ::cout << "caught something else" << std ::endl; } std ::cout << "exit" << std ::endl ; return 0 ; }

9.Exceptions: Uses and Abuses Exceptions, Specifications, and Testing 9 Exceptions are designed for exceptional circumstances . They should not be used for common occurrences. There is a performance penalty when code takes the exception path. Exceptions allow us to separate error-handling code from the normal execution path. Important uses are in File IO, network communication, and when users enter invalid inputs in GUIs (think: error message boxes). https://isocpp.org/wiki/faq/exceptions

10.Specifications Exceptions, Specifications, and Testing 10 Many software bugs arise when interfacing between two pieces of code because of a misunderstanding of what a particular function does, or how it behaves for a set of given set of inputs. int find(std ::vector< int >& data, int val ) What does this function do? int a0 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 2 ); int a1 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 6 ); int a2 = find({ 0 , 1 , 1 , 1 , 4 , 5 }, 1 ); int a3 = find({}, 1 );

11.Specifications Exceptions, Specifications, and Testing 11 A specification is a contract detailing conditions on the inputs, description of the outputs , and any guarantees that the method or class makes. It should provide all information a programmer needs to be able to use your code effectively, without needing to know the implementation details. Preconditions: set of conditions that must be satisfied by the input arguments. The onus is on the caller to guarantee these, otherwise the behaviour of the code is left unspecified. Postconditions: if the preconditions hold, precisely specifies the outputs: which variables are modified, if exceptions are thrown, and any other effects . The implementer has the obligation to guarantee these.

12.Specifications Exceptions, Specifications, and Testing 12 // REQUIRES: data is a non-empty vector // EFFECTS: returns the index of the last occurrence // of val in data, or -1 if not found int find( std :: vector < int >& data, int val); int a0 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 2 ); int a1 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 6 ); int a2 = find({ 0 , 1 , 1 , 1 , 4 , 5 }, 1 ); int a3 = find({}, 1 ); // f ails precondition, output indeterminate Preconditions Postconditions

13.Specifications Exceptions, Specifications, and Testing 13 // REQUIRES: any vector data, integer val // EFFECTS: returns the index of the first occurrence // of val in data , throws an ItemNotFound // exception if not found int find( std :: vector < int >& data, int val); int a0 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 2 ); int a1 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 6 ); int a2 = find({ 0 , 1 , 1 , 1 , 4 , 5 }, 1 ); int a3 = find({}, 1 );

14.Specifications Exceptions, Specifications, and Testing 14 // REQUIRES: data to be sorted in ascending order // EFFECTS: returns the index of an occurrence // of val in data , -1 if not found. // GUARANTEES: search time is O(log(n)) int find( std :: vector < int >& data, int val); int a0 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 2 ); int a1 = find({ 0 , 1 , 2 , 3 , 4 , 5 }, 6 ); int a2 = find({ 0 , 1 , 1 , 1 , 4 , 5 }, 1 ); int a3 = find({}, 1 );

15.Specifications as Documentation Exceptions, Specifications, and Testing 15 /** * Searches the vector data to find the element val * in O(log(n)) time * * @param data vector to search, sorted in ascending order * @param val value to find * @return first occurence of val in data * @throws ItemNotFound if val is not found */ int find( std :: vector < int >& data, int val); Note: These are Doxygen-style comments. Doxygen is a tool that can be used to automatically compile online documentation. http ://www.stack.nl/~dimitri/doxygen /

16.Specifications Exceptions, Specifications, and Testing 16 If there are no specified preconditions on an input, then it is assumed to accept any value according to its type. If the postconditions don’t mention modifying an input, then it is assumed they remain untouched. All effects must be explicitly described, including if internal member variables are modified in a way to have observable changes in the class’s behaviour. Specifications should never mention local variables, private fields, or implementation details. That way, the programmer should be free to change the implementation without needing to modify the specification.

17.Specifications Exceptions, Specifications, and Testing 17 // if c1 is used but not modified, use void use_cylinder ( const Cylinder & c1 ); // if c1 will be modified directly, use void use_cylinder ( Cylinder & c1 ); // if a copy of c1 is needed, use void use_cylinder ( Cylinder c1 ); // if c1 is allowed to be NULL (and you are going to check), // but you are not going to modify it, use void use_cylinder ( const Cylinder * c1 ); // if c1 is allowed to be NULL and (if not NULL) the // contents will be modified directly, use void use_cylinder ( Cylinder * c1 ); Forces c1 to be non-null, and cannot be modified Forces c1 to be non-null, but can be modified Forces c1 to be non-null, guaranteed not to modify original (makes a copy) c1 can be null, but cannot be modified c1 can be null, and can be modified

18.Testing Exceptions, Specifications, and Testing 18 Testing is a component of a more general process called validation . Validation includes: Formal reasoning : also called verification , constructs a formal proof that the program is correct. Code review : code “proof-reading” by a colleague or peer to look for bugs/inefficiencies. Testing : running the program on carefully selected inputs and checking the results against known answers .

19.Testing Exceptions, Specifications, and Testing 19 Proper testing can be hard. It requires us to switch from programmer to tester, lose the ego , and purposely try to break our own code. Exhaustive Testing: going through every possible input, checking all outputs against known solution. This is often infeasible. Haphazard Testing: choosing a few inputs/outputs to verify method functions as expected. This is often a first step, will likely catch basic bugs, shows that code is at least partially functional. Does nothing to instill confidence in program is correct. Random/Statistical Testing: randomly generate inputs, potentially comparing outputs to a different implementation already proven to be correct. Unfortunately, software behaves discontinuously and discretely across the space of possible inputs, often at an input boundary. e.g . the Pentium division bug. http://www.willamette.edu/~mjaneba/pentprob.html

20.Testing Exceptions, Specifications, and Testing 20 The most effective approach is to smartly and systematically choose test cases, based on the specifications, that cover all sets of expected distinct behaviours. Partition the input domain into a set of sub-domains, each consisting of inputs with similar expected program behaviours Choose one or two test cases from each sub-domain Choose test cases to trigger every possible effect (i.e. postcondition ) Choose one or two test cases from each boundary (or edge , or corner ) between sub-domains https://ocw.mit.edu/ans7870/6/6.005/s16/classes/03-testing/

21.Testing Exceptions, Specifications, and Testing 21 /** * Searches the vector data to find the element val * in O(log(n)) time * * @param data vector to search, sorted in ascending order * @param val value to find * @return first occurence of val in data * @throws ItemNotFound if val is not found */ int find( std :: vector < int >& data, int val); Input sub-domains: data contains one instance of val data contains multiple instances of val data contains no instance of val Boundaries: vector of size zero vector of size one val is first item val is last item

22.Testing Exceptions, Specifications, and Testing 22 Unit Testing: testing each method/class (i.e. a unit ) in isolation . Each partition of inputs is usually separated into a separate test so that a failed test will indicate exactly what type of bug to search for. Integration Testing: testing the system as a whole to ensure the various components of the software function properly in combination. If all unit tests pass, then you know the issue is somewhere in the interfacing or timings. Regression Testing: testing the system to ensure it performs exactly as it did in a previous functional version. Often important when adding new features or optimizing parts of code, making sure you don’t break any part that works.

23.Unit Testing Frameworks Exceptions, Specifications, and Testing 23 https://github.com/philsquared/Catch Catch: a “simple” header-only unit testing framework Google Test: a more feature-rich testing framework brought to you by Google. Requires compilation/installation. https://github.com/google/googletest

24.Activity: Exceptions, Specifications, and Testing 24 /** * Modular arithmetic multiplication, ( x*y)%n * * @param x left-multiplicant, >= 0 * @param y right-multiplier, >= 0 * @param n modulus, > 0 * @return the remainder of (x*y)/n */ int mod_mul( int x, int y, int n); Input sub-domains: result = 0, … , n-1 x, y < n x, y >= n Boundaries: x = 0 y = 0 n = 1 **large n? e.g. >= sqrt(2147483647) ** random testing can sometimes help reveal these