View
234
Download
0
Category
Tags:
Preview:
Citation preview
1
Object Oriented Programming
Testing with Unit Testing & the JUnit toolBasic Refactoring techniques
3
Today's Talk
• Testing software– motivation for the importance of testing
• What and how can we test?– what is testable?– is testing and debugging the same?
• The JUnit tool– a tool for Unit testing
4
Debugging and Testing
• Debugging– fo find out why a program does now work as
intended
• Testing– testing ≠ debugging
• testing revals if a problem exist• debugging is to find out exactly where and why
something goes wrong
5
found a Bug – sept. 9, 1945
6
Unit Testing
7
Real Programmers Need no Testing![citings of a lecture note on testing in SW.Eng at MIT]
• The TOP five bad excuses list
• 5) I want to get this done fast, testing is going to slow me down• 4) I started programming when I was 2. Don’t insult me by
testing my perfect code!• 3) Testing is for incompetent programmers who cannot hack.• 2) We are not Harward students, our code actually works
• 1) ”Most of the functions in Graph.java, as implemented, are one or two line functions that rely solely upon functions in HashMap or HashSet. I am assuming that these functions work perfectly, and thus there is no need to test them.” – excerpt from a students e-mail, SW. Eng. Course at MIT
8
Testing software• We need to test and verify that our
code functions correctly• Problem: most programs are very
large and complex– Difficult to trace why and where things go
wrong
• How do we test efficiently?– Where do we insert test code?– What parameters do we test on?– Do we cover all significant test cases?
9
Unit testing
• Large programs are compositions of several units (objects)
• We can reduce testing complexity by testing each component separately
• In Java, a unit test is made at the level of a class, method or interface
10
Testing Phases
• 1. Unit testing– test each class iteratively during
implementation– assert computed values are correct
according to the expected
• 2. Integration testing– merge tested units to larger
compositions and perform new tests
• 3. System testing– test complete program
11
What we can and cannot test with Unit tests
• We can test if the implementation match the specification– stable state conditions (the representation invariant)– that computed values (results) are the expected
values– That non-valid input are rejected or taken care of
• Things we cannot test– if lines of code are wrong, and which specific lines– behaviour in non-steady state (monitor variables and
instructions inside functions)• We must use a debugger for this purpose
12
What should we test and how?
• For each unit, we must define a set of test cases
• The test cases should help us– assert that the representation invariant
holds– assert that computed values match
expected values– faults detected are gracefully managed
• How should we formulate test cases?– We must test both valid and non-valid
input
• In most cases, it is practically impossible to test all cases– a common strategy: check the “boundary”
conditions and a selected set of representative cases
13
Basic structure of a Unit test
• Basic implementation of unit tests– 1. Create a test class– 2. Instantiate some test objects
– the class which we want to test
– 3. Send some test messages– inputs to the methods we want to test
– 4. Verify the expected output– Using a set of assertions
14
a tool for Unit testing
• JUnit is a tool (java framework) for unit testing• Why use a tool for unit testing?
– No test clutter the source code!• Test classes generated in separate package
– Automatic generation of• Test case skeletons
– stub methods
• Test runners (a test suite)– Wrapper code for running and hooking test methods in the unit
to test
– Simplifies incremental and iterative testing
15
The TestCase class
• A JUnit test class must extend the TestCase class
• Mandatory constructor with a string argument – a description of the specific test running
public class RationalTest extends TestCase{ public RationalTest(String testName){ super(testName); }
…}
16
Implementing test methods
Test message
Test objects
Assert correctness
Expected val.
public void testPlus() { System.out.println("plus"); Rational a = new Rational(2,3); Rational b = new Rational(2,3); /* Test two positive numbers */ Rational expResult = new Rational(4,3); Rational result = Rational.plus(a, b); assertEquals(expResult, result); assertTrue(result.repOk());
…
17
Assertions in JUnit(changed in newest release)
• In junit.framework.Assert– assertEquals(*);– assertTrue(*);– assertFalse(*);– assertNotNull(*);– assertNull(*);– assertNotSame(*);– assertSame(*);– fail(*);
18
Testing of Exceptions
• Many methods throws Exceptions
• In Junit we can test that Exceptions – are thrown when suppossed to– are thrown properly (descriptive message)
• Runtime Exception: ”you are stupid error” ???
• How do we do this?– 1. incorporate try-catch clauses– 2. using the fail() assertion in JUnit
19
Testing throwables
• The test is ”successful” if a fail ocurrs (”the bomb goes off”)
Rational a = new Rational(1,3);Rational b = new Rational();
Try{Rational.div(a,b);// Should not be reachedfail(”Div by zero should not be
allowed”);}catch(ArithmeticException ae){
// Exception was thrown so do nothing}
20
Asserting exception messages
• Force the exception message to be implemented as we require:
Rational a = new Rational(1,3); /* 1/3 */Rational b = new Rational(); /* zero */
Try{Rational.div(a,b);fail(”Div by zero should not be allowed”);
}catch(ArithmeticException ae){
assertEquals(”Division by zero”, ae.getMessage());}
21
Correctness of the test code
• The correctness of the tests are dependent on the correctness of the test methods
• Problem: How do we minimize the risqué for implementing faulty test code?
• Simple and practical guidance– 1. Run tests as soon you have a new method to test
• Do not implement too much before testing– 2. Do incremental testing frequently
• Extend the test class when new methods are completed
22
Refactoring
23
Refactoring of Code
• Refactoring is a technique to refine and restructure programs– E.g. factor (move) identical code sections into
generic classes or methods• What do we mean with a ”generic class”?
– classes collecting general functionality, which we can reuse (with or without adaptation)• code used in multiple sections of a program • a general framework useable for many programs
– optimally, they can be reused without need of modifying the code
24
Refactoring Strategies
• Three basic refactoring strategies:– Refactoring of methods
• at level of single program (code usually specific for a certain program)
– Refactoring by inheritance• at level of single program and framework (code
generalizable for a specific purpose)
– Refactoring by delegation• at level of single program and framework (codes
that are often most generalizable)
25
Refactoring of methodsClass ComputeThings {
public void computeAnything(){anything();compute1();compute2();compute3();
}public void computeSomething(){
something();compute1();compute2();compute3();
}}
These parts are similar....
26
Refactoring of methodsclass computeThings {
public void computeMany1(){anything();computeAlot();
}public void computeMany2(){
something();computeAlot();
}public void computeAlot(){
compute1();compute2();compute3();
}}
27
Refactoring of methods
• What is the purpose of refactoring methods?– reduces lines of code
(redundancy)– changes can be done
more safely • we only have to change in
one location of the program
– tend to make programs ”easier” to read and understand
28
Refactoring by Inheritance
Class computeStuff1{
superCompute1(){
compute1();
compute2();
compute3();
}
....
}
Class computeStuff2{
somethingElse(){
compute1();
compute2();
compute3();
}
...
}
Both classes implement
identical code sections
29
Refactoring by Inheritanceclass ComputeStuff1 extends Common{
...superCompute1(){
super.computeAlot();}....
}
class ComputeStuff2 extends Common{
...somethingElse(){
super.computeAlot();}...
}
class Common{...computeAlot(){
compute1();compute2();compute3();
}...
}
30
Refactoring by Delegation
• Instead of inheriting factorized code– let the the common code be encapsulated in a
separate class
• We can chose to make functions accessible– via an object reference
– by declaring the factorized methods as static • as for example the arithmetics in Rational
• Example: the Sorting and Helper classes in the search engine
31
Refactoring by Delegation(access by reference)
class ComputeStuff1{
Compute compVar;
superCompute1(){compVar.computeAlot();
}....
}
class ComputeStuff2{...somethingElse(){
compVar.computeAlot();}...
}
class Compute{
...
computeAlot(){
compute1();
compute2();
compute3();
}
...
}
32
The End...
Recommended