SlideShare a Scribd company logo
@oldJavaGuy@ZalandoTech
Not your father’s tests
Advanced Testing Patterns in Java (and Scala)
@oldJavaGuy@ZalandoTech
Sean P. Floyd
• Full Stack Engineer @ Zalando
• ~20 years experience
• Java, Maven, Spring, Scala,
Groovy
• StackOverflow: bit.ly/spfOnSO
@oldJavaGuy@ZalandoTech
Agenda
• Software Design Principles (applied to tests)
• JUnit best practices
• Matchers
• Mocks
• Extending the Lifecycle
• Testing in Scala
• Testing the Untestable
@oldJavaGuy@ZalandoTech
Software Design
Principles
(and their application in tests)
@oldJavaGuy@ZalandoTech
THE SOLID Principles
• Single Responsibility Principle
• Open / Closed Principle
• Liskov Substitution Principle
• Interface Segregation Principle
• Dependency Inversions Principle
• Michael Feathers, Uncle Bob Martin, ~2003
@oldJavaGuy@ZalandoTech
Single Responsibility Principle
• Mapping production to test:
• 3 levels of production code (class, method, input)
• JUnit: 2 levels of test (class, method)
• Only one type of change should break any given test
A class should have only a single responsibility (i.e. only one potential
change in the software's specification should be able to affect the
specification of the class)
@oldJavaGuy@ZalandoTech
class StringUtils{ // dimension 1
// dimension 2
static String leftPad(String s, char c, int n){
if(s==null) return s; // dimension 3 a
if(n <= s.length()) return s; // dimension 3 b
StringBuilder sb = new StringBuilder(n);
for(int i = s.length(); i < n; i++){
sb.append(c);
}
// dimension 3 c
return sb.append(s).toString();
}
@oldJavaGuy@ZalandoTech
public class BadStringUtilsTest {
/* violates single responsibility
principle */
@Test public void badLeftPadTest() {
assertNull(leftPad(null, ' ', 10));
assertEquals("foo", leftPad("foo", ' ', 3));
assertEquals("foo", leftPad("foo", ' ', 4));
}
}
@oldJavaGuy@ZalandoTech
Open / Closed Principle
• Closed for modification applies to tests also
• Antipattern: Subclasses for Tests
• Antipattern: package protected access
• Better: Clean abstractions, mock objects
“Software entities should be open for extension, but
closed for modification”
@oldJavaGuy@ZalandoTech
class TightlyCoupledClass{
private final MyService ms = new MyServiceImpl();
public String foo(){ return ms.bar(); }
}
class TightlyCoupledClassTest{
@Test public void fooTest(){
TightlyCoupledClass t = new TCC(){
@Override public String foo(){
return "baz";
} };
assertEquals("baz", t.foo());
}
}
@oldJavaGuy@ZalandoTech
final class NotSoTightlyCoupledClass{
public NSTCC(MyService ms){this.ms = ms;}
private final MyService ms;
public String foo(){ return ms.bar(); }
}
class NotSoTightlyCoupledClassTest{
@Test public void fooTest(){
MyService ms = Mockito.mock(MyService.class);
NotSoTightlyCoupledClass n = new NSTCC(ms);
when(ms.bar()).thenReturn("baz");
assertEquals("baz", n.foo());
}
}
@oldJavaGuy@ZalandoTech
Liskov Substitution Principle
• When designing class hierarchies, design
corresponding test class hierarchies
• If the classes have a common contract, the same is
true for the test classes
“Objects in a program should be replaceable with instances of
their subtypes without altering the correctness of that program.”
@oldJavaGuy@ZalandoTech
abstract class AbstractCollectionTest{
protected abstract <T> Collection<T> create();
@Test public void equalsTest(){
assertEquals(create(), create());
}
// etc.
}
class ArrayListTest extends AbstractCollectionTest{
protected <T> Collection<T> create(){
return new ArrayList<>();
}}
class HashSetTest extends AbstractCollectionTest{
protected <T> Collection<T> create(){
return new HashSet<>();
}}}
@oldJavaGuy@ZalandoTech
Interface Segregation Principle
• If a class implements multiple interfaces, test them
separately
“Many client-specific interfaces are better than one
general-purpose interface.”
@oldJavaGuy@ZalandoTech
class Multi implements Fooable, Barable {
@Override public String foo() { return "foo"; }
@Override public String bar() { return "bar"; }
}
class FooableMultiTest extends AbstractFooableTest {
@Override protected Fooable create() {
return new Multi();
}
}
class BarableMultiTest extends AbstractBarableTest {
@Override protected Barable create(){
return new Multi();
}
}
@oldJavaGuy@ZalandoTech
Dependency Inversion Principle
• Test public methods, not implementation details
• Test on the same level of abstraction as the
implementation
• Don’t change or test internal state
“Depend upon Abstractions. Do not depend upon
concretions.”
@oldJavaGuy@ZalandoTech
class WayTooInvolvedArrayListTest {
@Test
public void testAdd() throws Exception {
List<String> l = new ArrayList<>();
l.add("foo");
Field f = ArrayList.class
.getDeclaredField("elementData");
Object[] arr = (Object[]) f.get(l);
assertEquals("foo", arr[0]);
}
}
@oldJavaGuy@ZalandoTech
JUnit 4.x

Best Practices
@oldJavaGuy@ZalandoTech
Assertions vs
Matchers
@oldJavaGuy@ZalandoTech
public class TestWithStandardAssertions {
private List<String> list;
@Test
public void exactlyOneNoneEmptyString() {
assertNotNull(list);
assertEquals(list.size(), 1);
assertNotEquals(list.get(0).trim(), "");
}
}
Plain JUnit assertions
@oldJavaGuy@ZalandoTech
private List<String> list;
java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:86)
list = Arrays.asList();
java.lang.AssertionError:
Expected :0
Actual :1
list = Arrays.asList(" ");
java.lang.AssertionError: Values should be
different. Actual:
Output from plain assertions
@oldJavaGuy@ZalandoTech
Hamcrest
• “Matchers that can be combined to create flexible
expressions of intent”
• Make tests (and failure output) readable
• Work on the correct abstraction level
• Built-in matchers, extendable with custom
matchers
@oldJavaGuy@ZalandoTech
public class TestWithHamcrestMatchers {
private List<String> list;
@Test
public void exactlyOneNoneEmptyString() {
assertThat(list, hasSize(1));
assertThat(list, hasItem(nonEmptyString()));
}
}
@oldJavaGuy@ZalandoTech
// custom Matcher
private Matcher<String> nonEmptyString() {
return new TypeSafeMatcher<String>() {
boolean matchesSafely(String s) {
return !s.trim().isEmpty(); }
void describeTo(Description d) {
d.appendText(
"a non-empty String"); }
};
}
@oldJavaGuy@ZalandoTech
// Error messages for Hamcrest Matchers:
private List<String> list;
Expected: a collection with size <1>
but: was null
list = Arrays.asList();
Expected: a collection with size <1>
but: collection size was <0>
list = Arrays.asList(" ");
Expected: a collection containing a non-empty
String but: was " "
@oldJavaGuy@ZalandoTech
Dependency Inversion
Principle revisited
• Abstraction in its main sense is a conceptual
process by which general rules and concepts are
derived from the usage and classification of
specific examples, literal ("real" or "concrete")
signifiers, first principles, or other methods.

