谷歌 scott-lessons learned in testability
TRANSCRIPT
![Page 1: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/1.jpg)
Lessons Learned in Testability
Scott McMaster
Kirkland, Washington USA
![Page 2: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/2.jpg)
About Me
• Software Design Engineer @ Google.
– Building high-traffic web frontends and services in Java.
– AdWords, Google Code
• Ph.D. in Computer Science, U. of Maryland.
• Formerly of Amazon.com (2 years), Lockheed Martin (2 years), Microsoft
(7 years), and some small startups.
• Frequent adjunct professor @ Seattle University, teaching software
design, architecture, and OO programming.
• Author of technical blog at http://www.scottmcmaster365.com.
![Page 3: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/3.jpg)
Testing and Me
• Doing automated testing since 1995.
• Ph.D. work in test coverage and test suite maintenance.
• Champion of numerous unit, system, and performance
testing tools and techniques.
• Co-founder of WebTestingExplorer open-source automated
web testing framework (www.webtestingexplorer.org).
![Page 4: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/4.jpg)
Agenda
• What is Testability?
• Testability Sins
– Statics and singletons
– Mixing Business and Presentation Logic
– Breaking the Law of Demeter
• Testability Solutions
– Removing singletons.
– Asking for Dependencies
– Dependency Injection
– Mocks and Fakes
– Refactoring to UI Patterns
![Page 5: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/5.jpg)
Testability: Formal Definition
• Wikipedia: “the degree to which a software
artifact (i.e. a software system, software
module, requirements- or design document)
supports testing in a given test context.”
http://en.wikipedia.org/wiki/Software_testability
![Page 6: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/6.jpg)
Some Aspects of Testability
• Controllable: We can put the software in a state to begin
testing.
• Observable: We can see things going right (or wrong).
• Isolatable: We can test classes/modules/systems apart from
others.
• Automatable: We can write or generate automated tests.
– Requires each of the previous three to some degree.
![Page 7: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/7.jpg)
Testability: More Practical Definition
• Testability is a function of your testing goals.
• Our primary goal is to write or generate automated tests.
• Therefore, testability is the ease with which we can write:
– Unit tests
– System tests
– End-to-end tests
![Page 8: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/8.jpg)
Testers and Testability• At Google, test engineers:
– Help ensure that developers build testable
software.
– Provide guidance to developers on best practices
for unit and end-to-end testing.
– May participate in refactoring production code for
testability.
![Page 9: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/9.jpg)
Example: Weather App
![Page 10: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/10.jpg)
Weather App Architecture
Rich Browser UI (GWT)
Frontend Server (GWT RPC servlet)
Remote Web Service (XML-over-
HTTP)User Database
![Page 11: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/11.jpg)
Original Weather App Design
![Page 12: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/12.jpg)
Testability Problems?
• Can’t test without calling the cloud service.
– Slow to run, unstable.
• Can’t test any client-side components without
loading a browser or browser emulator.
– Slow to develop, slow to run, perhaps unstable.
![Page 13: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/13.jpg)
Mission #1: Unit Tests for WeatherServiceImpl• Problem: Uses static singleton reference to
GlobalWeatherService, can’t be tested in isolation.
• Solution:
– Eliminate the static singleton.
– Pass a mock or stub to the WeatherServiceImpl
constructor at test-time.
![Page 14: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/14.jpg)
WeatherServiceImpl: Beforeprivate private private private static static static static GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService service = new service = new service = new service = new GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService();();();();
public public public public List<String> List<String> List<String> List<String> getCitiesForCountrygetCitiesForCountrygetCitiesForCountrygetCitiesForCountry(String (String (String (String countryNamecountryNamecountryNamecountryName) {) {) {) { try try try try {{{{ if if if if ((((countryNamecountryNamecountryNamecountryName == null || == null || == null || == null || countryName.isEmptycountryName.isEmptycountryName.isEmptycountryName.isEmpty()) {()) {()) {()) { return return return return new new new new ArrayListArrayListArrayListArrayList<String>();<String>();<String>();<String>(); } return return return return service.getCitiesForCountryservice.getCitiesForCountryservice.getCitiesForCountryservice.getCitiesForCountry((((countryNamecountryNamecountryNamecountryName))));;;; } catch (Exception e) {catch (Exception e) {catch (Exception e) {catch (Exception e) { throw throw throw throw new new new new RuntimeExceptionRuntimeExceptionRuntimeExceptionRuntimeException(e);(e);(e);(e); }}
What if we try to test this in its current form?1. GlobalWeatherService gets loaded at classload-time.
1. This itself could be slow or unstable depending on the implementation.2. When we call getCititesForCountry(“China”), a remote web service call gets made.3. This remote web service call may:
1. Fail.2. Be really slow.3. Not return predictable results.� Any of these things can make our test “flaky”.
![Page 15: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/15.jpg)
Proposed Solution
• First we need to get rid of the static singleton.
• Then we need something that:
– Behaves like GlobalWebService.
– Is fast and predictable.
– Can be inserted into WeatherServiceImpl at test-
time.
![Page 16: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/16.jpg)
A Word About Static Methods and Singletons• Never use them!
• They are basically global variables (and we’ve all
been taught to avoid those).
• They are hard to replace with alternative
implementations, mocks, and stubs/fakes.
– They make automated unit testing extremely difficult.
![Page 17: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/17.jpg)
Scott’s Rules About Static Methods and Singletons
1. Avoid static methods.
2. For classes that are logically “singleton”, make
them non-singleton instances and manage
them in a dependency injection container (more
on this shortly).
![Page 18: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/18.jpg)
Singleton Removal
public class public class public class public class WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl extends extends extends extends RemoteServiceServletRemoteServiceServletRemoteServiceServletRemoteServiceServlet implements implements implements implements WeatherService {
private private private private final final final final GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService service; service; service; service;
public public public public WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl((((GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService service) { service) { service) { service) { this.servicethis.servicethis.servicethis.service = service;= service;= service;= service; } ...
• Also, make GlobalWeatherService into an interface.• Now we can pass in a special implementation for unit
testing.• But we have a big problem…
![Page 19: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/19.jpg)
We’ve Broken Our Service!
• The servlet container does not understand
how to create WeatherServiceImpl anymore.
– Its constructor takes a funny parameter.
• The solution?
![Page 20: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/20.jpg)
Dependency Injection• Can be a little complicated, but here is what you
need to know here:
• Accept your dependencies, don’t ask for them.
– Then your dependencies can be replaced (generally, with
simpler implementations) at test time.
– In production, your dependencies get inserted by a
dependency injection container.
• In Java, this is usually Spring or Google Guice.
![Page 21: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/21.jpg)
Dependency Injection with Google Guice
• Google Guice: A Dependency Injection framework.
• When properly set up, it will create your objects and pass
them to the appropriate constructors at runtime, freeing you
up to do other things with the constructors at test-time.
• Setting up Guice is outside the scope of this talk.
– This will get you started: http://code.google.com/p/google-
guice/wiki/Servlets
![Page 22: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/22.jpg)
Fixing WeatherServiceImpl (1)
•Configure our servlet to use Guice and tell it about our objects:
•When someone asks for a “GlobalWeatherService”, Guice will
give it an instance of GlobalWeatherServiceImpl.
public class public class public class public class WeatherAppModuleWeatherAppModuleWeatherAppModuleWeatherAppModule extends extends extends extends AbstractModuleAbstractModuleAbstractModuleAbstractModule { { { { @Override protected protected protected protected void configure() {void configure() {void configure() {void configure() { bind(WeatherServiceImpl.classclassclassclass);););); bind(GlobalWeatherService.classclassclassclass).to().to().to().to(GlobalWeatherServiceImpl.classGlobalWeatherServiceImpl.classGlobalWeatherServiceImpl.classGlobalWeatherServiceImpl.class);););); }}
![Page 23: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/23.jpg)
Fixing WeatherServiceImpl (2)
• At runtime, Guice will create our servlet and the object(s) it
needs:
@Singletonpublic class public class public class public class WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl extends extends extends extends RemoteServiceServletRemoteServiceServletRemoteServiceServletRemoteServiceServlet implements implements implements implements WeatherService {
private private private private final final final final GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService service; service; service; service;
@Inject public public public public WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl((((GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService service) { service) { service) { service) { this.servicethis.servicethis.servicethis.service = service;= service;= service;= service; } ...
The “@Inject” constructor parameters is how we ask Guice for instances.
![Page 24: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/24.jpg)
After Testability Refactoring #1
![Page 25: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/25.jpg)
Finally! We Can Test!• But how?
• We want to test WeatherServiceImpl in
isolation.
⇒For GlobalWeatherService, we only care about
how it interacts with WeatherServiceImpl.
⇒To create the proper interactions, a mock object mock object mock object mock object
is ideal
![Page 26: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/26.jpg)
Mock Object Testing
• Mock objects simulate real objects in ways specified by the tester.
• The mock object framework verifies these interactions occur as expected.
– A useful consequence of this: If appropriate, you can verify that an application
is not making more remote calls than expected.
– Another useful consequence: Mocks make it easy to test exception handling.
• Common mocking frameworks (for Java):
– Mockito
– EasyMock
• I will use this.
![Page 27: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/27.jpg)
Using Mock Objects1. Create a mock object.
2. Set up expectations:
1. How we expect the class-under-test to call it.
2. What we want it to return.
3. “Replay” the mock.
4. Invoke the class-under-test.
5. “Verify” the mock interactions were as-expected.
![Page 28: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/28.jpg)
Testing with a Mock Objectprivate private private private GlobalWeatherServiceGlobalWeatherServiceGlobalWeatherServiceGlobalWeatherService globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService;;;;private private private private WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl weatherServiceweatherServiceweatherServiceweatherService;;;;
@@@@BeforeBeforeBeforeBeforepublic public public public void void void void setUpsetUpsetUpsetUp() {() {() {() { globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService = = = = EasyMock.createMockEasyMock.createMockEasyMock.createMockEasyMock.createMock((((GlobalWeatherService.classGlobalWeatherService.classGlobalWeatherService.classGlobalWeatherService.class);););); weatherServiceweatherServiceweatherServiceweatherService = new = new = new = new WeatherServiceImplWeatherServiceImplWeatherServiceImplWeatherServiceImpl((((globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService););););}}}}
@@@@TestTestTestTestpublic public public public void void void void testGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmpty() throws Exception {() throws Exception {() throws Exception {() throws Exception { EasyMock.expectEasyMock.expectEasyMock.expectEasyMock.expect((((globalWeatherService.getCitiesForCountryglobalWeatherService.getCitiesForCountryglobalWeatherService.getCitiesForCountryglobalWeatherService.getCitiesForCountry("china"))("china"))("china"))("china")) . . . .andReturnandReturnandReturnandReturn((((ImmutableList.ofImmutableList.ofImmutableList.ofImmutableList.of("("("("beijingbeijingbeijingbeijing", "shanghai"));", "shanghai"));", "shanghai"));", "shanghai")); EasyMock.replayEasyMock.replayEasyMock.replayEasyMock.replay((((globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService);););); List List List List<String> cities = <String> cities = <String> cities = <String> cities = weatherService.getCitiesForCountryweatherService.getCitiesForCountryweatherService.getCitiesForCountryweatherService.getCitiesForCountry("china");("china");("china");("china"); assertEqualsassertEqualsassertEqualsassertEquals(2, (2, (2, (2, cities.sizecities.sizecities.sizecities.size());());());()); assertTrueassertTrueassertTrueassertTrue((((cities.containscities.containscities.containscities.contains("("("("beijingbeijingbeijingbeijing"));"));"));")); assertTrueassertTrueassertTrueassertTrue((((cities.containscities.containscities.containscities.contains("shanghai"));("shanghai"));("shanghai"));("shanghai")); EasyMock.verifyEasyMock.verifyEasyMock.verifyEasyMock.verify((((globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService););););}}}}
Observe:• How we take advantage of the new WeatherServiceImpl constructor.• How we use the mock GlobalWeatherService.
![Page 29: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/29.jpg)
Mission #2: Unit Tests for GlobalWeatherService
• Problem: Talks to external web service, does non-trivial XML
processing that we want to test.
• Solution:
– Split the remote call from the XML processing.
– Wrap external web service in an object with a known interface.
– Pass an instance to the GlobalWeatherServiceImpl constructor.
– Use dependency injection to create the real object at runtime, use a
fake at test-time.
![Page 30: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/30.jpg)
After Testability Refactoring #2
![Page 31: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/31.jpg)
Fakes vs. Mocks
• Mocks
– Verifies behavior (expected calls).
– Implementation usually generated by a mock object framework.
– Often only usable in a single test case.
– Often fragile as the implementation changes.
• Fakes
– Contains a simplified implementation of the real thing (perhaps using static data, an in-memory
database, etc.).
– Implementation usually generated by hand.
– Often reusable across test cases and test suites if carefully designed.
• Mocks and Fakes
– Either can often be used in a given situation.
– But some situations lend themselves to one more than the other.
![Page 32: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/32.jpg)
Use a Fake, or Use a Mock?• Problem: Setting up a mock object for
GlobalWeatherDataAccess that returns static XML is
possible, but ugly (and perhaps not very reusable).
• Idea: Create a fakefakefakefake implementation of
GlobalWeatherDataAccess.
– We can give the fake object the capability to return
different XML in different test circumstances.
![Page 33: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/33.jpg)
Implementing the Fake Objectpublic class public class public class public class FakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccess implements implements implements implements GlobalWeatherDataAccessGlobalWeatherDataAccessGlobalWeatherDataAccessGlobalWeatherDataAccess { { { {
/ / / // Try http:/// Try http:/// Try http:/// Try http://www.htmlescape.netwww.htmlescape.netwww.htmlescape.netwww.htmlescape.net////javaescape_tool.htmljavaescape_tool.htmljavaescape_tool.htmljavaescape_tool.html to generate these. to generate these. to generate these. to generate these. private private private private static final String CHINA_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String CHINA_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String CHINA_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String CHINA_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string xmlnsxmlnsxmlnsxmlns=\"http://=\"http://=\"http://=\"http://www.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NET\"><\"><\"><\"><NewDataSetNewDataSetNewDataSetNewDataSet>\n <Table>\n <Country>China</Country>\n <City>Beijing</City>\n </Table>\n <Table>\n <Country>China</Country>\n <City>Shanghai</City>\n </Table>\n</>\n <Table>\n <Country>China</Country>\n <City>Beijing</City>\n </Table>\n <Table>\n <Country>China</Country>\n <City>Shanghai</City>\n </Table>\n</>\n <Table>\n <Country>China</Country>\n <City>Beijing</City>\n </Table>\n <Table>\n <Country>China</Country>\n <City>Shanghai</City>\n </Table>\n</>\n <Table>\n <Country>China</Country>\n <City>Beijing</City>\n </Table>\n <Table>\n <Country>China</Country>\n <City>Shanghai</City>\n </Table>\n</NewDataSetNewDataSetNewDataSetNewDataSet></string>";></string>";></string>";></string>"; private private private private static final String BEIJING_WEATHER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String BEIJING_WEATHER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String BEIJING_WEATHER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String BEIJING_WEATHER = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string xmlnsxmlnsxmlnsxmlns=\"http://=\"http://=\"http://=\"http://www.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NET\">&\">&\">&\"><ltltlt;?xml version=\"1.0\" encoding=\"utf-16\"?&;?xml version=\"1.0\" encoding=\"utf-16\"?&;?xml version=\"1.0\" encoding=\"utf-16\"?&;?xml version=\"1.0\" encoding=\"utf-16\"?>gtgtgt;\;\;\;\n<CurrentWeather>n<CurrentWeather>n<CurrentWeather>n<CurrentWeather>\n &;\n &;\n &;\n <Location>Beijinglt;Location>Beijinglt;Location>Beijinglt;Location>Beijing, China (ZBAA) 39-56N 116-17E 55M</, China (ZBAA) 39-56N 116-17E 55M</, China (ZBAA) 39-56N 116-17E 55M</, China (ZBAA) 39-56N 116-17E 55M</Location>Location>Location>Location>\n &;\n &;\n &;\n <Time>Octlt;Time>Octlt;Time>Octlt;Time>Oct 27, 2012 - 04:00 PM EDT / 2012.10.27 2000 27, 2012 - 04:00 PM EDT / 2012.10.27 2000 27, 2012 - 04:00 PM EDT / 2012.10.27 2000 27, 2012 - 04:00 PM EDT / 2012.10.27 2000 UTC<UTC<UTC<UTC</;/;/;/Time>Time>Time>Time>\n &;\n &;\n &;\n <Wind>lt;Wind>lt;Wind>lt;Wind> from the N (010 degrees) at 9 MPH (8 KT):0</; from the N (010 degrees) at 9 MPH (8 KT):0</; from the N (010 degrees) at 9 MPH (8 KT):0</; from the N (010 degrees) at 9 MPH (8 KT):0</Wind>Wind>Wind>Wind>\n ;\n ;\n ;\n &&&<Visibility>lt;Visibility>lt;Visibility>lt;Visibility> greater than 7 mile(s):0</; greater than 7 mile(s):0</; greater than 7 mile(s):0</; greater than 7 mile(s):0</Visibility>Visibility>Visibility>Visibility>\n &;\n &;\n &;\n <Temperature>lt;Temperature>lt;Temperature>lt;Temperature> 39 F (4 C)&; 39 F (4 C)&; 39 F (4 C)&; 39 F (4 C)<ltltlt;/;/;/;/Temperature>Temperature>Temperature>Temperature>\n &;\n &;\n &;\n <DewPoint>lt;DewPoint>lt;DewPoint>lt;DewPoint> 28 F (-2 C)&; 28 F (-2 C)&; 28 F (-2 C)&; 28 F (-2 C)<ltltlt;/;/;/;/DewPoint>DewPoint>DewPoint>DewPoint>\n &;\n &;\n &;\n <RelativeHumidity>lt;RelativeHumidity>lt;RelativeHumidity>lt;RelativeHumidity> 64%&; 64%&; 64%&; 64%<ltltlt;/;/;/;/RelativeHumidity>RelativeHumidity>RelativeHumidity>RelativeHumidity>\n &;\n &;\n &;\n <Pressure>lt;Pressure>lt;Pressure>lt;Pressure> 30.30 in. Hg (1026 ; 30.30 in. Hg (1026 ; 30.30 in. Hg (1026 ; 30.30 in. Hg (1026 hPahPahPahPa)&)&)&)<ltltlt;/;/;/;/Pressure>Pressure>Pressure>Pressure>\n &;\n &;\n &;\n <Status>Success<lt;Status>Success<lt;Status>Success<lt;Status>Success</;/;/;/Status>Status>Status>Status>\;\;\;\n<n<n<n</;/;/;/CurrentWeather>CurrentWeather>CurrentWeather>CurrentWeather></string>";;</string>";;</string>";;</string>"; private private private private static final String NO_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String NO_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String NO_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string static final String NO_CITIES = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<string xmlnsxmlnsxmlnsxmlns=\"http://=\"http://=\"http://=\"http://www.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NETwww.webserviceX.NET\"><\"><\"><\"><NewDataSetNewDataSetNewDataSetNewDataSet /></string>"; /></string>"; /></string>"; /></string>";
@ @ @ @OverrideOverrideOverrideOverride public public public public String String String String getCitiesForCountryXmlgetCitiesForCountryXmlgetCitiesForCountryXmlgetCitiesForCountryXml(String (String (String (String countryNamecountryNamecountryNamecountryName) throws Exception {) throws Exception {) throws Exception {) throws Exception { if if if if ("("("("china".equalschina".equalschina".equalschina".equals((((countryName.toLowerCasecountryName.toLowerCasecountryName.toLowerCasecountryName.toLowerCase())) {())) {())) {())) { return return return return CHINA_CITIES;CHINA_CITIES;CHINA_CITIES;CHINA_CITIES; } } } } return return return return NO_CITIES;NO_CITIES;NO_CITIES;NO_CITIES; } } } }
@ @ @ @OverrideOverrideOverrideOverride public public public public String String String String getWeatherForCityXmlgetWeatherForCityXmlgetWeatherForCityXmlgetWeatherForCityXml(String (String (String (String countryNamecountryNamecountryNamecountryName, String , String , String , String cityNamecityNamecityNamecityName)))) throws throws throws throws Exception {Exception {Exception {Exception { return return return return BEIJING_WEATHER;BEIJING_WEATHER;BEIJING_WEATHER;BEIJING_WEATHER; } } } }}}}}
![Page 34: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/34.jpg)
Testing with a Fake Objectprivate private private private GlobalWeatherServiceImplGlobalWeatherServiceImplGlobalWeatherServiceImplGlobalWeatherServiceImpl globalWeatherServiceglobalWeatherServiceglobalWeatherServiceglobalWeatherService;;;;private private private private FakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccess dataAccessdataAccessdataAccessdataAccess;;;;
@Beforepublic public public public void void void void setUpsetUpsetUpsetUp() {() {() {() { dataAccess = new new new new FakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccessFakeGlobalWeatherDataAccess();();();(); globalWeatherService = new new new new GlobalWeatherServiceImplGlobalWeatherServiceImplGlobalWeatherServiceImplGlobalWeatherServiceImpl((((dataAccessdataAccessdataAccessdataAccess););););}
@Testpublic public public public void void void void testGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmptytestGetCitiesForCountry_nonEmpty() throws Exception {() throws Exception {() throws Exception {() throws Exception { List<String> cities = globalWeatherService.getCitiesForCountry("china"); assertEquals(2, cities.size()); assertTrue(cities.contains("beijing")); assertTrue(cities.contains("shanghai"));}
@Testpublic public public public void void void void testGetCitiesForCountry_emptytestGetCitiesForCountry_emptytestGetCitiesForCountry_emptytestGetCitiesForCountry_empty() throws Exception {() throws Exception {() throws Exception {() throws Exception { List<String> cities = globalWeatherService.getCitiesForCountry("nowhere"); assertTrue(cities.isEmpty());}
The fake keeps the tests short, simple, and to-the-point!
![Page 35: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/35.jpg)
Mission #3: Unit Tests for WeatherHome
• Problem: UI and business logic / service calls all mixed
together.
– The view layer is difficult and slow to instantiate at unit test-time.
– But we need to unit test the business logic.
• Solution:
– Refactor to patterns -- Model-View-Presenter (MVP).
– Write unit tests for the Presenter using a mock or stub View.
![Page 36: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/36.jpg)
Mixing Business and Presentation@UiHandler("login")void void void void onLoginonLoginonLoginonLogin((((ClickEventClickEventClickEventClickEvent e) { e) { e) { e) { weatherService.getWeatherForUser(userName.getText(), new new new new AsyncCallbackAsyncCallbackAsyncCallbackAsyncCallback<Weather>() {<Weather>() {<Weather>() {<Weather>() {
@Override public public public public void void void void onFailureonFailureonFailureonFailure((((ThrowableThrowableThrowableThrowable caught) caught) caught) caught) {{{{ Window.alert("oops");
}
@Override public public public public voidvoidvoidvoid onSuccessonSuccessonSuccessonSuccess((((WeatherWeatherWeatherWeather weatherweatherweatherweather) {) {) {) { if if if if (weather != null) {(weather != null) {(weather != null) {(weather != null) { fillWeather(weather); unknownUser.setVisible(false);false);false);false); } elseelseelseelse { { { { unknownUser.setVisible(true);true);true);true); } } });}
How NOT to write a UI event handler for maximum testability:• Have tight coupling between the UI event, processing a remote service call, and
updating the UI.
![Page 37: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/37.jpg)
Model-View-Presenter (MVP)
• UI pattern that separates business and
presentation logic.
• Makes the View easier to modify.
• Makes the business logic easier to test by
isolating it in the Presenter.
![Page 38: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/38.jpg)
Model-View-Presenter Overview
![Page 39: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/39.jpg)
Model-View-Presenter Responsibilities
• Presenter uses the View interface to
manipulate the UI.
• View delegates UI event handling back to the
Presenter via an event bus or an interface.
• Presenter handles all service calls and
reading/updating of the Model.
![Page 40: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/40.jpg)
Passive View MVP• A particular style of MVP where the View is
completely passive, only defining and layout and
exposing its widgets for manipulation by the
controller.
– In practice, you sometimes don’t quite get here, but this is
the goal.
• Especially if you use this style, you can skip testing
the View altogether.
![Page 41: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/41.jpg)
After Testability Refactoring #3
![Page 42: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/42.jpg)
Presenter Unit Test Using EasyMock
@Testpublic public public public void void void void testOnLogin_unknownUsertestOnLogin_unknownUsertestOnLogin_unknownUsertestOnLogin_unknownUser() {() {() {() { weatherService.expectGetWeatherForUser("unknown"); EasyMock.expect(weatherView.getUserName()).andReturn("unknown"); weatherView.setUnknownUserVisible(true);true);true);true); EasyMock.expectLastCall(); weatherView.setEventHandler(EasyMock.anyObject(WeatherViewEventHandler.classclassclassclass));));));)); EasyMock.expectLastCall();
EasyMock.replay(weatherView);
WeatherHomePresenter presenter = new new new new WeatherHomePresenterWeatherHomePresenterWeatherHomePresenterWeatherHomePresenter((((weatherServiceweatherServiceweatherServiceweatherService, , , , weatherViewweatherViewweatherViewweatherView);););); presenter.onLogin();
EasyMock.verify(weatherView); weatherService.verify();}
This test uses a manually created mock to make handling the async callback easier.
![Page 43: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/43.jpg)
Question
• Why does the View make the Presenter do
this:weatherView.setUnknownUserVisible(true);;;;
• Instead of this:weatherView.getUnknownUser().setVisible(true)
![Page 44: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/44.jpg)
Answer
weatherView.getUnknownUser().setVisible(true)
• Is hard to test because it is hard to mock:
–To mock this, we would have to mock not only the
WeatherView, but also the UnknownUser Label inside
of it.
• The above code is “talking to strangers”.
![Page 45: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/45.jpg)
Law of Demeter
• Also known as the “Don’t talk to strangers” rule.
• It says:
–Only have knowledge of closely collaborating objects.
–Only make calls on immediate friends.
• Look out for long chained “get”-style calls (and don’t do them:
a.getB().getC().getD()
• Your system will be more testable (and maintainable,
because you have to rework calling objects less often).
![Page 46: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/46.jpg)
What’s the Point?• To write good unit tests, we need to be able to insert mocks and fakes
into the code.
• Some things that help us do that:
– Eliminating static methods and singletons.
– Asking for dependencies instead of creating them.
– Using design patterns that promote loose coupling, especially between
business and presentation logic.
– Obeying the Law of Demeter.
• Code that does not do these things will often have poor test coverage.
![Page 47: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/47.jpg)
What Can I Do?• Developers:
– Follow these practices!
• Testers:
– Educate your developers.
– Jump into the code and drive testability improvements.
• A good way to motivate this is to track test coverage metrics.
![Page 48: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/48.jpg)
Questions?Scott McMaster
Kirkland, Washington USA
scott.d.mcmaster (at) gmail.com
![Page 49: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/49.jpg)
Bonus Slides
![Page 50: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/50.jpg)
Model-View-Controller (MVC)
![Page 51: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/51.jpg)
Model-View-Controller (MVC)
• View directly accesses the Model and fires
events to the Controller.
• Controller performs operations on the Model.
• Controller doesn’t really know about the View
other than selecting the View to render.
![Page 52: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/52.jpg)
Which to Use?
• Many web MVC frameworks exist (Struts,
Rails, ASP.NET MVC).
• But these days, we work more with MVP.
![Page 53: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/53.jpg)
Manually Created Mockpublic class public class public class public class MockWeatherServiceAsyncMockWeatherServiceAsyncMockWeatherServiceAsyncMockWeatherServiceAsync implements implements implements implements WeatherServiceAsyncWeatherServiceAsyncWeatherServiceAsyncWeatherServiceAsync { { { {
private private private private List<String> List<String> List<String> List<String> expectGetWeatherForUserCallsexpectGetWeatherForUserCallsexpectGetWeatherForUserCallsexpectGetWeatherForUserCalls = = = = Lists.Lists.Lists.Lists.newArrayListnewArrayListnewArrayListnewArrayList();();();(); private private private private List<String> List<String> List<String> List<String> observeGetWeatherForUserCallsobserveGetWeatherForUserCallsobserveGetWeatherForUserCallsobserveGetWeatherForUserCalls = = = = Lists.Lists.Lists.Lists.newArrayListnewArrayListnewArrayListnewArrayList();();();();
// More @Overrides not shown on the slide.
@Override public public public public void void void void getWeatherForUsergetWeatherForUsergetWeatherForUsergetWeatherForUser(String (String (String (String userNameuserNameuserNameuserName, , , , AsyncCallbackAsyncCallbackAsyncCallbackAsyncCallback<Weather> callback) {<Weather> callback) {<Weather> callback) {<Weather> callback) { observeGetWeatherForUserCalls.add(userName); if if if if ("("("("scottscottscottscott".equals(".equals(".equals(".equals(userNameuserNameuserNameuserName)) {)) {)) {)) { callback.onSuccess(new Weather());new Weather());new Weather());new Weather()); } elseelseelseelse { { { { callback.onSuccess(nullnullnullnull);););); } }
public public public public voidvoidvoidvoid expectGetWeatherForUserexpectGetWeatherForUserexpectGetWeatherForUserexpectGetWeatherForUser((((StringStringStringString userNameuserNameuserNameuserName) {) {) {) { expectGetWeatherForUserCalls.add(userName); }
public public public public voidvoidvoidvoid verifyverifyverifyverify() {() {() {() { assertEquals(expectGetWeatherForUserCalls, observeGetWeatherForUserCalls); expectGetWeatherForUserCalls.clear(); observeGetWeatherForUserCalls.clear(); }}
![Page 54: 谷歌 Scott-lessons learned in testability](https://reader034.vdocuments.site/reader034/viewer/2022052214/555a6072d8b42ae7218b46d7/html5/thumbnails/54.jpg)
2012-12-20