LECTURE 14: DESIGN FOR TESTING
CSE 442 – Software Engineering
LECTURE 14: DESIGN FOR TESTING CSE 442 Software Engineering - - PowerPoint PPT Presentation
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
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
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; } }
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
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
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(); } }
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); }
Why Mock Classes Help
¨ Define interface so hard-to-test interactions testable
¤ 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
¨ Mock(s) used in test(s); actual class in production
¤ May not need many mocks, since looking at interaction ¤ Combines well with stubs to force events to occur
Try On Your Own
¨ Design TESTABLE function(s) to solve this problem:
Each day, the Haas Avocado Board posts average price per avocado & total avocados sold for each of their US markets. The data are posted as a CSV file and always at the same URL. Your code updates a webpage to show the average price per avocado for the US, total US sales, and the price & sales data for the 4 NY markets (Buffalo, NYC, Albany, Syracuse) for each day in the file
My Solution
get_data(url)→ 2d array with file's data get_us_sold(array, day)→ #sold that day get_us_income(array, day)→ $sold that day get_us_avg_price(array, day)→ (obvs) get_market_sold(array, name, day)→ format_us_results(day, sold, price) → return HTML table with the us results for that day