BOOK NOTES CHAPTER 10 10.1 UNIT TESTS - unit test - most important testing tool - checks a single method or a set of cooperating methods - test the classes in isolation - avoid confusion due to interacting classes - e.g. use standard input to get a test value, execute method on it - test harness - simple class - for each test - feeds parameters to methods being tested - easily repeatable 10.2 PROVIDING TEST INPUT - hardwire test inputs into the test harness - e.g. - create an array of inputs - use a for loop to test the method on each - generate automatically - feasible to do representative number with loop if few possible inputs - e.g. - use the loop value itself as the test - increment determines which values tested - min and max determine range - random generation extends values - e.g. - use a loop to determine how many random tests to do - inside the loop, use Random class to generate test values - test all features - check typical test cases - legitimate inputs - boundary test cases - values at the boundary of the set of acceptable inputs - still legitimate - expect program to handle correctly - negative test cases - inputs expect program to reject AT 10.1 READING TEST INPUTS FROM A FILE - link a file to the input of a program (as if typed by user) - input redirection - Linux: java Program < data.txt - redirect output - Linux: java Program > output.txt - useful for archiving test cases - e.g. - still use standard in - no need to prompt because not interacting with real user 10.3 TEST CASE EVALUATION - verify output - calculate correct value by hand - often impractical - supply inputs with known answers - verify properties - e.g., square root power of 2 should give original - oracle - also computes correct answer - but slower - write a separate program that writes the oracle values to file, then compare manually 10.4 REGRESSION TESTING AND TEST COVERAGE - make a new test whenever you find a bug - verify bug fix really works - use in all subsequent versions - test suite: collection of such tests - cycling: reappearance of an old, "fixed" buf - best to think through what really causes a bug and fix the root cause - but even if don't, keeping old tests maintains an honest appraisal of how well currently working - regression testing: testing against a set of past failures - black-box testing - testing functionality without consideration of internal structure - important because users don't know internal structure - impossible to ensure works correctly on all cases if only test subset - white-box testing - consider internal structure - toward proving absence of bugs - test coverage - make sure each part of program exercised by at least one test case - only way to know what happens with that code eventually called by a user - both parts of every conditional - write first test cases before complete program - insight into what the program should do - something to use when first compiles - grows more complex with more complex input - GUI - network 10.5 LOGGING - determine where a program spends its time - get printout of program flow - insert trace messages with standard output - current method name - current branch of a conditional - when done testing, want to remove - if find another bug, need to add back in - Logger class - turn off trace messages without removing them from the program - replace print(""); with Logger.global.info("") - Logger messages printed by default - deactivate Logger messages - at the beginning of the main method Logger.global.setLevel(Level.OFF); - most important events for tracing execution flow - entering and exiting a method - print parameters at beginning of method - print return value at end - can also report on progress inside a method - useful for analyzing program behavior - disadvantages - time-consuming to find out which logging messages to insert - too many messages are hard analyze - too few messages don't expose error 10.6 USING A DEBUGGER - programs rarely run perfectly the first time - frustrating to find bugs - use logging to show key variables and program flow - time-consuming to perform necessary logging - debugger - special program - packaged with most modern development environments - locate bugs by following the execution of the program - pause and continue program - see contents of variables while paused - at each pause, choose - variables to inspect - how long to run before next pause - still takes time to run debugger, so important to write best program possible initially - can't avoid using debugger: as programs become longer, logging becomes less feasible - vary widely among systems - book shows Eclipse, which we are using - breakpoint - place in the code where you want the debugger to stop - when run, will execute until it gets to that line - when paused, can - inspect variables - step through the program a line at a time - continue running to the next breakpoint - remain active until removed: should periodically clear unused ones - debugger stops when program terminates - look at current values of variables - when program is stopped - window with current local variables - inspect command: debugger displays contents - continue as longer as all variables contain the expected values - open object so can inspect instance fields - single step command - executes current line and stops at the next line - step through program a line at a time - know how program flows - step into - go to method code - check if the method carries out its job correctly - step over - execute method without showing the code - continue to next line after method - know method already works - rerun program in debugger 10.7 SAMPLE DEBUGGING SESSION - testing can only show the presence of bugs, not their absence - go through this section carefully on your own - even recommend trying to debug it by yourself first - can download all of the example code from Horstmann's website: http://www.horstmann.com/javaconcepts.html HT 10.1 DEBUGGING - reproduce the error - debuggers better for analyzing particular failure than studying program in general - simplify error - find the simplest input you can that still causes the error - divide and conquer - locate the code that produces the failure - start with main method - recursively: - step over methods in current method - will crash - the last method called before the crash is the broken one - restart from that line, and step into the method that caused the crash before - know what your program should do - so can compare against what debugger shows you program does - how many times should a loop iterate? - what should a variable's value be? - when find unexpected value, double-check your expectations - avoid nasty computations by having an idea of range; e.g. shouldn't calculate square root of -2.73148 because know should never be negative - look at all the details - often have a theory - still keep an open mind - understand and agree with everything your program does before looking elsewhere - fix problems as you find them - a later problem may not really be independent of an earlier one - just like compiler errors! - understand each bug before you fix it - e.g. loop executes too many times: may be off by 2, not 1 - continuous problems that just seem to move around may indicate logic errors - hard to debug - may need to rethink design and organization