kgolev.com@kotseto
Test Code
Kostadin Golev
CTO @ Rewards Labs
That will not slow you down
kgolev.com@kotseto
kgolev.com@kotseto
Tests? Who’s got time for that?
What do we have time for, anyway?
kgolev.com@kotseto
How do programmers actually
spend their time?
kgolev.com@kotseto
What programmers actually do
Understand
70%
Change
25%
Write
5%
LInk: http://blogs.msdn.com/b/peterhal/archive/2006/01/04/509302.aspx
kgolev.com@kotseto
Is this real?
Let’s verify!
kgolev.com@kotseto
What is your ideal
programming day?
kgolev.com@kotseto
How many of those days
do you get?
And how often you chase a bug for hours?
kgolev.com@kotseto
One day of work is replicated in
less than one hour
What happens in the other 7+ hours?
kgolev.com@kotseto
kgolev.com@kotseto
Can an understanding be:
• executed?
• shared?
• scaled?
kgolev.com@kotseto
How do you call an understanding that is:
• concrete
• small
• executable
• shareable
• scalable
kgolev.com@kotseto
How do you call an understanding that is:
• concrete
• small
• executable
• shareable
• scalable
we call it
a TEST
kgolev.com@kotseto
What would you automate?
Understand
70%
Change
25%
Write
5%
LInk: http://blogs.msdn.com/b/peterhal/archive/2006/01/04/509302.aspx
kgolev.com@kotseto
A test is an understanding of
the system
Understands, so you do not have to!
You can even verify it by execution
kgolev.com@kotseto
A test is not an understanding
of the system implementation
Why do double work?
kgolev.com@kotseto
For all this to work a test must
be trusted
kgolev.com@kotseto
How do you put understanding
into your tests?
How do you translate between AbstractSingletonProxyRegistrationFactoryTest
fails and “Register with Facebook is broken”?
kgolev.com@kotseto
@Test
void testRegistration() {
// Arrange | Given
UsersRepo usersRepo = Mockito.mock(UsersRepo.class);
Users users = new Users(usersRepo);
UserRequest userRequest =
new UserRequest(null, "john.doe@somemail.com", "John", "", "Doe", "asdf", null, null, 5.0);
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(5);
// Act | When
UserRequestResult result = users.register(userRequest);
// Assert | Then
assertTrue(result.isSuccessful());
assertEquals(5, result.getUserId());
}
kgolev.com@kotseto
@Test
void testRegistration() {
// Arrange | Given
UsersRepo usersRepo = Mockito.mock(UsersRepo.class);
Users users = new Users(usersRepo);
UserRequest userRequest =
new UserRequest(null, "john.doe@somemail.com", "John", "", "Doe", "asdf", null, null, 5.0);
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(5);
// Act | When
UserRequestResult result = users.register(userRequest);
// Assert | Then
assertTrue(result.isSuccessful());
assertEquals(5, result.getUserId());
}
Assert
Act
Arrange
Name
kgolev.com@kotseto
@Test
void testRegistration() {
// Arrange | Given
UsersRepo usersRepo = Mockito.mock(UsersRepo.class);
Users users = new Users(usersRepo);
UserRequest userRequest =
new UserRequest(null, "john.doe@somemail.com", "John", "", "Doe", "asdf", null, null, 5.0);
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(5);
// Act | When
UserRequestResult result = users.register(userRequest);
// Assert | Then
assertTrue(result.isSuccessful());
assertEquals(5, result.getUserId());
}
kgolev.com@kotseto
testRegistration?
registration_withMandatoryFields_isSuccessful
kgolev.com@kotseto
whatWeTest_whenCondition_doesThat
registration_withMandatoryFields_isSuccessful
kgolev.com@kotseto
whatWeTest_whenCondition_doesThat
registration_withMandatoryFields_isSuccessful
registration_withFacebook_isSuccessful
registration_withMissingEmail_returnsError
kgolev.com@kotseto
What is easier to read?
kgolev.com@kotseto
@DisplayName
kgolev.com@kotseto
@DisplayName("Registration with mandatory fields ✅")
@Test
void registration_withMandatoryFields_isSuccessful() {
}
kgolev.com@kotseto
Or even:
kgolev.com@kotseto
Also on the JVM:
Spock (Groovy)
Spek (Kotlin)
ScalaTest (Scala)
kgolev.com@kotseto
class UsersTest: Spek ({
describe("users") {
Users users = new Users(...
it("should register with mandatory fields") {
...
}
it("should fail with email missing") {
...
}
}
})
kgolev.com@kotseto
describe("users") {
…
it("should register with mandatory fields") {
...
}
Describe what
kgolev.com@kotseto
What about assertions?
kgolev.com@kotseto
junit.framework.AssertionFailedError
at junit.framework.Assert.fail(Assert.java:55)
at junit.framework.Assert.assertTrue(Assert.java:22)
at junit.framework.Assert.assertTrue(Assert.java:31)
at junit.framework.TestCase.assertTrue(TestCase.java:201)
What?
kgolev.com@kotseto
junit.framework.AssertionFailedError: registration should be successful
…
Better now?
kgolev.com@kotseto
assertTrue(“registration should be successful", true);
junit.framework.AssertionFailedError: registration should be successful
…
kgolev.com@kotseto
assertEquals(5, result.getUserId(), "id for the created user should be returned”);
org.opentest4j.AssertionFailedError: id for the created user should be returned ==>
Expected :4
Actual :5
kgolev.com@kotseto
// Arrange | Given
…
UserRequest userRequest =
new UserRequest(null, "john.doe@somemail.com",
"John", "", "Doe", "asdf", null, null, 5.0);
…
// Act | When
// Assert | Then
What about these?
kgolev.com@kotseto
// Arrange | Given
…
UserRequest userRequest = …
when(usersRepo.createUser(userRequest)).thenReturn(5);
// Act | When
// Assert | Then
assertEquals(5, result.getUserId());
Or these?
kgolev.com@kotseto
// Arrange | Given
…
UserRequest userRequest = …
when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// Act | When
// Assert | Then
assertEquals(USER_ID, result.getUserId());
No Magic
kgolev.com@kotseto
// Arrange | Given
…
UserRequest userRequest =
new UserRequest(NO_AUTH_TOKEN, EMAIL, FIRST_NAME,
NO_MID_NAME, LAST_NAME, PASSWORD_HASH, NO_PHONE,
NO_ADDRESS, SIGN_UP_BONUS);
…
// Act | When
// Assert | Then
No Magic
kgolev.com@kotseto
UserRequest userRequestWithMandatoryParameters =
new UserRequest(EMAIL, FIRST_NAME, LAST_NAME,
PASSWORD_HASH, SIGN_UP_BONUS);
// Or just a method?
Wasn’t this register_withMandatoryParameters?
kgolev.com@kotseto
Remove noise
kgolev.com@kotseto
assertTrue(result.isSuccessful());
assertEquals(USER_ID, result.getUserId());
UserRequestResult expected =
new UserRequestResult(SUCCESSFUL, USER_ID);
assertEquals(expected, actual);
Remove noise
kgolev.com@kotseto
Mockito.verify(userRepo).createUser(userRequest);
assertNotNull(actual);
assertEquals(expected, actual);
Mockito.verifyNoMoreInteractions(userRepo);
Mockito.verifyZeroInteractions(otherDependency);
Remove noise
kgolev.com@kotseto
Assert one thing, in one test
Do not assert the same thing in different tests
No “let’s be on the safe side”
kgolev.com@kotseto
Your tests will fail. Make them
easy to read and understand
When in doubt, prefer more verbose/readable test code
It will save you tons of time
kgolev.com@kotseto
10% optimisation
Saved time
7%
Understand
63%
Change
25%
Write
5%
LInk: http://blogs.msdn.com/b/peterhal/archive/2006/01/04/509302.aspx
kgolev.com@kotseto
Is it free?
More tests == more code
kgolev.com@kotseto
A test should be an understanding of your
system behaviour
A test should not be an understanding of
your system implementation
kgolev.com@kotseto
The best place to understand
your production code is your
production code
Tests that know about your implementation details are
double work
kgolev.com@kotseto
When you change the
implementation the test will fail
Behaviour stayed the same
kgolev.com@kotseto
A test needs to know what?
DANGER ZONE
kgolev.com@kotseto
UserRequestResult register(UserRequest userRequest) {
// some validation here
int userId = usersRepo.createUser(userRequest);
return new UserRequestResult(userId);
}
Let’s look inside
kgolev.com@kotseto
// make sure userRepo returns some userId for given parameters
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// execute what we want to test
UserRequestResult actual = users.register(userRequest);
// verify call to userRepo was OK
Mockito.verify(usersRepo).createUser(userRequest);
// construct expected UserRequestResult
UserRequestResult expected = new UserRequestResult(SUCCESSFUL,
USER_ID);
// assert expected is the same as the actual
assertEquals(expected, actual);
Let’s Follow The Path
kgolev.com@kotseto
// make sure userRepo returns some userId for given parameters
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// execute what we want to test
UserRequestResult actual = users.register(userRequest);
// verify call to userRepo was OK
Mockito.verify(usersRepo).createUser(userRequest);
// construct expected UserRequestResult
UserRequestResult expected = new UserRequestResult(SUCCESSFUL,
USER_ID);
// assert expected is the same as the actual
assertEquals(expected, actual);
Let’s Follow The Path
kgolev.com@kotseto
// make sure userRepo returns some userId for given parameters
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// execute what we want to test
UserRequestResult actual = users.register(userRequest);
// verify call to userRepo was OK
Mockito.verify(usersRepo).createUser(userRequest);
// construct expected UserRequestResult
UserRequestResult expected = new UserRequestResult(SUCCESSFUL,
USER_ID);
// assert expected is the same as the actual
assertEquals(expected, actual);
Let’s Follow The Path
kgolev.com@kotseto
// make sure userRepo returns some userId for given parameters
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// execute what we want to test
UserRequestResult actual = users.register(userRequest);
// verify call to userRepo was OK
Mockito.verify(usersRepo).createUser(userRequest);
// construct expected UserRequestResult
UserRequestResult expected =
new UserRequestResult(SUCCESSFUL, USER_ID);
// assert expected is the same as the actual
assertEquals(expected, actual);
Let’s Follow The Path
kgolev.com@kotseto
Test should not verify your
implementation path
kgolev.com@kotseto
// make sure userRepo returns some userId for given parameters
Mockito.when(usersRepo.createUser(userRequest)).thenReturn(USER_ID);
// execute what we want to test
UserRequestResult actual = users.register(userRequest);
// verify call to userRepo was OK
Mockito.verify(usersRepo).createUser(userRequest);
// construct expected UserRequestResult
UserRequestResult expected = new UserRequestResult(SUCCESSFUL,
USER_ID);
// assert expected is the same as the actual
assertEquals(expected, actual);
Do you need that?
kgolev.com@kotseto
Do not use verify() if
you do not really need it
Use Mock Object judiciously by Daniela Kolarova
https://dzone.com/articles/use-mock-objects-judiciously
kgolev.com@kotseto
Try writing the test first
no implementation -> no implementation details!
kgolev.com@kotseto
Implementation dependency
cannot be avoided completely
Your
code
Intention Implementation
kgolev.com@kotseto
Can we ease the pain?
Let’s see how
kgolev.com@kotseto
kgolev.com@kotseto
kgolev.com@kotseto
kgolev.com@kotseto
Tests are often a tree
How does that help?
kgolev.com@kotseto
We change UsersRepo class
kgolev.com@kotseto
We change UsersRepo class
why so many changes in UsersTest?
2 lines production code
9 lines of test code!
kgolev.com@kotseto
@DisplayName("Users registration")
class UsersTest {
@Test
void whenUserDoesNotExist_withMandatory_shouldSucceed() {}
@Test
void whenUserDoesNotExist_withSignUpBonus_should_assign_it() {}
@Test
void whenUserDoesNotExist_whenRegisteredWithFacebook_shouldRecordThat() {}
@Test
void whenUserExists_shouldReturnAlreadyRegistered () {}
kgolev.com@kotseto
@Nested
Less code to maintain, easier to read and understand
kgolev.com@kotseto
@DisplayName("Users registration")
class UsersTest {
@Nested
@DisplayName("when user does not exist")
class whenUserDoesNotExists {
@Test
void withMandatory_shouldSucceed() {}
@Test
void withSignUpBonus_should_assign_it() {}
@Test
void whenRegisteredWithFacebook_shouldRecordThat() {}
}
@Test
void whenUserExists_shouldReturnAlreadyRegistered () {}
kgolev.com@kotseto
@DisplayName("Users registration")
class UsersTest {
private UsersRepo usersRepo;
private Users users;
@BeforeEach // <- executed first
void setUp() {
usersRepo = mock(UsersRepo.class);
users = new Users(usersRepo);
}
@DisplayName("when user does not exist")
@Nested
class whenUserDoesNotExists {
@BeforeEach // <- executed second
void setUp() {}
kgolev.com@kotseto
Place for all common setup
kgolev.com@kotseto
@BeforeEach
void setUp() { … }
@DisplayName("when user does not exist")
@Nested
class whenUserDoesNotExists {
@BeforeEach
void setUpUserDoesNotExist() { … }
@Test
void whenUserDoesNotExists_withMandatory_shouldSucceed() {
UserRequest userRequest = …;
UserRequestResult actual = users.register(userRequest);
UserRequestResult expected = …;
assertEquals(expected, actual);
}
Much simpler arrange | given now
kgolev.com@kotseto
If we make the same change again
2 lines production code
5 lines (from 9) of test code
kgolev.com@kotseto
@RunWith(HierarchicalContextRunner.class)
Requires JUnit 4.12

Cannot be combined with other runner
IDE support limited compared to JUnit 5
kgolev.com@kotseto
What about UsersRepoTest?
Can we ease the pain there as well?
kgolev.com@kotseto
What if code we test is a pure
function?
How about some email validation?
kgolev.com@kotseto
john.doe@gmail.com
@gmail.com
john@doe@mail@com
john.@mail@com
john.doe@mail.co.uk
kgolev.com@kotseto
@Test
public void validEmail_isValid() {
boolean isValid =
emailValidation.isValid("john.doe@gmail.com");
assertTrue(isValid);
}
@Test
public void missingUsername_isNotValid() {
boolean isValid = emailValidation.isValid("@gmail.com");
assertTrue(!isValid);
}
…
// 3 similar tests here
kgolev.com@kotseto
public void mailValidation(String email, boolean expected) {
boolean actual = emailValidation.isValid(email);
assertEquals(expected, actual);
}
What we actually need
kgolev.com@kotseto
public void mailValidation(String email, boolean expected) {
boolean actual = emailValidation.isValid(email);
assertEquals(expected, actual);
}
Why not just call it from our tests?
Still have to maintain all those one liner tests
kgolev.com@kotseto
@RunWith(Parameterized.class)
public class EmailValidationTest {
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ "john.doe@gmail.com", true},
{ "@gmail.com", false },
{ "john@doe@mail@com", true },
{ "john.@mail@com", false },
{ "john.doe@mail.co.uk", true }
});
}
@Parameterized.Parameter(0)
public String email;
@Parameterized.Parameter(1)
public boolean expected;
kgolev.com@kotseto
@Test
public void mailValidation() {
boolean actual = emailValidation.isValid(email);
assertEquals(expected, actual);
}
After this setup we can write
kgolev.com@kotseto
@ParameterizedTest
@ValueSource(strings = {
"john.doe@gmail.com",
"john.doe@mail.co.uk" })
void validEmails_areValid(String email) {
assertEquals(VALID, emailValidation.isValid(email));
}
kgolev.com@kotseto
enum Status {
OK, INVALID_NAME, INVALID_DATE, …
}
@ParameterizedTest
@EnumSource(value = Status.class, mode = EXCLUDE, names = “OK")
void whenNotOK_shouldReturnError(Status status) {
…
Other ways to provide parameters
Or get them from a method or even csv
kgolev.com@kotseto
@Annotations
@Unstopable
@Progress
annotationmania.com
kgolev.com@kotseto
describe("Email validation") {
val emailValidation = EmailValidation();
listOf(
"john.doe@gmail.com",
"john@mail.co.uk"
).forEach { email ->
describe("validating email $email") {
it("is valid") {
assertTrue(emailValidation.isValid(email))
}
}
}
}
kgolev.com@kotseto
listOf(
"john.doe@gmail.com",
"john@mail.co.uk"
).forEach { email ->
describe("validating email $email") {
it("is valid") {}
}
}
}
kgolev.com@kotseto
Also great: Spock for Groovy
kgolev.com@kotseto
Does this solve the problem?
2 lines production code
3 lines of test code
kgolev.com@kotseto
Nested and Parameterized tests
Less code to maintain and understand
Faster development
kgolev.com@kotseto
How do you make tests that
you trust?
Isn’t easy to understand and maintain enough already?
kgolev.com@kotseto
“All Models are wrong, some are useful” — George Box
source: https://martinfowler.com/articles/practical-test-pyramid.html
kgolev.com@kotseto
Small tests - small problems
Like children
kgolev.com@kotseto
Why we need higher level tests
source: https://chriskottom.com/
kgolev.com@kotseto
The higher you go,
the harder to know where the
problem is
For every high level test that fails, there should be a
lower level one that also fails and points at the issue
kgolev.com@kotseto
If you find a bug with a higher level test
Write a smaller unit one, so you know where to focus
immediately next time
kgolev.com@kotseto
Let’s wrap it up
kgolev.com@kotseto
Automate your understanding
kgolev.com@kotseto
Automate your understanding
Not your implementation details
kgolev.com@kotseto
Automate your understanding
Not your implementation details
Automate at the lowest possible level
kgolev.com@kotseto
Questions?
kgolev.com/talks/automate-your-understanding
kgolev.com@kotseto
This was my understanding
Thank you!
kgolev.com/talks/automate-your-understanding

Test code that will not slow you down