CS 635 Advanced Object-Oriented Design & Programming Spring Semester, 2001 Patterns, Refactoring & Testing |
||
---|---|---|
© 2001, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 03-Apr-01 |
Patterns and Coding
Ralph Johnson recommends using patterns with you know you really need it
You have a program in development
You discover that using a pattern would improve the existing code
But adding the pattern requires modifying existing code!
Refactoring
Refactoring is the modifying existing code without adding functionality
Changing existing code is dangerous
Testing
Johnson's Law
If it is not tested it does not work
Types of tests
Why Unit Testing
If it is not tested it does not work
The more time between coding and testing
Who Writes Unit Tests?
Programmers
Waiting for QA or testing wastes time
Testing First
First write the tests
Then write the code to be tested
Writing tests first:
Why Automated Tests
Why bother with automated test?
JUnit
Framework for unit testing Java code
Available at: http://www.junit.org/
Already installed in JDK 1.2 & 1.3 on rohan and moria
Ports of JUnit are available in
C++ |
Delphi |
Eiffel |
Forte
4GL
|
Objective-C |
Perl |
PowerBuilder |
Python |
Ruby |
Smalltalk |
Visual
Basic
|
.net |
Using JUnit
Example
Goal: Implement a Stack containing integers.
Tests:
package example; Import junit.framework.TestCase; public class StackTest extends TestCase { //required constructor public StackTest(String name) { super(name); } public void testDefaultConstructor() { Stack test = new Stack(); assert( test.isEmpty() ); } public void testSizeConstructor() { Stack test = new Stack(5); assert( test.isEmpty() ); } }
First part of the Stack
package example; public class Stack { int[] elements; int topElement = -1; public Stack() { this(10); } public Stack(int size) { elements = new int[size]; } public boolean isEmpty() { return topElement == -1; } }
Running JUnit
JUnit has three interfaces
Starting TestRunner
Make sure your classpath includes the code to tested
On Rohan use:
java junit.ui.LoadingTestRunner You get a window like:Enter the full name of the test class
Click on the Run button
If there are errors/failures select one and click on Show You will see a stack trace of the error With LoadingTestRunner you can recompile the Stack & StackTest classes without exiting LoadingTestRunner
Testing the Tests
If can be useful to modify the code to break the tests
package example; public class Stack { int[] elements; int topElement = -1; etc. public boolean isEmpty() { return topElement == 1; } }
One company had an automatic build and test cycle that ran at night. The daily build was created and all the tests were run at night. The test results were available first thing in the morning. One night the build process crashed, so the daily build was not made. Hence there was no code to test. Still 70% of the tests passed. If they had tested their tests, they would have discovered immediately that their tests were broken.
Test Fixtures
Before each test setUp() is run
After each test tearDown() is run
package example; import junit.framework.TestCase; public class StackTest extends TestCase { Stack test; public StackTest(String name) { super(name); } public void setUp() { test = new Stack(5); for (int k = 1; k <=5;k++) test.push( k); } public void testPushPop() { for (int k = 5; k >= 1; k--) assert( "Popping element " + k, test.pop() == k); } }
Suites – Multiple Test Classes
Multiple test classes can be run at the same time
Running AllTests in TestRunner runs the test in
package example; import junit.framework.TestSuite; public class AllTests { static public TestSuite suite() { TestSuite suite= new TestSuite(); try { suite.addTest(new TestSuite(StackTest.class)); suite.addTest(new TestSuite(QueueTest.class)); } catch (Exception e) { } return suite; } }
Using Main We can use main to run the test via textui.TestRunner
The command:
java example.AllTestswill run all the tests in StackTest & QueueTest
package example; import junit.framework.TestSuite; import junit.textui.TestRunner; public class AllTests { static public void main(String[] args) { TestRunner.main(args); } static public TestSuite suite() { same as last page } }
What to Test
Fowler on Testing [1]
"It is better to write and run incomplete tests than not to run complete tests"
"Don't let the fear that testing can't catch all bugs stop you from writing the tests that will catch most bugs"
"Trying to write too many tests usually leads to not writing enough"
"Run your tests frequently"
"When you get a bug report, start by writing a unit test that exposes the bug"
Think of the boundary conditions and concentrate your tests there
Programming Errors
Programmers tend to make the same errors many times
Keep a list or catalog of your errors
A Short Catalog of Test Ideas
Tests develop catalogs of commonly found errors in programs
Since errors are often repeated, this helps testers find common errors
As programmers such a catalog:
Any Object
Test nil(null) references and pointers to objects
In Java/Smalltalk
String firstName = nullSmalltalk
| firstName | firstName := nil.
Strings
Test the empty string
Does the code to the correct thing when string variables/parameters are the empty string
In Java/Smalltalk an empty string is not the same as a null(nil) reference to a string
Java
String firstName = ""; String secondName = new String();Smalltalk
| firstName secondName | firstName := ''. secondName := String new
Numbers
Test the code using:
int planetIndex; //Represents the I'th planet from the Sun
0 |
Below
the smallest
|
1 |
Smallest |
9 |
Largest
(Pluto is still considered a planet)
|
10 |
Above
the largest
|
Collections
Test the code using:
Linked Structures (trees, graphs, etc.)
Test the code using:
Equality Testing of Objects
Objects have two meanings of equality
Test the code with objects equal but not identical
Don't Forget
Back To Refactoring
Refactoring requires:
Code Smells
If it stinks, change it
-- Grandma Beck on child-rearing
Some Smells
Duplicate
Code
|
Long
Method
|
Large
Class
|
Long
Parameter List
|
Divergent
Change
|
Shotgun
Surgery
|
Feature
Envy
|
Data
Clumps
|
Primitive
Obsession
|
Switch
Statements
|
Parallel
Inheritance Hierarchies
|
Lazy
Class
|
Speculative
Generality
|
Temporary
Field
|
Message
Chains
|
Middle
Man
|
Inappropriate
Intimacy
|
Alternative
Classes with Different Interfaces
|
Incomplete
Library Class
|
Data
Class
|
Refused
Bequest
|
Comments |
Most Common Refactoring: Extract Method[2]
You have a code fragment that can be grouped together.
Turn the fragment into a method whose name explains the purpose of the method
Motivation
Short methods:
Mechanics
Mechanics - Continued
Example[3]
No Local Variables
Note I will use Fowler's convention of starting instance variables with "_" even though one can not do this is Squeak.
void printOwing() { //print banner System.out.println( "*************************"); System.out.println( "****Customer Owes*********"); System.out.println( "*************************");
Iterator orders = _orders.iterator(); double outstanding = 0.0; // Calculate outstanding while (orders.hasNext() ) { Order each = (Order) orders.next(); outstanding = outstanding + each.getAmount(); } //Print Details System.out.println("name: " + _name); System.out.println("amout: " + outstanding); }Extracting the banner code we get:
void printOwing() { printBanner();
Iterator orders = _orders.iterator(); double outstanding = 0.0;
// Calculate outstanding while (orders.hasNext() ) { Order each = (Order) orders.next(); outstanding = outstanding + each.getAmount(); } //Print Details System.out.println("name: " + _name); System.out.println("amout: " + outstanding); }
void printBanner() { System.out.println( "*************************"); System.out.println( "****Customer Owes*********"); System.out.println( "*************************"); }
Examples: Using Local Variables
We can extract printDetails() to get
void printOwing() { printBanner(); Iterator orders = _orders.iterator(); double outstanding = 0.0; // Calculate outstanding while (orders.hasNext() ) { Order each = (Order) orders.next(); outstanding = outstanding + each.getAmount(); } printDetails(outstanding); }
void printDetails( double amountOwed) { System.out.println("name: " + _name); System.out.println("amout: " + outstanding); }
Then we can extract outstanding to get:
void printOwing() { printBanner(); double outstanding = outStanding(); printDetails(outstanding); }
double outStanding() { Iterator orders = _orders.iterator(); double outstanding = 0.0; while (orders.hasNext() ) { Order each = (Order) orders.next(); outstanding = outstanding + each.getAmount(); } return outstanding; }
Using Replace Parameter with Method [4] we can change this to:
void printOwing() { printBanner(); printDetails(); }
void printDetails() { System.out.println("name: " + _name); System.out.println("amout: " + outstanding()); }
Reducing Coupling
The printing is still coupled to System.out
Using Add Parameter we get:
void printOwing(PrintString out) { printBanner(out); printDetails(out); }
where
void printDetails(PrintString out) { out.println("name: " + _name); out.println("amout: " + outstanding()); }
void printBanner(PrintString out) { out.println( "*************************"); out.println( "****Customer Owes*********"); out.println( "*************************"); }
If you really do print to the screen a lot, you might add:
void printOwing() { printOwing(System.out); }
[1] Fowler Chapter 4, pp. 89-102
[2] Refactoring Text, pp. 110-116
[3] Example code is Squeak version of Fowler's Java example
[4] Fowler pp. 292-294
Copyright ©, All rights reserved.
2001 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.
Previous    visitors since 03-Apr-01    Next