Download presentation
Presentation is loading. Please wait.
Published bySharyl Ferguson Modified over 9 years ago
1
Multi-purpose tests (Cool tricks with JUnit) JavaZone 2012 Johannes Brodwall, Principal Architect Steria Norway @jhannes
2
Part I
3
Properties of good test suites
4
Fast feedback Reasonable confidence in seconds
5
While you type! Holy crap! Get Infinitest! http://infinitest.github.com/
6
Good coverage Never mind measured code coverage Do you catch many potential mistakes?
7
Robust Does refactoring break you tests? Do tests interfere with each other?
8
@Test public void should_filter_people() { Person person = Person.withName("Darth Vader"); Person nonMatching = Person.withName("Anakin Skywalker"); personRepository.savePerson(person); personRepository.savePerson(nonMatching); assertThat(personRepository.findPeople("vade")).contains(person).excludes(nonMatching); } While you type! Holy crap! Get Infinitest! http://infinitest.github.com/ “Act” and “assert” at same level of abstraction Allows for garbage in repository
9
Good test suites Fast feedback – ok confidence in seconds Coverage – breaks when they should Robust – don’t break when they shouldn’t
10
“Integration test” Sacrifice feedback for coverage
11
Part II
12
The configurable test
13
Starting point
14
public class PersonServletTest { private PersonServlet servlet = new PersonServlet(); private HttpServletRequest req = mock(HttpServletReq.class); private HttpServletResponse resp = mock(HttpServletResp.class); private PersonRepository personRepository = mock(PersonRep.class); @Test public void should_save_person() throws IOException { when(req.getParameter("full_name")).thenReturn("Darth Vader"); servlet.doPost(req, resp); verify(personRepository).savePerson(Person.withName("Darth Vader")); }
15
Fake instead of mock
16
public class PersonServletTest { private PersonServlet servlet = new PersonServlet(); private HttpServletRequest req = mock(HttpServletReq.class); private HttpServletResponse resp = mock(HttpServletResp.class); private PersonRepository personRepository = new FakePersonRepository(); @Test public void should_save_person() throws IOException { when(req.getParameter("full_name")).thenReturn("Darth Vader"); servlet.doPost(req, resp); assertThat(personRepository.findAll()).contains(Person.withName("Darth Vader")); }
17
But we want also to use the real thing
18
public class PersonServlet WithRealRepo Test { private PersonServlet servlet = new PersonServlet(); private HttpServletRequest req = mock(HttpServletReq.class); private HttpServletResponse resp = mock(HttpServletResp.class); private PersonRepository personRepository = new JdbcPersonRepository(…); @Test public void should_save_person() throws IOException { when(req.getParameter("full_name")).thenReturn("Darth Vader"); servlet.doPost(req, resp); assertThat(personRepository.findAll()).contains(Person.withName("Darth Vader")); }
19
What if one test could do both?
20
@RunWith(RepositoryTestRunner.class) public class PersonServletTest { private PersonServlet servlet = new PersonServlet(); private HttpServletRequest req = mock(HttpServletReq.class); private HttpServletResponse resp = mock(HttpServletResp.class); private PersonRepository personRepository; public PersonServletTest(PersonRepository personRepo) { this.personRepository = personRepo; } @Test public void should_save_person() throws IOException { … }
21
I learned to write my own test runner by looking at org.junit.runner.Parameterized
22
The runner
23
public class RepositoryTestRunner extends Suite { private static JdbcConnectionPool dataSource = …; public RepositoryTestRunner(Class testClass) { super(testClass, createRunners(testClass)); } private static List createRunners(Class testClass) { List runners = new ArrayList<>(); runners.add(new RepoTestRunner(new FakePersonRepository(), testClass)); runners.add(new RepoTestRunner(new JdbcPersonRepository(dataSource), testClass)); return runners; } public static class RepoTestRunner extends BlockJUnit4ClassRunner {
24
public class RepositoryTestRunner extends Suite { public static class RepoTestRunner extends BlockJUnit4ClassRunner { private PersonRepository personRepo; RepoTestRunner(PersonRepository personRepo, Class testClass) { super(testClass); this.personRepo = personRepo; } protected Object createTest() throws Exception { return getTestClass().getOnlyConstructor().newInstance(personRepo) ; } protected void validateConstructor(List errors) { validateOnlyOneConstructor(errors); }
25
The result
27
Keeping it honest
28
@RunWith(RepositoryTestRunner.class) public class PersonRepositoryTest { private PersonRepository personRepository; public PersonRepositoryTest(PersonRepository personRepository) { this.personRepository = personRepository; } @Test public void should_filter_people() { Person person = Person.withName("Darth Vader"); Person nonMatching = Person.withName("Anakin Skywalker"); personRepository.savePerson(person); personRepository.savePerson(nonMatching); assertThat(personRepository.findPeople("vade")).contains(person).excludes(nonMatching); }
30
https://github.com/jhannes/javaee-spike
31
Part III
32
The big picture
33
Worth it?
34
Thought experiment: What if you could test your whole application without connecting to anything?
35
Maintain fakes or Speed up realz?
36
Maintain fakes or Speed up realz?
37
Other uses
38
@RunWith(FileTestRunner.class) @FileTestRunner.Directory("src/test/xml/samples") public class XmlSerializationTest { private final File xmlFile; public XmlSerializationTest(File xmlFile) { this.xmlFile = xmlFile; } @Test public void serializedShouldMatch() { assertThat(normalize(Xml.read(xmlFile).toXML())).isEqualTo(normalize(slurp(xmlFile))); }
40
https://github.com/jhannes/eaxy
41
Conclusions
42
There’s more to tests than “integration test” or “unit test”
43
Get fast feedback Detect mistakes Only fail when you should
44
Thank you johannes@brodwall.com http://johannesbrodwall.com http://github.com/jhannes http://twitter.com/jhannes
45
“Integration tests or unit tests”
46
Integration tests or unit test?
47
public class JdbcPersonRepositoryTest { private static DataSource dataSource; @BeforeClass public static void createDataSource() { dataSource = JdbcConnectionPool.create(…); JdbcPersonRepository.createDatabaseSchema(dataSource); } PersonRepository personRepo = new JdbcPersonRepository(dataSource); @Test public void should_save_people() { Person person = Person.withName("Johannes Brodwall"); personRepo.savePerson(person); assertThat(personRepo.findPeople(null)).contains(person); }
48
“Integration tests” Connects to services, database, files? Or just integrates whole system? Or integrates system with all services?
49
“Integration tests” are Slow Dependent on external systems Realistic?
50
Integration tests or unit test?
51
public class PersonTest { @Test public void equality() { assertThat(Person.withName("Darth Vader")).isEqualTo(Person.withName("Darth Vader")).isNotEqualTo(Person.withName("Anakin Skywalker")).isNotEqualTo(new Object()).isNotEqualTo(null); } Since you’re asking: FEST-assert http://code.google.com/p/fest/
52
“Unit tests” No resources outside JVM? Only one class? Only one method?
53
“Unit tests” are Very fast Coupled to implementation?
54
Integration tests or unit test?
55
Why does it matter?
56
Who is this talk for? You have written more than a few tests in JUnit You want to find out when you write unit test and when to write integration tests and how to use mocks well You will be able to write your own test-runner for fun and profit
57
What will I cover Testing considerations Unit test or integration test What are good tests Mocks versus fakes Creating your own test runner The mocked test The faked test The multi purpose test What’s next? Eventually abandoned But still using @RunWith in many cases – eg. Files
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.