Testing and Debugging Your C# Application
Lesson 1
Error Types
Syntax Errors
- Compiler cannot process provided source code - e.g. keyword wrongly typed
- Build project -> errors detected, underlined and added to task list
- Double click error in task list - associated code highlighted
- Obtain help by pressing F1
- Minimal syntax checking provided whilst typing in code
Run Time
-
App performs illegal operation
- Divide by zero
- Security exceptions
-
Exception describing error is thrown - write code to handle it
Logical
- Correct execution but unexpected results
- e.g. calculate pay but multiply hours per week by 400, not 40
Break Mode
- Halt program execution and step through line at a time
- Enter on following conditions:
- Choose to Step Into, Over or Out
- Execution reaches breakpoint
- Execution reaches stop statement
- Unhandled exception thrown
Breakpoints
-
Function - stop at specified location in function
- Click grey bar to left of code line to stop at
- Right click on desired line and insert breakpoint
- Insert via Debug menu
-
File - specified location in file
-
Address - when specified memory address accessed
-
Breakpoint window
- manage all breakpoints
- display information
- create, delete or disable
Debugging Windows
- Output - command line output, debug and trace statements
- Locals (all vars), Autos (current and previous line vars), Watch (chosen vars) - monitor program variables
- Command - execute procedures, evaluate expressions, change vars
- Cannot accept data declarations
- In Break mode
- if enter statement / method call IDE switches to runtime, executes statement, return to break mode
- print variable values - use ? operator
Lesson 2
Debug and Trace
- Debug class - log messages during execution (of debug code)
- Trace class - log messages during execution (both debug and release code)
- Both classes contain static methods to test conditions and log data
- Class output sent to listeners (and Output window)
Methods
- Write - write to listeners unconditionally
- WriteLine - write to listeners unconditionally followed by CR
- WriteIf - write to listeners if Boolean expression true
- WriteLineIf - write to listeners unconditionally followed by CR if Boolean expression true
- Assert - displays message box and writes to listeners if Boolean expression false
- Fail - displays message box and writes to listeners
Listeners Collection
- Listeners collection organises and exposes classes that can receive trace output
- Listeners collection initialised with DefaultTraceListener class - receives messages even if no other listeners attached
- DefaultTraceListener directs trace to IDE output window
- 2 base trace classes
- EventLogTraceListener - directs output to event log
- TextWriterTraceListener - directs output to stream or TextWriter
// Open (or create) file
System.IO.FileStream myLog = new System.IO.FileStream("C:\\myfile.txt", System.IO.FileMode.OpenOrCreate);
// Create TraceListener logging to specified file
TextWriterTraceListener myListener = new TextWriteTraceListener(myLog);
// Add to listeners collection
Trace.Listeners.Add(myListener);
- To write to file must flush, either by explicitly calling trace method or setting AutoFlush property to true
- Using EventLogTraceListener similar
- Create new EventLog
- Create EventLogTraceListener class
- Add to listeners collection
// Create event log entitled "DebugLog"
EventLog myLog = new EventLog("Debug Log");
// Set EventLog source property (avoid error)
myLog.source = "Trace Output";
// Create EventLogTraceListener
EventLogTraceListener = new EventLogTraceListener(myLog);
Trace Switches
- 2 types - BooleanSwitch class (on or off) and TraceSwitch class (five values)
- Both classes require DisplayName (its name in configuration file) and Description
BooleanSwitch myBoolSwitch = newBooleanSwitch("Switch1", "Control Data Tracing");
TraceSwitch myTraceSwitch = new TraceSwitch("Switch2", "Control Form Tracing");
-
TraceSwitch = 5 settings, exposed by TraceSwitch.Level property
- TraceLevel.Off
- TraceLevel.Error
- TraceLevel.Warning
- TraceLevel.Info
- TraceLevel.Verbose
-
TraceSwitch class exposed four Boolean properties corresponding to trace levels of same name, e.g. if TraceSwitch.Level = TraceLevel.Info then TraceSwitch.TraceInfo is true (as are TraceSwitch.TraceError and TraceSwitch.TraceWarning) while TraceSwitch.TraceVerbose is false
-
No auto hook-up between trace switches and statements, use TraceSwitch to test if output required:
Trace.WriteIf(myBoolSwitch.Enabled == true, "error");
Trace.WriteIf(myTraceSwitch.TraceInfo == true, "type mismatch");
Configure Trace Switches
-
Configured within XML application .config file
-
Config file located in executables folder
-
Config file called appname.exe.config
-
May need to create config file (not all apps have them)
-
When app creates trace switch it checks .config file for info on switch (identified by DisplayName)
-
To create file
-
Add new item from project menu
-
Choose text file and name appropriately
-
Type following
<?xml version="1.0" encoding="Windows-1252"?>
<configuration>
< system.diagnostics>
<switches>
<add name="myBoolSwitch" value="0"/>
<add name="myTraceSwitch" value="3"/>
</switches>
< /system.diagnostics>
</configuration>
Lesson 3
Unit Test Plan
-
Run-time and logical error snot detected without thorough testing
-
Testing and debugging separate but related activities
- Debug = finding and correcting code errors
- Testing = process by which errors found
-
Testing usually broken down by method, exercised using variety of params - termed unit testing
-
Cannot test all permutations - use representative examples (test cases)
Test Case Design
-
Minimum = exercise all lines of code
-
Design cases to work through all decision branches
-
App should behave as expected when normal params provided
-
App should degrade gracefully when params outside bounds provided
- Boundary conditions - minimum and maximum and off by one
- Bad data - values well outside range (0, negative, etc.)
- Data combinations - test all method params at boundary, bad values, etc
-
Determine expected test case results prior to actual test
-
Compare obtained results with those expected
Lesson 4
Exceptions
-
Structured Exception Handling means of recovering gracefully from errors
-
Exception = instance of specialist class deriving from System.Exception
- Message property = human readable description of error
- StackTrace property = stack trace to pinpoint error location
-
Run time error occurs
- Exception generated and passed up call stack to caller
- If exception handler encountered it is handled, otherwise passed up stack to next method... and so on
- If no handler found default used - message box displayed and app stopped
Exception Handler
- Implemented on method basis - i.e. individually tailored to it and exceptions likely to be thrown
- Wrap code associated with handler in try block
- Add 1+ catch blocks to handle exceptions
- Add code that must always be executed in finally block
public void Parse(string a string)
{
try
{
double aDouble;
aDouble = Double.Parse(aString);
}
catch (System.ArgumentNullException e)
{
// Code to handle null argument - e
// contains ref to exception (access to
// its info)
}
catch
{
// Catch any other exception
}
finally
{
// Code that must be executed
}
}
- After code in (only one) catch block executed, the finally block is run
- Write catch blocks in order from most to least specific
- Can omit catch block (exception handled further up call stack) but still include finally block to execute any code in method that must run before return
Throwing Exceptions
-
2 situations
- Only partially handled exception and want to bubble it up call stack
- Unacceptable condition occurred that cannot be handled locally and must be communicated to parent - throw standard or custom exception
-
Rethrow exception by using throw
try
{
}
catch (System.NullReferenceException e)
{
// Assuming exception can not be handled
// rethrow
throw e;
}
- Can pass additional information on when rethrowing, e.g. to provide informative message around original exception
throw new NullReferenceException("Widget A is not set", e);
- Custom exception - only for exceptional circumstances - not for use as communication between client and components (use events). Only use when conditions mean execution can not proceed without intervention
- Custom exceptions derive from System.ApplicationException, e.g.
public class WidgetException:System.ApplicationException
{
// Var to hold widget
Widget mWidget;
public Widget ErrorWidget
{
get
{
return mWidget;
}
}
// Constructor takes widget and string
//describing error conditions
public WidgetException(Widget W, string S) : base(S)
{
mWidget = W;
}
}
Widget Alpha;
throw new WidgetException(Alpha,"Alpha is corrupt");