Source: en.wikipedia.org/wiki/Abstraction
• The hard part is finding the right level of abstraction
@oldJavaGuy@ZalandoTech
Abstraction level: too low
• “Stringly typed”:
• Method parameters that
take strings when other
more appropriate types
should be used.
• Message passing without
using typed messages etc.
• Tests will most likely have to
parse custom Strings, tightly
coupling them to domain
knowledge
blog.codinghorror.com/new-programming-jargon/
@oldJavaGuy@ZalandoTech
Abstraction level: too high
• “Baklava Code”:
• While thin layers are fine for a
pastry, thin software layers
don’t add much value,
especially when you have
many such layers piled on
each other. Each layer has to
be pushed onto your mental
stack as you dive into the
code.
• Tests will most likely have to
either set up or mock multiple
layers, introducing tight coupling
blog.codinghorror.com/new-programming-jargon/
@oldJavaGuy@ZalandoTech
Example: JSON
• JSON (JavaScript Object Notation) is becoming the
lingua franca for Web-based (Restful?) APIs
• Almost every application deals with JSON in one
form or another
• How can we test JSON output for correctness?
@oldJavaGuy@ZalandoTech
public class ManualJsonTest {
private String json = "{ "id": 123, "name": "John Smith"}";
@Test
public void exactMatch() {
// missing a space breaks the assertion, although the semantics
// are still correct
assertEquals(json, "{"id": 123, "name": "John Smith"}");
}
}
Bad: Manual JSON assertions
False negatives through irrelevant things like
whitespace or property ordering
@oldJavaGuy@ZalandoTech
public class RegexJsonTest {
private String json = "{ "id": 123, "name": "John Smith"}";
@Test
public void regexMatch() {
// too technical, we have to effectively implement a JSON parser
// which violates the single responsibility principle
// also, re-ordering of properties is semantically correct
// but breaks the test
assertTrue(json.matches(
"{s*"id"s*:s*123s*,s*"name"s*:s*"John Smith"}"));
}
}
Bad: Regex JSON assertions
False negatives through property reordering or
grammar edge cases
@oldJavaGuy@ZalandoTech
public class JacksonJsonTest {
private String json = "{ "id": 123, "name": "John Smith"}";
@Test
public void jacksonMatch() throws IOException {
// better: we no longer have to implement our own parsing
JsonNode jsonNode = new ObjectMapper().readTree(json);
assertTrue(jsonNode.isObject());
// but this is still more complicated than necessary:
assertEquals(jsonNode.get("id").asInt(),123);
assertEquals(jsonNode.get("name").asText(),"John Smith");
}
}
Better: Using Jackson
Still: much boilerplate
Non-descriptive error messages
@oldJavaGuy@ZalandoTech
JsonPath-assert
A library with hamcrest-matchers for JsonPath.
https://github.com/jayway/JsonPath/tree/master/json-path-assert
@oldJavaGuy@ZalandoTech
public class JaywayJsonTest {
String json = "{ "id": 123, "name": "John Smith"}";
@Test
public void jsonPathMatch() throws IOException {
JsonAssert.with(json)
.assertEquals("id", 123)
.assertEquals("name", "John Smith");
}
}
Perfect: Using JsonAssert
No boilerplate, automatic type conversion,
descriptive error messages
@oldJavaGuy@ZalandoTech
Matchers (summary)
• Look for existing matcher libraries
• Write your own matchers in a re-usable way
• start with a local factory method
• if you need it in another class, move it to a dedicated helper
class (e.g. XyzMatchers)
• if you need it in multiple projects, make your own matcher library
@oldJavaGuy@ZalandoTech
Mocking
mock: verb (gerund or present participle: mocking)
1. tease or laugh at in a scornful or contemptuous manner
2. make (something) seem laughably unreal or impossible
3. make a replica or imitation of something.
pragprog.com/magazines/2010-05/the-virtues-of-mockery
@oldJavaGuy@ZalandoTech
No, really
• A mock object is a testing tool that acts as a stand-in for a
“real” object during testing in much the same way as an Elvis
impersonator stands in for the real King. The impersonator is
cheaper, easier to access, and most likely lighter weight.

