Multipurpose tests Cool tricks with JUnit Java Zone

























































- Slides: 57
Multi-purpose tests (Cool tricks with JUnit) Java. Zone 2012 Johannes Brodwall, Principal Architect Steria Norway @jhannes
Part I
Properties of good test suites
Fast feedback • Reasonable confidence in seconds
While you type! Holy crap! Get Infinitest! http: //infinitest. github. com/
Good coverage • Never mind measured code coverage • Do you catch many potential mistakes?
Robust • • Does refactoring break you tests? Do tests interfere with each other?
While you type! Holy crap! “Act” and “assert” at same level of Get Infinitest! abstraction http: //infinitest. github. com/ @Test public void should_filter_people() { Person person = Person. with. Name("Darth Vader"); Person non. Matching = Person. with. Name("Anakin Skywalker"); person. Repository. save. Person(person); person. Repository. save. Person(non. Matching); assert. That(person. Repository. find. People("vade")). contains(person). excludes(non. Matching); } Allows for garbage in repository
Good test suites • • Fast feedback – ok confidence in seconds • Coverage – breaks when they should Robust – don’t break when they shouldn’t
“Integration test” • Sacrifice feedback for coverage
Part II
The configurable test
Starting point
public class Person. Servlet. Test { private Person. Servlet servlet = new Person. Servlet(); Http. Servlet. Request req = mock(Http. Servlet. Req. class); Http. Servlet. Response resp = mock(Http. Servlet. Resp. class); Person. Repository person. Repository = mock(Person. Rep. class); @Test public void should_save_person() throws IOException { when(req. get. Parameter("full_name")). then. Return("Darth Vader"); servlet. do. Post(req, resp); verify(person. Repository). save. Person(Person. with. Name("Darth Vader")); }
Fake instead of mock
public class Person. Servlet. Test { private Person. Servlet servlet = new Person. Servlet(); private Http. Servlet. Request req = mock(Http. Servlet. Req. class); private Http. Servlet. Response resp = mock(Http. Servlet. Resp. class); private Person. Repository person. Repository = new Fake. Person. Repository(); @Test public void should_save_person() throws IOException { when(req. get. Parameter("full_name")). then. Return("Darth Vader"); servlet. do. Post(req, resp); assert. That(person. Repository. find. All()). contains(Person. with. Name("Darth Vader")); } }
But we want also to use the real thing
public class Person. Servlet With. Real. Repo. Test { private Person. Servlet servlet = new Person. Servlet(); private Http. Servlet. Request req = mock(Http. Servlet. Req. class); private Http. Servlet. Response resp = mock(Http. Servlet. Resp. class); private Person. Repository person. Repository = new Jdbc. Person. Repository(…); @Test public void should_save_person() throws IOException { when(req. get. Parameter("full_name")). then. Return("Darth Vader"); servlet. do. Post(req, resp); assert. That(person. Repository. find. All()). contains(Person. with. Name("Darth Vader")); } }
What if one test could do both?
@Run. With(Repository. Test. Runner. class) public class Person. Servlet. Test { private Person. Servlet servlet = new Person. Servlet(); Http. Servlet. Request req = mock(Http. Servlet. Req. class); Http. Servlet. Response resp = mock(Http. Servlet. Resp. class); Person. Repository person. Repository; public Person. Servlet. Test(Person. Repository person. Repo) { this. person. Repository = person. Repo; } @Test public void should_save_person() throws IOException { … } }
I learned to write my own test runner by looking at org. junit. runner. Parameterized
The runner
public class Repository. Test. Runner extends Suite { private static Jdbc. Connection. Pool data. Source = …; public Repository. Test. Runner(Class<? > test. Class) { super(test. Class, create. Runners(test. Class)); } private static List<Runner> create. Runners(Class<? > test. Class) { List<Runner> runners = new Array. List<>(); runners. add(new Repo. Test. Runner(new Fake. Person. Repository(), test. Class)); runners. add(new Repo. Test. Runner(new Jdbc. Person. Repository(data. Source), test. Class)); return runners; } public static class Repo. Test. Runner extends Block. JUnit 4 Class. Runner {
public class Repository. Test. Runner extends Suite { public static class Repo. Test. Runner extends Block. JUnit 4 Class. Runner { private Person. Repository person. Repo; Repo. Test. Runner(Person. Repository person. Repo, Class<? > test. Class) { super(test. Class); this. person. Repo = person. Repo; } protected Object create. Test() throws Exception { return get. Test. Class(). get. Only. Constructor(). new. Instance(person. Repo); } protected void validate. Constructor(List<Throwable> errors) { validate. Only. One. Constructor(errors); } } }
The result
Keeping it honest
@Run. With(Repository. Test. Runner. class) public class Person. Repository. Test { private Person. Repository person. Repository; public Person. Repository. Test(Person. Repository person. Repository) { this. person. Repository = person. Repository; } @Test public void should_filter_people() { Person person = Person. with. Name("Darth Vader"); Person non. Matching = Person. with. Name("Anakin Skywalker"); person. Repository. save. Person(person); person. Repository. save. Person(non. Matching); assert. That(person. Repository. find. People("vade")). contains(person). excludes(non. Matching); } }
https: //github. com/jhannes/javaee-spike
Part III
The big picture
Worth it?
Thought experiment: What if you could test your whole application without connecting to anything?
Maintain fakes or Speed up realz?
Maintain fakes or Speed up realz?
Other uses
@Run. With(File. Test. Runner. class) @File. Test. Runner. Directory("src/test/xml/samples") public class Xml. Serialization. Test { private final File xml. File; public Xml. Serialization. Test(File xml. File) { this. xml. File = xml. File; } @Test public void serialized. Should. Match() { assert. That(normalize(Xml. read(xml. File). to. XML())). is. Equal. To(normalize(slurp(xml. File))); }
https: //github. com/jhannes/eaxy
Conclusions
There’s more to tests than “integration test” or “unit test”
• Get fast feedback • Detect mistakes • Only fail when you should
Thank you johannes@brodwall. com http: //johannesbrodwall. com http: //github. com/jhannes http: //twitter. com/jhannes
“Integration tests or unit tests”
Integration tests or unit test?
public class Jdbc. Person. Repository. Test { private static Data. Source data. Source; @Before. Class public static void create. Data. Source() { data. Source = Jdbc. Connection. Pool. create(…); Jdbc. Person. Repository. create. Database. Schema(data. Source); } Person. Repository person. Repo = new Jdbc. Person. Repository(data. Source); @Test public void should_save_people() { Person person = Person. with. Name("Johannes Brodwall"); person. Repo. save. Person(person); assert. That(person. Repo. find. People(null)). contains(person); } }
“Integration tests” • Connects to services, database, files? • Or just integrates whole system? • Or integrates system with all services?
“Integration tests” are • Slow • Dependent on external systems • Realistic?
Integration tests or unit test?
public class Person. Test { @Test public void equality() { assert. That(Person. with. Name("Darth Vader")). is. Equal. To(Person. with. Name("Darth Vader")). is. Not. Equal. To(Person. with. Name("Anakin Skywalker")). is. Not. Equal. To(new Object()). is. Not. Equal. To(null); } } Since you’re asking: FEST-assert http: //code. google. com/p/fest/
“Unit tests” • No resources outside JVM? • Only one class? • Only one method?
“Unit tests” are • Very fast • Coupled to implementation?
Integration tests or unit test?
Why does it matter?
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
• What will I cover Testing considerations • • Creating your own test runner • • • Unit test or integration test What are good tests Mocks versus fakes The mocked test • The faked test The multi purpose test • What’s next? • Eventually abandoned But still using @Run. With in many cases – eg. Files •