LECTURE 14: DESIGN FOR TESTING CSE 442 – Software Engineering
Easiest Code to Test
Easiest Code to Test
Easiest Code to Test Functional Languages
Coding in Most Languages ¨ Side-effects common & makes testing more difficult
Suggestions for Tests public class Time { private int second, minute, hour; // Assume toString() also exists public void advanceTime(int secs) { second = second + secs; minute = minute + (second / 60); second = second % 60; hour = hour + (minute / 60); minute = minute % 60; while (hour > 12) { hour = hour – 12; } } }
Suggestions for Tests public class Time { private int second, minute, hour; // Assume toString() & getters for fields also exist public void advanceTime(int secs) { second = second + secs; minute = minute + (second / 60); second = second % 60; hour = hour + (minute / 60); minute = minute % 60; while (hour > 12) { hour = hour – 12; } } }
Tests Access Inputs & Outputs public class Time { private int second, minute, hour; // Assume toString() & getters for fields also exist public void advanceTime(int secs) { second = second + secs; minute = minute + (second / 60); second = second % 60; hour = hour + (minute / 60); minute = minute % 60; while (hour > 12) { hour = hour – 12; } } }
Important Test Concept ¨ Errors likely in calculations, especially if complex ¤ Entire goal is finding & HELPING FIX bugs using tests ¤ Needs simple code that can be understood & examined ¤ Also requires easy way to set inputs & see outputs ¨ Do best to separate calculations & state changes ¤ Calculations long & complex needing lots of testing ¤ Need to be focus of testing & results important to test ¤ State changes simple & unlikely to be bug source
Important Test Concept #2 ¨ Large calculations complex & tests tough to plan How could anyone climb that?
Important Test Concept #2 ¨ Decompose calculations into smallest pieces ¤ Smaller functions easier to read, test, & debug ¤ Test each function separately to make certain they work ¤ Once pieces done, write function combining results
Decompose This Code public class Time { private int second, minute, hour; public void advanceTime(int secs) { second = second + secs; minute = minute + (second / 60); second = second % 60; hour = hour + (minute / 60); minute = minute % 60; while (hour > 12) { hour = hour – 12; } } }
Decomposed For Testing! public class Time { private int second, minute, hour; public int advanceSeconds(int secs) { second = second + secs; advanceMinutes(second / 60); second = second % 60; return second; } public int advanceMinutes(int mins) { minute = minute + mins; advanceHours(minute / 60); minute = minute % 60; return minute; } public int advanceHours(int hrs) { hour = hour + hrs ; while (hour > 12) { hour = hour – 12; } return hour; } }
Define Modules Carefully
Define Modules Carefully Write code so important calculations testable
Not Just For Assignments ¨ Also important to separate I/O from calculations ¤ I/O tough to test , since requires reviewing output ¤ But, since built into language, even harder to fix ¨ Write functions returning Strings or bytes with data ¤ Second set of functions take in data and just output it ¤ But this can create performance issues & other problems ¤ Solution relies on understanding another issue
Dependency Management ¨ Want to keep coupling between classes loose ¨ Dependency needed to preserve single responsibility public class Engine { /* Code here */ } public class Car { private Engine motor; public Car() { motor = new Engine(); } /* Even more code here */ }
Why Could drive() Fail? public class Engine { /* Code here */ } public class Car { private Engine motor; public Car() { motor = new Engine(); } public int drive(int distance) { int gasUsed = motor.move(distance); return gasUsed; } /* Even more code here */ }
Dependency Inversion
Dependency Inversion ¨ Make classes advertise their dependencies ¤ Do this by adding parameters within constructor ¤ Improves testability by allowing other options ¤ Makes implementations easier when Null is an object public class Engine { /* Code here */ } public class Car { private Engine motor; public Car() { motor = new Engine(); } /* Even more code here */ }
Dependency Inversion ¨ Make classes advertise their dependencies ¤ Do this by adding parameters within constructor ¤ Improves testability by allowing other options ¤ Makes implementations easier when Null is an object public class Engine { /* Code here */ } public class Car { private Engine motor; public Car(Engine inMotor) { motor = inMotor; } /* Even more code here */ }
Tests Dependency Inversion ¨ Dependency inversion enables using stubs & mocks ¨ Stub object fakes data to allow code to be tested ¤ Important when actual data uncontrollable or not coded: Internet traffic Database queries File I/O Multithreaded interactions
Where Stub Needed public class NuclearPowerPlant { private NuclearReactor reactor; public NuclearPowerPlant() { reactor = new NuclearReactor("SNPP"); } public boolean checkForBreach() { if (!reactor.withinLimits()) return reactor.alarmSounding(); return false; } }
Passed Test?
Adding Dependency Inversion public class NuclearPowerPlant { private NuclearReactor reactor; public NuclearPowerPlant(NuclearReactor n){ reactor = n; } public boolean checkForBreach() { if (!reactor.withinLimits()) return reactor.alarmSounding(); return false; } }
Create Interface For Stub public interface NR { public boolean withinLimits(); public boolean alarmSounding(); } public class NuclearPowerPlant { private NR reactor; public NuclearPowerPlant(NR n){ reactor = n; } public boolean checkForBreach() { if (!reactor.withinLimits()) return reactor.alarmSounding(); return false; } }
Why We Write Stubs Testing NuclearPowerPlant NOT NuclearReactor
Create Stub Class ¨ Only write code test needs – only reason stub exists ¤ Code for NuclearReactor developed separately ¨ ONLY purpose is testing other classes code ¤ Often hard-code values; make it as simple as possible ¤ Stub used to simplify; avoid using files, networks, etc. ¨ Use of duct tape & stubs similar ¤ Not for serious fix, but useful in a pinch
Writing Stub Class public interface NR { public boolean withinLimits(); public boolean alarmSounding(); } public class MeltdownStub implements NR{ public boolean withinLimits() { return false; } public boolean alarmSounding() { return true; } }
Using Stubs ¨ Can now run class in many different ways ¤ Constructor passed stub instance to test code works ¤ Actual reactor instance in production to avoid warnings ¨ If more tests desired, can create additional stubs ¤ Easy to write stub, since all of the data is hard-coded ¤ Tests must be convincing & errors not due to test code
Using Stubs ¨ Can now run class in many different ways ¤ Constructor passed stub instance to test code works ¤ Actual reactor instance in production to avoid warnings ¨ If more tests desired, can create additional stubs ¤ Easy to write stub, since all of the data is hard-coded ¤ Tests must be convincing & errors not due to test code or
Using Stub Class public class MeltdownStub implements NR { public boolean withinLimits() { return false; } public boolean alarmSounding() { return true; } } public class NuclearPowerPlant { public boolean checkForBreach() { if (!reactor.withinLimits()) return reactor.alarmSounding(); return false; }} @Test public void testTMI() { NR ms = new MeltdownStub(); NuclearPowerPlant tmi=new NuclearPowerPlant(ms); assertTrue(tmi.checkForBreach()); }
Stub Class Review ¨ Define interface so hard-to-use classes have options ¤ Interface defines minimum number of methods ¤ Update existing class to implement this interface ¨ Requires using dependency inversion in holding class ¤ Pass instance to constructor & eliminate new command ¤ Not a bad idea, in general, since also improves coupling ¨ Stub(s) used in test(s); actual class in production ¤ Easy to create many stubs, since often provide constant ¤ Testing for hard situations or while waiting on others
Tests Dependency Inversion ¨ Dependency inversion enables using stubs & mocks ¨ Mock object tracks calls to test class interactions ¤ Important when important to check call or arguments ¤ Checks class interactions and not method results ¤ Test case uses results in mock object to see if passing
Where Mock Needed public class EmergencySystem { private AlertReport reporter; public EmergencySystem(AlertReport r) { reporter = r; } public void incomingMissle() { reporter.sendEASAlert(); } }
Passed Test?
Using Mock Class public class MockAlert implements AlertReport { public boolean alertSent = false; public boolean sendEASAlert () {alertSent = true;}} public class EmergencySystem { public void incomingMissle() { reporter.sendEASAlert(); } } @Test public void testNOTHawaii() { AlertReport ma = new MockAlert(); EmergencySystem eas=new EmergencySystem(ma); eas.incomingMissle(); assertTrue(ma.alertSent); }
Recommend
More recommend