pragprog.com/magazines/2010-05/the-virtues-of-mockery
• Mocking in Java can take take numerous forms:
• overriding methods of the original class
• providing an alternative interface implementation
• creating a proxy (interface based or cglib etc.)
@oldJavaGuy@ZalandoTech
Recalling SOLID
• overriding methods of the original class
• violates Open / Closed principle
• providing an alternative interface implementation
• violates Interface segregation principle
• creating a proxy (interface based or cglib etc.)
• violates Dependency Inversion Principle
@oldJavaGuy@ZalandoTech
Mockito
http://mockito.org/
@oldJavaGuy@ZalandoTech
public interface UserService {
Optional<Session> login(
String user, String pass);
}
interface Session{ /* stuff in here */ }
public class LoginService {
private final UserService us;
public boolean login(String user, String pass){
return us.login(user, pass).isPresent();
}
}
@oldJavaGuy@ZalandoTech
public class LoginServiceTestWithoutMockito {
@Test
public void loginSuccess() {
LoginService ls = new LoginService(
(user, password) -> Optional.of(new Session() {}));
assertTrue(ls.login("ali baba", "open sesame"));
}
@Test
public void loginFailure() {
LoginService ls = new LoginService((user, password) -> Optional.empty());
assertFalse(ls.login("ali baba", "open sesame"));
}
}
Mocking without Mockito
• if interface evolves, all tests break
• code duplication, no common setup
@oldJavaGuy@ZalandoTech
public class LoginServiceTestWithMockito {
UserService userService; LoginService ls;
@Before public void setUp(){
userService= mock(UserService.class);
ls = new LoginService(userService);
}
@Test public void loginSuccess() {
when(userService.login(anyString(), anyString()))
.thenReturn(Optional.of(mock(Session.class)));
assertTrue(ls.login("ali baba", "open sesame"));
verify(ls,times(1)).login("ali baba", "open sesame"));
}
}
Mocking with Mockito
• Common setup
• No tight coupling to UserService and Session
• Verify mock interaction (reduce false positives)
@oldJavaGuy@ZalandoTech
Advanced Mockito
• All methods in a mock return default values (null for
references, 0 or false for primitives)
• Use Mockito.spy(object) to wrap an existing object
inside a mock
• Use .thenAnswer() to interact with method
parameters rather than return static values
• Use deep stubs to return chained mocks (huge
code smell, but useful for legacy code)
@oldJavaGuy@ZalandoTech
Mocking Web APIs
• Scenario: Testing API clients
• Blackbox:
• Faking the API calls by a 3rd party Mock Server
• MockServer, WireMock
• Whitebox:
• Using the API’s own code to create the Mock
• Example: Spring MockMvc
• No actual network call, mock requests / responses
@oldJavaGuy@ZalandoTech
new MockServerClient("127.0.0.1", 1080)
.when(
request()
.withMethod("POST").withPath("/login")
.withQueryStringParameters(new Parameter("returnUrl", "/account"))
.withBody(exact("{username: 'foo', password: 'bar'}")),
exactly(1)
)
.respond(
response()
.withStatusCode(401)
.withHeaders(
new Header("Content-Type", "application/json; charset=utf-8"),
new Header("Cache-Control", "public, max-age=86400")
)
.withBody("{ message: 'incorrect username and password combination' }")
);
Example: MockServer
• set up behavior to mimic real API
• start up server via @Before method or @Rule
@oldJavaGuy@ZalandoTech
Spring Mvc Controller
@RestController @RequestMapping("/contacts")
public class ContactController {
private final ContactRepository contactRepository;
public ContactController(ContactRepository contactRepository) {
this.contactRepository = contactRepository;
}
@RequestMapping(method = GET, value = "/{contactId}")
public Contact getContact(@PathVariable int contactId) {
return contactRepository.findOne(contactId);
}
}
@oldJavaGuy@ZalandoTech
public class ContactControllerTest {
ContactRepository contactRepository; MockMvc mockMvc;
@Before public void setup() {
contactRepository = mock(ContactRepository.class);
ContactController controller = new ContactController(contactRepository);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test public void getContactById() throws Exception {
final Contact contact = new Contact();
contact.setFirstName("Jim");
when(contactRepository.findOne(anyInt())).thenReturn(contact);
mockMvc.perform(get("/contacts/123").accept(APPLICATION_JSON)) //
.andExpect(status().isOk()) //
.andExpect(content().contentType(APPLICATION_JSON)) //
.andExpect(jsonPath("$.firstName").value("Jim"));
}
}
MockMvc
• Request / response cycle is virtual
• Integrates nicely with Mockito and JsonPath
@oldJavaGuy@ZalandoTech
JUnit lifecycle
Runners, rules, annotations
@oldJavaGuy@ZalandoTech
Basic Lifecycle
• @Before / @After
• instance, before / after every test
• setting up / cleaning up test data
• instantiating classes and mocks
• @BeforeClass / @AfterClass
• static, before / after entire test class
• bootstrapping databases and web servers
• creating and deleting temporary folders
@oldJavaGuy@ZalandoTech
Okay, but
• What if I need the same kind of setup in many
tests?
• Move the setup to an abstract test class
• If setup needs test-specific parameters, get them
from abstract methods
@oldJavaGuy@ZalandoTech
Inheritance vs aggregation
• This works fine for test hierarchies with a clear relation
(“AbstractUserServiceTest” -> “UserServiceLoginTest”)
• LSP says: common hierarchies for production and tests
• Which means: we probably shouldn’t have an
AbstractDBIntegrationTest, since we probably don’t
have an AbstractDatabaseService
• Okay, so how can we embrace DRY without
inheritance?
@oldJavaGuy@ZalandoTech
Custom JUnit Runners
• @RunWith(someclass) replaces JUnit’s standard Runner
• Full control over entire lifecycle
http://www.rockcellarmagazine.com/2012/10/01/the-man-behind-darth-vader/
@oldJavaGuy@ZalandoTech
Example Runners
• SpringJunit4Runner
• Ties JUnit lifecycle to Spring lifecycle
• Dependency injection, transaction handling
• MockitoRunner
• Mocks all fields annotated with @Mock
• Parameterized
• Runs tests with parameters
@oldJavaGuy@ZalandoTech
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 },
{ 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
@Parameter(0) public int fInput;
@Parameter(1) public int fExpected;
@Test public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
Parameterized Example
• Generates one test per method, per values array
• Values are common for all test methods
@oldJavaGuy@ZalandoTech
• You can’t mix runners (e.g. Spring + Mockito)
• Runner interface is very technical
• You have to do everything
http://www.quickmeme.com/meme/35n2io
Runner Drawbacks
@oldJavaGuy@ZalandoTech
JUnit Rules
Reusable JUnit lifecycle
components
@oldJavaGuy@ZalandoTech
public static class HasTempFolder {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testUsingTempFolder() throws IOException {
File createdFile = folder.newFile("myfile.txt");
File createdFolder = folder.newFolder("subfolder");
// ...
}
}
Example test rule
• Rules can interact with test lifecycle
• @Rule is per test, @ClassRule is per test class
@oldJavaGuy@ZalandoTech
public class NTimes implements TestRule {
private final int times; private final Logger logger;
public NTimes(final int times, final Logger logger) {
this.times = times; this.logger = logger; }
@Override public Statement apply(Statement base, Description desc) {
return new Statement() {
@Override public void evaluate() throws Throwable {
StopWatch stopWatch = new StopWatch();
for (int i = 0; i < times; i++) { base.evaluate();}
logger.info("Executed {} times in {} ms", times,
stopWatch.getLastTaskTimeMillis());
}};
}}
Implementing our own rule
• runs every test n times, logs total duration
@oldJavaGuy@ZalandoTech
Lifecycle recap
• Use @Before / @After for specific local setup
• Use @Rule / @ClassRule if you want to reuse setup
logic, ideally move your rules to libraries
• Use custom runners for parameterized testing or
Spring (but you can’t combine them)
• You probably shouldn’t write a custom runner
@oldJavaGuy@ZalandoTech
Testing in Scala
https://www.youtube.com/watch?v=RnqAXuLZlaE
@oldJavaGuy@ZalandoTech
Scala Test frameworks
• ScalaTest
• supports a plethora of testing styles
• very readable DSLs
• custom matcher SPI
• alternative: Spec2
@oldJavaGuy@ZalandoTech
class ExampleSpec extends FlatSpec with Matchers {
"A Stack" should "pop values in last-in-first-out order" in {
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
stack.pop() should be (2)
stack.pop() should be (1)
}
it should "throw NoSuchElementException if an empty stack is popped" in {
val emptyStack = new Stack[Int]
a [NoSuchElementException] should be thrownBy {
emptyStack.pop()
}
}
}
Example from ScalaTest
@oldJavaGuy@ZalandoTech
Differences
• Java:
• tests are defined through annotated methods, instantiated through
reflection
• 2 levels of nesting (class, method)
• Scala:
• tests are defined programmatically, using a DSL
• no isolation: runtime error in one test destroys everything
• arbitrary levels of nesting
• many different ways to write tests
@oldJavaGuy@ZalandoTech
class TestWithArbitraryDepth extends FunSpec{
describe("my package"){
describe("my class"){
describe("my method"){
it("should reject nulls"){
intercept[NullPointerException] {
myClass.myMethod(null)
}
}
it("should return true for non-null params"){
assert(myClass.myMethod("foo"))
}
}
}
}
}
Arbitrary nesting in ScalaTest
@oldJavaGuy@ZalandoTech
TestWithArbitraryDepth:
my package
my class
my method
- should reject nulls
- should return true for non-null params
- should return false for input 42 * FAILED
Output
@oldJavaGuy@ZalandoTech
Matchers
• Extend your test class with the Matchers trait
• Matchers enable “readable” assertions with the
“should” keyword, e.g.



result should have length 3

file should have ('name ("temp.txt"))
• You can create custom matchers and combine
them using a DSL
@oldJavaGuy@ZalandoTech
trait CustomMatchers {
class ExtensionMatcher(expected: String)
extends Matcher[java.io.File] {
def apply(left: java.io.File) = {
val name = left.getName
MatchResult(
name.endsWith(expected),
s"""File $name did not end with extension "$expected"""",
s"""File $name ended with extension "$expected""""
)
}
}
def hasExtension(extension: String) = new ExtensionMatcher(extension)
}
Custom matcher
@oldJavaGuy@ZalandoTech
Property-based testing with
ScalaCheck
• ScalaCheck is a tool for testing Scala and Java
programs, based on property specifications and
automatic test data generation.
• Define a property that specifies the behaviour of a
method or some unit of code, and ScalaCheck
checks that the property holds.
• All test data are generated automatically in a
random fashion, so you don't have to worry about
any missed cases.
@oldJavaGuy@ZalandoTech
class Fraction(n: Int, d: Int) {
require(d != 0)
require(d != Integer.MIN_VALUE)
require(n != Integer.MIN_VALUE)
val numer = if (d < 0) -1 * n else n
val denom = d.abs
override def toString = numer + " / " + denom
}
Class to test
@oldJavaGuy@ZalandoTech
class PropertySpec extends FlatSpec with PropertyChecks
with Matchers {
forAll { (n: Int, d: Int) =>
whenever(d != 0 && d != Integer.MIN_VALUE
&& n != Integer.MIN_VALUE) {
val f = new Fraction(n, d)
if (n < 0 && d < 0 || n > 0 && d > 0)
f.numer should be > 0
else if (n != 0) f.numer should be < 0
else f.numer should be === 0
f.denom should be > 0
}
}
Property Testing (Part1)
@oldJavaGuy@ZalandoTech
val invalidCombos =
Table(
("n", "d"),
(Integer.MIN_VALUE, Integer.MIN_VALUE),
(1, Integer.MIN_VALUE),
(Integer.MIN_VALUE, 1),
(Integer.MIN_VALUE, 0),
(1, 0)
)
forAll(invalidCombos) { (n: Int, d: Int) =>
evaluating {
new Fraction(n, d)
} should produce[IllegalArgumentException]
}
}
Property Testing (Part2)
Source: http://stackoverflow.com/a/30152387/342852
@oldJavaGuy@ZalandoTech
JUnit ScalaTest
Use Hamcrest Matchers Mix in Matchers Trait
@Before, @After Do it in code
@Rule Mix in a trait
Parameterized runner Property Testing with ScalaCheck
Solving similar problems in
JUnit and ScalaTest
@oldJavaGuy@ZalandoTech
What now?
• Java and Scala can be combined
• You can write tests for your Java code in Scala
• No runtime dependencies to Scala
• Scala can be integrated easily into all major build
systems and IDEs
@oldJavaGuy@ZalandoTech
OK but what if I really don’t
like Scala?
• JUnit 4 is also kinda cool when you use Hamcrest,
Mockito and Rules
• JUnit 5 will come soon, supporting lots of new cool
features:
• New test callback mechanism
• Dynamic test generation (limited)
• Lambda support
@oldJavaGuy@ZalandoTech
Testing the Untestable
https://mubi.com/films/mission-impossible
@oldJavaGuy@ZalandoTech
Untestable Software
checklist
• Shared static state
• Lack of Visibility
• Deep dependency tree
@oldJavaGuy@ZalandoTech
Case Study
Stripes Framework
used by Zalando Shop
in the early years
@oldJavaGuy@ZalandoTech
The madness
• Stripes relies on static initialization in a Servlet Filter
• Controllers, Localization and many other
components depend on this state
• Spring integration is worse:



ContextLoader

.getCurrentWebApplicationContext()

.getBean(MyBean.class)
@oldJavaGuy@ZalandoTech
Divide and conquer
• Create solutions for Stripes and Spring, separately
• Make sure you tear up everything you set up
• Keep low-tech hacks in one place
• Provide high level facades for your tests
@oldJavaGuy@ZalandoTech
static StripesFilter stripesFilter;
static MockServletContext ctx;
public static void initStripes() throws Exception {
if (!stripesIsAlreadyActive()) {
stripesFilter = new StripesFilter();
final MockFilterConfig config = new MockFilterConfig();
config.addInitParameter("ActionResolver.Packages", "de.zalando.shop.frontend.stripes.action");
ctx = new MockServletContext(); config.setServletContext(ctx);
stripesFilter.init(config);
}
}
public static void shutdownStripes() throws Exception {
if (stripesFilter != null) {
stripesFilter.destroy(); stripesFilter = null; ctx = null; }
}
private static boolean stripesIsAlreadyActive() {
final Logger stripesLogger = Logger.getLogger(StripesFilter.class);
final Level oldLevel = stripesLogger.getLevel();
stripesLogger.setLevel(Level.FATAL);
try { new LocalizableMessage("link.catalog") .getMessage(Locale.GERMAN);
return true; } catch (final Exception e) { return false; }
finally { stripesLogger.setLevel(oldLevel); }
}
StripesTestUtils (extract)
@oldJavaGuy@ZalandoTech
private static ConfigurableWebApplicationContext ctx;
public static void registerBean(Object service, String beanName) {
getOrCreateTestWebAppContext().getBeanFactory().registerSingleton(beanName, service); }
private static ConfigurableWebApplicationContext setupContextLoaderTestWebApplicationContext() {
try { ConfigurableWebApplicationContext cwa = new GenericWebApplicationContext();
AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor =
new AutowiredAnnotationBeanPostProcessor();
autowiredAnnotationBeanPostProcessor.setBeanFactory(cwa.getBeanFactory());
cwa.getBeanFactory().addBeanPostProcessor(autowiredAnnotationBeanPostProcessor);
getCurrentContextField().set(null, cwa); ctx = cwa; return cwa;
} catch (Exception e) { fail(e.getMessage()); throw new IllegalStateException(); } }
private static Field getCurrentContextField() throws NoSuchFieldException {
final Field contextField = ContextLoader.class.getDeclaredField("currentContext");
contextField.setAccessible(true); return contextField; }
private static ConfigurableWebApplicationContext getOrCreateTestWebAppContext() {
WebApplicationContext currentWebApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ConfigurableWebApplicationContext twa;
if ((currentWebApplicationContext instanceof ConfigurableWebApplicationContext)
&& (currentWebApplicationContext == ctx)) {
twa = (ConfigurableWebApplicationContext) currentWebApplicationContext;
} else { twa = setupContextLoaderTestWebApplicationContext(); }
return twa; }
SpringTestUtils (extract)
@oldJavaGuy@ZalandoTech
Visibility
• If you control the sources, change
visibility to package-protected
• If you don’t, use reflection and build
high-level facades
@oldJavaGuy@ZalandoTech
Case study
ANTLR (ANother Tool for
Language Recognition) is a
powerful parser generator
for reading, processing, or
translating structured files.
@oldJavaGuy@ZalandoTech
• You develop a grammar, ANTLR will generate the
parser class for it
• Example grammar:



grammar Expr; 

prog:(expr NEWLINE)* ;

expr:expr ('*'|'/') expr

| expr ('+'|'-') expr

| INT

| '(' expr ')';

NEWLINE : [rn]+ ;

INT : [0-9]+ ;
@oldJavaGuy@ZalandoTech
How to unit test a grammar?
• Grammars consist of rules
• Let’s try to test rules separately
• Each rule is a dedicated method in the parser, and it’s
protected, so we have to hack it
• Let’s use convention over configuration to generate
test from sample files
• We’ll use the file naming convention <rule>.txt and
create test dynamically with ScalaTest
@oldJavaGuy@ZalandoTech
describe("Valid examples for") {
baseDir.listFiles().foreach({
(f) => {
val ruleName: String = f.getName.replace(".txt", "")
describe(ruleName) {
try { val method: Method = parserType.getDeclaredMethod(ruleName)
describe("should be successfully parsed") {
val source: String = new String(Files.readAllBytes(f.toPath), UTF_8)
val index = new AtomicInteger();
source.split("---+").map(stripComments).foreach((src) => {
val ct: Int = index.getAndIncrement()
it("item[" + ct + "]") {
val parser: P = parserFor(src, ruleName)
try { method.invoke(parser) } catch {
case e: InvocationTargetException
=> fail(s"$ruleName[${ct}]", e.getCause)
} } }) }
} catch {
case e: NoSuchMethodException => {
err.println(s"Bad rule name: $ruleName")
} } } } }) }
Generating tests dynamically
@oldJavaGuy@ZalandoTech
Deep Dependency Tree
@oldJavaGuy@ZalandoTech
Law of Demeter
• the Law of Demeter for functions
requires that a method m of an
object O may only invoke the
methods of the following kinds of
objects:
• O itself
• m's parameters
• Any objects created / instantiated
within m
• O's direct component objects
• A global variable, accessible by
O, in the scope of m
https://commons.wikimedia.org/wiki/File:Demeter_Altemps_Inv8596.jpg
@oldJavaGuy@ZalandoTech
Testing tightly coupled code
if (articleDetailLayout.getArticleMedia() != null
&& articleDetailLayout.getArticleMedia().getThreeSixty() != null
&& articleDetailLayout.getArticleMedia().getThreeSixty()
.getImage().getMediumUrl() != null) {
articleContainer.setProduct360DegreeView(
articleDetailLayout.getArticleMedia().getThreeSixty()
.getImage().getMediumUrl());
}
• Use Mockito’s deep stubbing technique
@Test public void deepStubbing() {
ArticleDetailLayout articleDetailLayout =
mock(ArticleDetailLayout.class, RETURNS_DEEP_STUBS);
String url = "http://mysite.com/myImage.jpg";
when(articleDetailLayout.getArticleMedia().getThreeSixty().getImage()
.getMediumUrl()).thenReturn(url);
assertThat(articleDetailLayout.getArticleMedia().getThreeSixty()
.getImage().getMediumUrl(), is(url));}
@oldJavaGuy@ZalandoTech
Source: http://www.gapingvoidart.com/gallery/any-questions/
@oldJavaGuy@ZalandoTech
JOIN OUR TEAM!
github.com/zalando
@ZalandoTech
tech.zalando.com/jobs

More Related Content

What's hot

Good Practices On Test Automation
Good Practices On Test AutomationGood Practices On Test Automation
Good Practices On Test Automation
Gustavo Labbate Godoy
 
PgTAP Best Practices
PgTAP Best PracticesPgTAP Best Practices
PgTAP Best Practices
David Wheeler
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
Harry Potter
 
Java concurrency questions and answers
Java concurrency questions and answers Java concurrency questions and answers
Java concurrency questions and answers
CodeOps Technologies LLP
 
ReactJS for Programmers
ReactJS for ProgrammersReactJS for Programmers
ReactJS for Programmers
David Rodenas
 
Developer testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticDeveloper testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing Fanatic
LB Denker
 
How Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran MizrahiHow Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran Mizrahi
Ran Mizrahi
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
GeeksLab Odessa
 
Spring Certification Questions
Spring Certification QuestionsSpring Certification Questions
Spring Certification Questions
SpringMockExams
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript Testing
Thomas Fuchs
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
GeeksLab Odessa
 
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, howTomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski
 
Pyconie 2012
Pyconie 2012Pyconie 2012
Pyconie 2012Yaqi Zhao
 
Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016
Danny Preussler
 
Testing Java Code Effectively - BaselOne17
Testing Java Code Effectively - BaselOne17Testing Java Code Effectively - BaselOne17
Testing Java Code Effectively - BaselOne17
Andres Almiray
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
Ian Robertson
 

What's hot (19)

Good Practices On Test Automation
Good Practices On Test AutomationGood Practices On Test Automation
Good Practices On Test Automation
 
PgTAP Best Practices
PgTAP Best PracticesPgTAP Best Practices
PgTAP Best Practices
 
Google mock for dummies
Google mock for dummiesGoogle mock for dummies
Google mock for dummies
 
Java concurrency questions and answers
Java concurrency questions and answers Java concurrency questions and answers
Java concurrency questions and answers
 
ReactJS for Programmers
ReactJS for ProgrammersReactJS for Programmers
ReactJS for Programmers
 
Developer testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing FanaticDeveloper testing 101: Become a Testing Fanatic
Developer testing 101: Become a Testing Fanatic
 
How Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran MizrahiHow Testability Inspires AngularJS Design / Ran Mizrahi
How Testability Inspires AngularJS Design / Ran Mizrahi
 
Qunit Java script Un
Qunit Java script UnQunit Java script Un
Qunit Java script Un
 
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
QA Lab: тестирование ПО. Станислав Шмидт: "Self-testing REST APIs with API Fi...
 
Spring Certification Questions
Spring Certification QuestionsSpring Certification Questions
Spring Certification Questions
 
Adventures In JavaScript Testing
Adventures In JavaScript TestingAdventures In JavaScript Testing
Adventures In JavaScript Testing
 
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
 
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, howTomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
Tomasz Polanski - Automated mobile testing 2016 - Testing: why, when, how
 
Pyconie 2012
Pyconie 2012Pyconie 2012
Pyconie 2012
 
Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016Unit testing without Robolectric, Droidcon Berlin 2016
Unit testing without Robolectric, Droidcon Berlin 2016
 
Testing Java Code Effectively - BaselOne17
Testing Java Code Effectively - BaselOne17Testing Java Code Effectively - BaselOne17
Testing Java Code Effectively - BaselOne17
 
Java programming-examples
Java programming-examplesJava programming-examples
Java programming-examples
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Lambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive CodeLambda Chops - Recipes for Simpler, More Expressive Code
Lambda Chops - Recipes for Simpler, More Expressive Code
 

Similar to Not your father's tests

Iterative architecture
Iterative architectureIterative architecture
Iterative architecture
JoshuaRizzo4
 
Svcc Groovy Testing
Svcc Groovy TestingSvcc Groovy Testing
Svcc Groovy Testing
Andres Almiray
 
We Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End DevelopmentWe Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End Development
All Things Open
 
Atlassian Groovy Plugins
Atlassian Groovy PluginsAtlassian Groovy Plugins
Atlassian Groovy Plugins
Paul King
 
JavaParser - A tool to generate, analyze and refactor Java code
JavaParser - A tool to generate, analyze and refactor Java codeJavaParser - A tool to generate, analyze and refactor Java code
JavaParser - A tool to generate, analyze and refactor Java code
Federico Tomassetti
 
Javaone2008 Bof 5101 Groovytesting
Javaone2008 Bof 5101 GroovytestingJavaone2008 Bof 5101 Groovytesting
Javaone2008 Bof 5101 Groovytesting
Andres Almiray
 
Boosting Your Testing Productivity with Groovy
Boosting Your Testing Productivity with GroovyBoosting Your Testing Productivity with Groovy
Boosting Your Testing Productivity with GroovyJames Williams
 
Oscon Java Testing on the Fast Lane
Oscon Java Testing on the Fast LaneOscon Java Testing on the Fast Lane
Oscon Java Testing on the Fast Lane
Andres Almiray
 
Evolving The Java Language
Evolving The Java LanguageEvolving The Java Language
Evolving The Java LanguageQConLondon2008
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
roisagiv
 
Pattern Matching in Java 14
Pattern Matching in Java 14Pattern Matching in Java 14
Pattern Matching in Java 14
GlobalLogic Ukraine
 
Junit and testNG
Junit and testNGJunit and testNG
Junit and testNG
Марія Русин
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
ciklum_ods
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unit
liminescence
 
Groovy Basics
Groovy BasicsGroovy Basics
Groovy Basics
Wes Williams
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotation
javatwo2011
 
Using Rhino Mocks for Effective Unit Testing
Using Rhino Mocks for Effective Unit TestingUsing Rhino Mocks for Effective Unit Testing
Using Rhino Mocks for Effective Unit TestingMike Clement
 
Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
 Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
Yandex
 
Functional Java 8 - Introduction
Functional Java 8 - IntroductionFunctional Java 8 - Introduction
Functional Java 8 - Introduction
Łukasz Biały
 
Mastering Mock Objects - Advanced Unit Testing for Java
Mastering Mock Objects - Advanced Unit Testing for JavaMastering Mock Objects - Advanced Unit Testing for Java
Mastering Mock Objects - Advanced Unit Testing for Java
Denilson Nastacio
 

Similar to Not your father's tests (20)

Iterative architecture
Iterative architectureIterative architecture
Iterative architecture
 
Svcc Groovy Testing
Svcc Groovy TestingSvcc Groovy Testing
Svcc Groovy Testing
 
We Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End DevelopmentWe Are All Testers Now: The Testing Pyramid and Front-End Development
We Are All Testers Now: The Testing Pyramid and Front-End Development
 
Atlassian Groovy Plugins
Atlassian Groovy PluginsAtlassian Groovy Plugins
Atlassian Groovy Plugins
 
JavaParser - A tool to generate, analyze and refactor Java code
JavaParser - A tool to generate, analyze and refactor Java codeJavaParser - A tool to generate, analyze and refactor Java code
JavaParser - A tool to generate, analyze and refactor Java code
 
Javaone2008 Bof 5101 Groovytesting
Javaone2008 Bof 5101 GroovytestingJavaone2008 Bof 5101 Groovytesting
Javaone2008 Bof 5101 Groovytesting
 
Boosting Your Testing Productivity with Groovy
Boosting Your Testing Productivity with GroovyBoosting Your Testing Productivity with Groovy
Boosting Your Testing Productivity with Groovy
 
Oscon Java Testing on the Fast Lane
Oscon Java Testing on the Fast LaneOscon Java Testing on the Fast Lane
Oscon Java Testing on the Fast Lane
 
Evolving The Java Language
Evolving The Java LanguageEvolving The Java Language
Evolving The Java Language
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 
Pattern Matching in Java 14
Pattern Matching in Java 14Pattern Matching in Java 14
Pattern Matching in Java 14
 
Junit and testNG
Junit and testNGJunit and testNG
Junit and testNG
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 
Pragmatic unittestingwithj unit
Pragmatic unittestingwithj unitPragmatic unittestingwithj unit
Pragmatic unittestingwithj unit
 
Groovy Basics
Groovy BasicsGroovy Basics
Groovy Basics
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotation
 
Using Rhino Mocks for Effective Unit Testing
Using Rhino Mocks for Effective Unit TestingUsing Rhino Mocks for Effective Unit Testing
Using Rhino Mocks for Effective Unit Testing
 
Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
 Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
Making Your Own Static Analyzer Using Freud DSL. Marat Vyshegorodtsev
 
Functional Java 8 - Introduction
Functional Java 8 - IntroductionFunctional Java 8 - Introduction
Functional Java 8 - Introduction
 
Mastering Mock Objects - Advanced Unit Testing for Java
Mastering Mock Objects - Advanced Unit Testing for JavaMastering Mock Objects - Advanced Unit Testing for Java
Mastering Mock Objects - Advanced Unit Testing for Java
 

Recently uploaded

Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...
Globus
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
Ortus Solutions, Corp
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
Donna Lenk
 
top nidhi software solution freedownload
top nidhi software solution freedownloadtop nidhi software solution freedownload
top nidhi software solution freedownload
vrstrong314
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
Adele Miller
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
wottaspaceseo
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
Globus
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Globus
 
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdfEnhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
Jay Das
 
A Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdfA Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdf
kalichargn70th171
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New Zealand
IES VE
 
Understanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSageUnderstanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSage
Globus
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
Philip Schwarz
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
Tier1 app
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar
 
Enterprise Resource Planning System in Telangana
Enterprise Resource Planning System in TelanganaEnterprise Resource Planning System in Telangana
Enterprise Resource Planning System in Telangana
NYGGS Automation Suite
 
Lecture 1 Introduction to games development
Lecture 1 Introduction to games developmentLecture 1 Introduction to games development
Lecture 1 Introduction to games development
abdulrafaychaudhry
 
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoamOpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
takuyayamamoto1800
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
rickgrimesss22
 
2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx
Georgi Kodinov
 

Recently uploaded (20)

Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...Developing Distributed High-performance Computing Capabilities of an Open Sci...
Developing Distributed High-performance Computing Capabilities of an Open Sci...
 
BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024BoxLang: Review our Visionary Licenses of 2024
BoxLang: Review our Visionary Licenses of 2024
 
Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"Navigating the Metaverse: A Journey into Virtual Evolution"
Navigating the Metaverse: A Journey into Virtual Evolution"
 
top nidhi software solution freedownload
top nidhi software solution freedownloadtop nidhi software solution freedownload
top nidhi software solution freedownload
 
May Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdfMay Marketo Masterclass, London MUG May 22 2024.pdf
May Marketo Masterclass, London MUG May 22 2024.pdf
 
How Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptxHow Recreation Management Software Can Streamline Your Operations.pptx
How Recreation Management Software Can Streamline Your Operations.pptx
 
First Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User EndpointsFirst Steps with Globus Compute Multi-User Endpoints
First Steps with Globus Compute Multi-User Endpoints
 
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data AnalysisProviding Globus Services to Users of JASMIN for Environmental Data Analysis
Providing Globus Services to Users of JASMIN for Environmental Data Analysis
 
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdfEnhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
Enhancing Project Management Efficiency_ Leveraging AI Tools like ChatGPT.pdf
 
A Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdfA Comprehensive Look at Generative AI in Retail App Testing.pdf
A Comprehensive Look at Generative AI in Retail App Testing.pdf
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New Zealand
 
Understanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSageUnderstanding Globus Data Transfers with NetSage
Understanding Globus Data Transfers with NetSage
 
A Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of PassageA Sighting of filterA in Typelevel Rite of Passage
A Sighting of filterA in Typelevel Rite of Passage
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
 
Enterprise Resource Planning System in Telangana
Enterprise Resource Planning System in TelanganaEnterprise Resource Planning System in Telangana
Enterprise Resource Planning System in Telangana
 
Lecture 1 Introduction to games development
Lecture 1 Introduction to games developmentLecture 1 Introduction to games development
Lecture 1 Introduction to games development
 
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoamOpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
OpenFOAM solver for Helmholtz equation, helmholtzFoam / helmholtzBubbleFoam
 
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptxTop Features to Include in Your Winzo Clone App for Business Growth (4).pptx
Top Features to Include in Your Winzo Clone App for Business Growth (4).pptx
 
2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx2024 RoOUG Security model for the cloud.pptx
2024 RoOUG Security model for the cloud.pptx
 

Not your father's tests

  • 1. @oldJavaGuy@ZalandoTech Not your father’s tests Advanced Testing Patterns in Java (and Scala)
  • 2. @oldJavaGuy@ZalandoTech Sean P. Floyd • Full Stack Engineer @ Zalando • ~20 years experience • Java, Maven, Spring, Scala, Groovy • StackOverflow: bit.ly/spfOnSO
  • 3. @oldJavaGuy@ZalandoTech Agenda • Software Design Principles (applied to tests) • JUnit best practices • Matchers • Mocks • Extending the Lifecycle • Testing in Scala • Testing the Untestable
  • 5. @oldJavaGuy@ZalandoTech THE SOLID Principles • Single Responsibility Principle • Open / Closed Principle • Liskov Substitution Principle • Interface Segregation Principle • Dependency Inversions Principle • Michael Feathers, Uncle Bob Martin, ~2003
  • 6. @oldJavaGuy@ZalandoTech Single Responsibility Principle • Mapping production to test: • 3 levels of production code (class, method, input) • JUnit: 2 levels of test (class, method) • Only one type of change should break any given test A class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)
  • 7. @oldJavaGuy@ZalandoTech class StringUtils{ // dimension 1 // dimension 2 static String leftPad(String s, char c, int n){ if(s==null) return s; // dimension 3 a if(n <= s.length()) return s; // dimension 3 b StringBuilder sb = new StringBuilder(n); for(int i = s.length(); i < n; i++){ sb.append(c); } // dimension 3 c return sb.append(s).toString(); }
  • 8. @oldJavaGuy@ZalandoTech public class BadStringUtilsTest { /* violates single responsibility principle */ @Test public void badLeftPadTest() { assertNull(leftPad(null, ' ', 10)); assertEquals("foo", leftPad("foo", ' ', 3)); assertEquals("foo", leftPad("foo", ' ', 4)); } }
  • 9. @oldJavaGuy@ZalandoTech Open / Closed Principle • Closed for modification applies to tests also • Antipattern: Subclasses for Tests • Antipattern: package protected access • Better: Clean abstractions, mock objects “Software entities should be open for extension, but closed for modification”
  • 10. @oldJavaGuy@ZalandoTech class TightlyCoupledClass{ private final MyService ms = new MyServiceImpl(); public String foo(){ return ms.bar(); } } class TightlyCoupledClassTest{ @Test public void fooTest(){ TightlyCoupledClass t = new TCC(){ @Override public String foo(){ return "baz"; } }; assertEquals("baz", t.foo()); } }
  • 11. @oldJavaGuy@ZalandoTech final class NotSoTightlyCoupledClass{ public NSTCC(MyService ms){this.ms = ms;} private final MyService ms; public String foo(){ return ms.bar(); } } class NotSoTightlyCoupledClassTest{ @Test public void fooTest(){ MyService ms = Mockito.mock(MyService.class); NotSoTightlyCoupledClass n = new NSTCC(ms); when(ms.bar()).thenReturn("baz"); assertEquals("baz", n.foo()); } }
  • 12. @oldJavaGuy@ZalandoTech Liskov Substitution Principle • When designing class hierarchies, design corresponding test class hierarchies • If the classes have a common contract, the same is true for the test classes “Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.”
  • 13. @oldJavaGuy@ZalandoTech abstract class AbstractCollectionTest{ protected abstract <T> Collection<T> create(); @Test public void equalsTest(){ assertEquals(create(), create()); } // etc. } class ArrayListTest extends AbstractCollectionTest{ protected <T> Collection<T> create(){ return new ArrayList<>(); }} class HashSetTest extends AbstractCollectionTest{ protected <T> Collection<T> create(){ return new HashSet<>(); }}}
  • 14. @oldJavaGuy@ZalandoTech Interface Segregation Principle • If a class implements multiple interfaces, test them separately “Many client-specific interfaces are better than one general-purpose interface.”
  • 15. @oldJavaGuy@ZalandoTech class Multi implements Fooable, Barable { @Override public String foo() { return "foo"; } @Override public String bar() { return "bar"; } } class FooableMultiTest extends AbstractFooableTest { @Override protected Fooable create() { return new Multi(); } } class BarableMultiTest extends AbstractBarableTest { @Override protected Barable create(){ return new Multi(); } }
  • 16. @oldJavaGuy@ZalandoTech Dependency Inversion Principle • Test public methods, not implementation details • Test on the same level of abstraction as the implementation • Don’t change or test internal state “Depend upon Abstractions. Do not depend upon concretions.”
  • 17. @oldJavaGuy@ZalandoTech class WayTooInvolvedArrayListTest { @Test public void testAdd() throws Exception { List<String> l = new ArrayList<>(); l.add("foo"); Field f = ArrayList.class .getDeclaredField("elementData"); Object[] arr = (Object[]) f.get(l); assertEquals("foo", arr[0]); } }
  • 20. @oldJavaGuy@ZalandoTech public class TestWithStandardAssertions { private List<String> list; @Test public void exactlyOneNoneEmptyString() { assertNotNull(list); assertEquals(list.size(), 1); assertNotEquals(list.get(0).trim(), ""); } } Plain JUnit assertions
  • 21. @oldJavaGuy@ZalandoTech private List<String> list; java.lang.AssertionError at org.junit.Assert.fail(Assert.java:86) list = Arrays.asList(); java.lang.AssertionError: Expected :0 Actual :1 list = Arrays.asList(" "); java.lang.AssertionError: Values should be different. Actual: Output from plain assertions
  • 22. @oldJavaGuy@ZalandoTech Hamcrest • “Matchers that can be combined to create flexible expressions of intent” • Make tests (and failure output) readable • Work on the correct abstraction level • Built-in matchers, extendable with custom matchers
  • 23. @oldJavaGuy@ZalandoTech public class TestWithHamcrestMatchers { private List<String> list; @Test public void exactlyOneNoneEmptyString() { assertThat(list, hasSize(1)); assertThat(list, hasItem(nonEmptyString())); } }
  • 24. @oldJavaGuy@ZalandoTech // custom Matcher private Matcher<String> nonEmptyString() { return new TypeSafeMatcher<String>() { boolean matchesSafely(String s) { return !s.trim().isEmpty(); } void describeTo(Description d) { d.appendText( "a non-empty String"); } }; }
  • 25. @oldJavaGuy@ZalandoTech // Error messages for Hamcrest Matchers: private List<String> list; Expected: a collection with size <1> but: was null list = Arrays.asList(); Expected: a collection with size <1> but: collection size was <0> list = Arrays.asList(" "); Expected: a collection containing a non-empty String but: was " "
  • 26. @oldJavaGuy@ZalandoTech Dependency Inversion Principle revisited • Abstraction in its main sense is a conceptual process by which general rules and concepts are derived from the usage and classification of specific examples, literal ("real" or "concrete") signifiers, first principles, or other methods.
 Source: en.wikipedia.org/wiki/Abstraction • The hard part is finding the right level of abstraction
  • 27. @oldJavaGuy@ZalandoTech Abstraction level: too low • “Stringly typed”: • Method parameters that take strings when other more appropriate types should be used. • Message passing without using typed messages etc. • Tests will most likely have to parse custom Strings, tightly coupling them to domain knowledge blog.codinghorror.com/new-programming-jargon/
  • 28. @oldJavaGuy@ZalandoTech Abstraction level: too high • “Baklava Code”: • While thin layers are fine for a pastry, thin software layers don’t add much value, especially when you have many such layers piled on each other. Each layer has to be pushed onto your mental stack as you dive into the code. • Tests will most likely have to either set up or mock multiple layers, introducing tight coupling blog.codinghorror.com/new-programming-jargon/
  • 29. @oldJavaGuy@ZalandoTech Example: JSON • JSON (JavaScript Object Notation) is becoming the lingua franca for Web-based (Restful?) APIs • Almost every application deals with JSON in one form or another • How can we test JSON output for correctness?
  • 30. @oldJavaGuy@ZalandoTech public class ManualJsonTest { private String json = "{ "id": 123, "name": "John Smith"}"; @Test public void exactMatch() { // missing a space breaks the assertion, although the semantics // are still correct assertEquals(json, "{"id": 123, "name": "John Smith"}"); } } Bad: Manual JSON assertions False negatives through irrelevant things like whitespace or property ordering
  • 31. @oldJavaGuy@ZalandoTech public class RegexJsonTest { private String json = "{ "id": 123, "name": "John Smith"}"; @Test public void regexMatch() { // too technical, we have to effectively implement a JSON parser // which violates the single responsibility principle // also, re-ordering of properties is semantically correct // but breaks the test assertTrue(json.matches( "{s*"id"s*:s*123s*,s*"name"s*:s*"John Smith"}")); } } Bad: Regex JSON assertions False negatives through property reordering or grammar edge cases
  • 32. @oldJavaGuy@ZalandoTech public class JacksonJsonTest { private String json = "{ "id": 123, "name": "John Smith"}"; @Test public void jacksonMatch() throws IOException { // better: we no longer have to implement our own parsing JsonNode jsonNode = new ObjectMapper().readTree(json); assertTrue(jsonNode.isObject()); // but this is still more complicated than necessary: assertEquals(jsonNode.get("id").asInt(),123); assertEquals(jsonNode.get("name").asText(),"John Smith"); } } Better: Using Jackson Still: much boilerplate Non-descriptive error messages
  • 33. @oldJavaGuy@ZalandoTech JsonPath-assert A library with hamcrest-matchers for JsonPath. https://github.com/jayway/JsonPath/tree/master/json-path-assert
  • 34. @oldJavaGuy@ZalandoTech public class JaywayJsonTest { String json = "{ "id": 123, "name": "John Smith"}"; @Test public void jsonPathMatch() throws IOException { JsonAssert.with(json) .assertEquals("id", 123) .assertEquals("name", "John Smith"); } } Perfect: Using JsonAssert No boilerplate, automatic type conversion, descriptive error messages
  • 35. @oldJavaGuy@ZalandoTech Matchers (summary) • Look for existing matcher libraries • Write your own matchers in a re-usable way • start with a local factory method • if you need it in another class, move it to a dedicated helper class (e.g. XyzMatchers) • if you need it in multiple projects, make your own matcher library
  • 36. @oldJavaGuy@ZalandoTech Mocking mock: verb (gerund or present participle: mocking) 1. tease or laugh at in a scornful or contemptuous manner 2. make (something) seem laughably unreal or impossible 3. make a replica or imitation of something. pragprog.com/magazines/2010-05/the-virtues-of-mockery
  • 37. @oldJavaGuy@ZalandoTech No, really • A mock object is a testing tool that acts as a stand-in for a “real” object during testing in much the same way as an Elvis impersonator stands in for the real King. The impersonator is cheaper, easier to access, and most likely lighter weight.
 pragprog.com/magazines/2010-05/the-virtues-of-mockery • Mocking in Java can take take numerous forms: • overriding methods of the original class • providing an alternative interface implementation • creating a proxy (interface based or cglib etc.)
  • 38. @oldJavaGuy@ZalandoTech Recalling SOLID • overriding methods of the original class • violates Open / Closed principle • providing an alternative interface implementation • violates Interface segregation principle • creating a proxy (interface based or cglib etc.) • violates Dependency Inversion Principle
  • 40. @oldJavaGuy@ZalandoTech public interface UserService { Optional<Session> login( String user, String pass); } interface Session{ /* stuff in here */ } public class LoginService { private final UserService us; public boolean login(String user, String pass){ return us.login(user, pass).isPresent(); } }
  • 41. @oldJavaGuy@ZalandoTech public class LoginServiceTestWithoutMockito { @Test public void loginSuccess() { LoginService ls = new LoginService( (user, password) -> Optional.of(new Session() {})); assertTrue(ls.login("ali baba", "open sesame")); } @Test public void loginFailure() { LoginService ls = new LoginService((user, password) -> Optional.empty()); assertFalse(ls.login("ali baba", "open sesame")); } } Mocking without Mockito • if interface evolves, all tests break • code duplication, no common setup
  • 42. @oldJavaGuy@ZalandoTech public class LoginServiceTestWithMockito { UserService userService; LoginService ls; @Before public void setUp(){ userService= mock(UserService.class); ls = new LoginService(userService); } @Test public void loginSuccess() { when(userService.login(anyString(), anyString())) .thenReturn(Optional.of(mock(Session.class))); assertTrue(ls.login("ali baba", "open sesame")); verify(ls,times(1)).login("ali baba", "open sesame")); } } Mocking with Mockito • Common setup • No tight coupling to UserService and Session • Verify mock interaction (reduce false positives)
  • 43. @oldJavaGuy@ZalandoTech Advanced Mockito • All methods in a mock return default values (null for references, 0 or false for primitives) • Use Mockito.spy(object) to wrap an existing object inside a mock • Use .thenAnswer() to interact with method parameters rather than return static values • Use deep stubs to return chained mocks (huge code smell, but useful for legacy code)
  • 44. @oldJavaGuy@ZalandoTech Mocking Web APIs • Scenario: Testing API clients • Blackbox: • Faking the API calls by a 3rd party Mock Server • MockServer, WireMock • Whitebox: • Using the API’s own code to create the Mock • Example: Spring MockMvc • No actual network call, mock requests / responses
  • 45. @oldJavaGuy@ZalandoTech new MockServerClient("127.0.0.1", 1080) .when( request() .withMethod("POST").withPath("/login") .withQueryStringParameters(new Parameter("returnUrl", "/account")) .withBody(exact("{username: 'foo', password: 'bar'}")), exactly(1) ) .respond( response() .withStatusCode(401) .withHeaders( new Header("Content-Type", "application/json; charset=utf-8"), new Header("Cache-Control", "public, max-age=86400") ) .withBody("{ message: 'incorrect username and password combination' }") ); Example: MockServer • set up behavior to mimic real API • start up server via @Before method or @Rule
  • 46. @oldJavaGuy@ZalandoTech Spring Mvc Controller @RestController @RequestMapping("/contacts") public class ContactController { private final ContactRepository contactRepository; public ContactController(ContactRepository contactRepository) { this.contactRepository = contactRepository; } @RequestMapping(method = GET, value = "/{contactId}") public Contact getContact(@PathVariable int contactId) { return contactRepository.findOne(contactId); } }
  • 47. @oldJavaGuy@ZalandoTech public class ContactControllerTest { ContactRepository contactRepository; MockMvc mockMvc; @Before public void setup() { contactRepository = mock(ContactRepository.class); ContactController controller = new ContactController(contactRepository); mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); } @Test public void getContactById() throws Exception { final Contact contact = new Contact(); contact.setFirstName("Jim"); when(contactRepository.findOne(anyInt())).thenReturn(contact); mockMvc.perform(get("/contacts/123").accept(APPLICATION_JSON)) // .andExpect(status().isOk()) // .andExpect(content().contentType(APPLICATION_JSON)) // .andExpect(jsonPath("$.firstName").value("Jim")); } } MockMvc • Request / response cycle is virtual • Integrates nicely with Mockito and JsonPath
  • 49. @oldJavaGuy@ZalandoTech Basic Lifecycle • @Before / @After • instance, before / after every test • setting up / cleaning up test data • instantiating classes and mocks • @BeforeClass / @AfterClass • static, before / after entire test class • bootstrapping databases and web servers • creating and deleting temporary folders
  • 50. @oldJavaGuy@ZalandoTech Okay, but • What if I need the same kind of setup in many tests? • Move the setup to an abstract test class • If setup needs test-specific parameters, get them from abstract methods
  • 51. @oldJavaGuy@ZalandoTech Inheritance vs aggregation • This works fine for test hierarchies with a clear relation (“AbstractUserServiceTest” -> “UserServiceLoginTest”) • LSP says: common hierarchies for production and tests • Which means: we probably shouldn’t have an AbstractDBIntegrationTest, since we probably don’t have an AbstractDatabaseService • Okay, so how can we embrace DRY without inheritance?
  • 52. @oldJavaGuy@ZalandoTech Custom JUnit Runners • @RunWith(someclass) replaces JUnit’s standard Runner • Full control over entire lifecycle http://www.rockcellarmagazine.com/2012/10/01/the-man-behind-darth-vader/
  • 53. @oldJavaGuy@ZalandoTech Example Runners • SpringJunit4Runner • Ties JUnit lifecycle to Spring lifecycle • Dependency injection, transaction handling • MockitoRunner • Mocks all fields annotated with @Mock • Parameterized • Runs tests with parameters
  • 54. @oldJavaGuy@ZalandoTech @RunWith(Parameterized.class) public class FibonacciTest { @Parameters public static Collection<Object[]> data() { return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); } @Parameter(0) public int fInput; @Parameter(1) public int fExpected; @Test public void test() { assertEquals(fExpected, Fibonacci.compute(fInput)); } } Parameterized Example • Generates one test per method, per values array • Values are common for all test methods
  • 55. @oldJavaGuy@ZalandoTech • You can’t mix runners (e.g. Spring + Mockito) • Runner interface is very technical • You have to do everything http://www.quickmeme.com/meme/35n2io Runner Drawbacks
  • 57. @oldJavaGuy@ZalandoTech public static class HasTempFolder { @Rule public TemporaryFolder folder = new TemporaryFolder(); @Test public void testUsingTempFolder() throws IOException { File createdFile = folder.newFile("myfile.txt"); File createdFolder = folder.newFolder("subfolder"); // ... } } Example test rule • Rules can interact with test lifecycle • @Rule is per test, @ClassRule is per test class
  • 58. @oldJavaGuy@ZalandoTech public class NTimes implements TestRule { private final int times; private final Logger logger; public NTimes(final int times, final Logger logger) { this.times = times; this.logger = logger; } @Override public Statement apply(Statement base, Description desc) { return new Statement() { @Override public void evaluate() throws Throwable { StopWatch stopWatch = new StopWatch(); for (int i = 0; i < times; i++) { base.evaluate();} logger.info("Executed {} times in {} ms", times, stopWatch.getLastTaskTimeMillis()); }}; }} Implementing our own rule • runs every test n times, logs total duration
  • 59. @oldJavaGuy@ZalandoTech Lifecycle recap • Use @Before / @After for specific local setup • Use @Rule / @ClassRule if you want to reuse setup logic, ideally move your rules to libraries • Use custom runners for parameterized testing or Spring (but you can’t combine them) • You probably shouldn’t write a custom runner
  • 61. @oldJavaGuy@ZalandoTech Scala Test frameworks • ScalaTest • supports a plethora of testing styles • very readable DSLs • custom matcher SPI • alternative: Spec2
  • 62. @oldJavaGuy@ZalandoTech class ExampleSpec extends FlatSpec with Matchers { "A Stack" should "pop values in last-in-first-out order" in { val stack = new Stack[Int] stack.push(1) stack.push(2) stack.pop() should be (2) stack.pop() should be (1) } it should "throw NoSuchElementException if an empty stack is popped" in { val emptyStack = new Stack[Int] a [NoSuchElementException] should be thrownBy { emptyStack.pop() } } } Example from ScalaTest
  • 63. @oldJavaGuy@ZalandoTech Differences • Java: • tests are defined through annotated methods, instantiated through reflection • 2 levels of nesting (class, method) • Scala: • tests are defined programmatically, using a DSL • no isolation: runtime error in one test destroys everything • arbitrary levels of nesting • many different ways to write tests
  • 64. @oldJavaGuy@ZalandoTech class TestWithArbitraryDepth extends FunSpec{ describe("my package"){ describe("my class"){ describe("my method"){ it("should reject nulls"){ intercept[NullPointerException] { myClass.myMethod(null) } } it("should return true for non-null params"){ assert(myClass.myMethod("foo")) } } } } } Arbitrary nesting in ScalaTest
  • 65. @oldJavaGuy@ZalandoTech TestWithArbitraryDepth: my package my class my method - should reject nulls - should return true for non-null params - should return false for input 42 * FAILED Output
  • 66. @oldJavaGuy@ZalandoTech Matchers • Extend your test class with the Matchers trait • Matchers enable “readable” assertions with the “should” keyword, e.g.
 
 result should have length 3
 file should have ('name ("temp.txt")) • You can create custom matchers and combine them using a DSL
  • 67. @oldJavaGuy@ZalandoTech trait CustomMatchers { class ExtensionMatcher(expected: String) extends Matcher[java.io.File] { def apply(left: java.io.File) = { val name = left.getName MatchResult( name.endsWith(expected), s"""File $name did not end with extension "$expected"""", s"""File $name ended with extension "$expected"""" ) } } def hasExtension(extension: String) = new ExtensionMatcher(extension) } Custom matcher
  • 68. @oldJavaGuy@ZalandoTech Property-based testing with ScalaCheck • ScalaCheck is a tool for testing Scala and Java programs, based on property specifications and automatic test data generation. • Define a property that specifies the behaviour of a method or some unit of code, and ScalaCheck checks that the property holds. • All test data are generated automatically in a random fashion, so you don't have to worry about any missed cases.
  • 69. @oldJavaGuy@ZalandoTech class Fraction(n: Int, d: Int) { require(d != 0) require(d != Integer.MIN_VALUE) require(n != Integer.MIN_VALUE) val numer = if (d < 0) -1 * n else n val denom = d.abs override def toString = numer + " / " + denom } Class to test
  • 70. @oldJavaGuy@ZalandoTech class PropertySpec extends FlatSpec with PropertyChecks with Matchers { forAll { (n: Int, d: Int) => whenever(d != 0 && d != Integer.MIN_VALUE && n != Integer.MIN_VALUE) { val f = new Fraction(n, d) if (n < 0 && d < 0 || n > 0 && d > 0) f.numer should be > 0 else if (n != 0) f.numer should be < 0 else f.numer should be === 0 f.denom should be > 0 } } Property Testing (Part1)
  • 71. @oldJavaGuy@ZalandoTech val invalidCombos = Table( ("n", "d"), (Integer.MIN_VALUE, Integer.MIN_VALUE), (1, Integer.MIN_VALUE), (Integer.MIN_VALUE, 1), (Integer.MIN_VALUE, 0), (1, 0) ) forAll(invalidCombos) { (n: Int, d: Int) => evaluating { new Fraction(n, d) } should produce[IllegalArgumentException] } } Property Testing (Part2) Source: http://stackoverflow.com/a/30152387/342852
  • 72. @oldJavaGuy@ZalandoTech JUnit ScalaTest Use Hamcrest Matchers Mix in Matchers Trait @Before, @After Do it in code @Rule Mix in a trait Parameterized runner Property Testing with ScalaCheck Solving similar problems in JUnit and ScalaTest
  • 73. @oldJavaGuy@ZalandoTech What now? • Java and Scala can be combined • You can write tests for your Java code in Scala • No runtime dependencies to Scala • Scala can be integrated easily into all major build systems and IDEs
  • 74. @oldJavaGuy@ZalandoTech OK but what if I really don’t like Scala? • JUnit 4 is also kinda cool when you use Hamcrest, Mockito and Rules • JUnit 5 will come soon, supporting lots of new cool features: • New test callback mechanism • Dynamic test generation (limited) • Lambda support
  • 76. @oldJavaGuy@ZalandoTech Untestable Software checklist • Shared static state • Lack of Visibility • Deep dependency tree
  • 77. @oldJavaGuy@ZalandoTech Case Study Stripes Framework used by Zalando Shop in the early years
  • 78. @oldJavaGuy@ZalandoTech The madness • Stripes relies on static initialization in a Servlet Filter • Controllers, Localization and many other components depend on this state • Spring integration is worse:
 
 ContextLoader
 .getCurrentWebApplicationContext()
 .getBean(MyBean.class)
  • 79. @oldJavaGuy@ZalandoTech Divide and conquer • Create solutions for Stripes and Spring, separately • Make sure you tear up everything you set up • Keep low-tech hacks in one place • Provide high level facades for your tests
  • 80. @oldJavaGuy@ZalandoTech static StripesFilter stripesFilter; static MockServletContext ctx; public static void initStripes() throws Exception { if (!stripesIsAlreadyActive()) { stripesFilter = new StripesFilter(); final MockFilterConfig config = new MockFilterConfig(); config.addInitParameter("ActionResolver.Packages", "de.zalando.shop.frontend.stripes.action"); ctx = new MockServletContext(); config.setServletContext(ctx); stripesFilter.init(config); } } public static void shutdownStripes() throws Exception { if (stripesFilter != null) { stripesFilter.destroy(); stripesFilter = null; ctx = null; } } private static boolean stripesIsAlreadyActive() { final Logger stripesLogger = Logger.getLogger(StripesFilter.class); final Level oldLevel = stripesLogger.getLevel(); stripesLogger.setLevel(Level.FATAL); try { new LocalizableMessage("link.catalog") .getMessage(Locale.GERMAN); return true; } catch (final Exception e) { return false; } finally { stripesLogger.setLevel(oldLevel); } } StripesTestUtils (extract)
  • 81. @oldJavaGuy@ZalandoTech private static ConfigurableWebApplicationContext ctx; public static void registerBean(Object service, String beanName) { getOrCreateTestWebAppContext().getBeanFactory().registerSingleton(beanName, service); } private static ConfigurableWebApplicationContext setupContextLoaderTestWebApplicationContext() { try { ConfigurableWebApplicationContext cwa = new GenericWebApplicationContext(); AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor = new AutowiredAnnotationBeanPostProcessor(); autowiredAnnotationBeanPostProcessor.setBeanFactory(cwa.getBeanFactory()); cwa.getBeanFactory().addBeanPostProcessor(autowiredAnnotationBeanPostProcessor); getCurrentContextField().set(null, cwa); ctx = cwa; return cwa; } catch (Exception e) { fail(e.getMessage()); throw new IllegalStateException(); } } private static Field getCurrentContextField() throws NoSuchFieldException { final Field contextField = ContextLoader.class.getDeclaredField("currentContext"); contextField.setAccessible(true); return contextField; } private static ConfigurableWebApplicationContext getOrCreateTestWebAppContext() { WebApplicationContext currentWebApplicationContext = ContextLoader.getCurrentWebApplicationContext(); ConfigurableWebApplicationContext twa; if ((currentWebApplicationContext instanceof ConfigurableWebApplicationContext) && (currentWebApplicationContext == ctx)) { twa = (ConfigurableWebApplicationContext) currentWebApplicationContext; } else { twa = setupContextLoaderTestWebApplicationContext(); } return twa; } SpringTestUtils (extract)
  • 82. @oldJavaGuy@ZalandoTech Visibility • If you control the sources, change visibility to package-protected • If you don’t, use reflection and build high-level facades
  • 83. @oldJavaGuy@ZalandoTech Case study ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, or translating structured files.
  • 84. @oldJavaGuy@ZalandoTech • You develop a grammar, ANTLR will generate the parser class for it • Example grammar:
 
 grammar Expr; 
 prog:(expr NEWLINE)* ;
 expr:expr ('*'|'/') expr
 | expr ('+'|'-') expr
 | INT
 | '(' expr ')';
 NEWLINE : [rn]+ ;
 INT : [0-9]+ ;
  • 85. @oldJavaGuy@ZalandoTech How to unit test a grammar? • Grammars consist of rules • Let’s try to test rules separately • Each rule is a dedicated method in the parser, and it’s protected, so we have to hack it • Let’s use convention over configuration to generate test from sample files • We’ll use the file naming convention <rule>.txt and create test dynamically with ScalaTest
  • 86. @oldJavaGuy@ZalandoTech describe("Valid examples for") { baseDir.listFiles().foreach({ (f) => { val ruleName: String = f.getName.replace(".txt", "") describe(ruleName) { try { val method: Method = parserType.getDeclaredMethod(ruleName) describe("should be successfully parsed") { val source: String = new String(Files.readAllBytes(f.toPath), UTF_8) val index = new AtomicInteger(); source.split("---+").map(stripComments).foreach((src) => { val ct: Int = index.getAndIncrement() it("item[" + ct + "]") { val parser: P = parserFor(src, ruleName) try { method.invoke(parser) } catch { case e: InvocationTargetException => fail(s"$ruleName[${ct}]", e.getCause) } } }) } } catch { case e: NoSuchMethodException => { err.println(s"Bad rule name: $ruleName") } } } } }) } Generating tests dynamically
  • 88. @oldJavaGuy@ZalandoTech Law of Demeter • the Law of Demeter for functions requires that a method m of an object O may only invoke the methods of the following kinds of objects: • O itself • m's parameters • Any objects created / instantiated within m • O's direct component objects • A global variable, accessible by O, in the scope of m https://commons.wikimedia.org/wiki/File:Demeter_Altemps_Inv8596.jpg
  • 89. @oldJavaGuy@ZalandoTech Testing tightly coupled code if (articleDetailLayout.getArticleMedia() != null && articleDetailLayout.getArticleMedia().getThreeSixty() != null && articleDetailLayout.getArticleMedia().getThreeSixty() .getImage().getMediumUrl() != null) { articleContainer.setProduct360DegreeView( articleDetailLayout.getArticleMedia().getThreeSixty() .getImage().getMediumUrl()); } • Use Mockito’s deep stubbing technique @Test public void deepStubbing() { ArticleDetailLayout articleDetailLayout = mock(ArticleDetailLayout.class, RETURNS_DEEP_STUBS); String url = "http://mysite.com/myImage.jpg"; when(articleDetailLayout.getArticleMedia().getThreeSixty().getImage() .getMediumUrl()).thenReturn(url); assertThat(articleDetailLayout.getArticleMedia().getThreeSixty() .getImage().getMediumUrl(), is(url));}