Top Banner
Developer Tests Things to Know Vaidas Pilkauskas 2014 Vilnius JUG [email protected]
103
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Developer Tests - Things to Know (Vilnius JUG)

Developer TestsThings to Know

Vaidas Pilkauskas 2014 Vilnius JUG

[email protected]

Page 2: Developer Tests - Things to Know (Vilnius JUG)

meMy hobbies● developer at Wix.com● main language - Scala● main professional interest - developer communities

If there is time left after my hobbies● mountain bicycle rider, snowboarder ● consumer of rock music, contemporary art, etc

Page 3: Developer Tests - Things to Know (Vilnius JUG)

me - how to contact meconnect with me on LinkedIn http://lt.linkedin.com/pub/vaidas-pilkauskas/8/77/863/

add me on G+ https://www.google.com/+VaidasPilkauskas

follow on Twitter @liucijus

Page 4: Developer Tests - Things to Know (Vilnius JUG)

“We couldn’t understand why people without technical knowledge had to tell programmers “what” to do and, furthermore, they had to supervise “how” programmers did it.”

Cristian Rennellahttp://qz.com/260846/why-our-startup-has-no-bosses-no-office-and-a-four-day-work-week/

Page 5: Developer Tests - Things to Know (Vilnius JUG)

What this talk is about

● Things we argue about during code reviews● Things that took me time to understand and

prove that they are actually good way to go● Small things we have no time to discuss

during big talks

Page 6: Developer Tests - Things to Know (Vilnius JUG)
Page 7: Developer Tests - Things to Know (Vilnius JUG)

“Legacy Code is code without Tests”

Michael FeathersWorking Effectively with Legacy Code

Page 8: Developer Tests - Things to Know (Vilnius JUG)

So what is test?

It is system’s exercise under predefined conditions and then verification of an expected outcome.

Page 9: Developer Tests - Things to Know (Vilnius JUG)

Thing #1Test structure

Page 10: Developer Tests - Things to Know (Vilnius JUG)

Test phases

SetupExercise

VerifyTeardown

Page 11: Developer Tests - Things to Know (Vilnius JUG)

Test phases in code Server server = new NotesServer(); // setup

Note note = new Note("test note"); // setup

Status status = server.add(note); // exercise

assertEquals(SUCCESS, status); // verify

server.shutdown(); // teardown

Page 12: Developer Tests - Things to Know (Vilnius JUG)

Start everything in one method@Test

public void serverShouldAddNoteSuccessfully() {

Server server = new NotesServer(); // setup

Note note = new Note("test note"); // setup

Status status = server.add(note); // exercise

assertEquals(SUCCESS, status); // verify

server.shutdown(); // teardown

}

Page 13: Developer Tests - Things to Know (Vilnius JUG)

Refactor to lifecycle methods@Before public void before() {

server = new NotesServer(); // setup

note = new Note("test note"); // setup

}

@Test public void serverShouldAddNoteSuccessfully() {

Status status = server.add(note); // exercise

assertEquals(SUCCESS, status); // verify

}

@After public void after() {

server.shutdown(); // teardown

}

Page 14: Developer Tests - Things to Know (Vilnius JUG)

DON’Ts - #1

Do not start from setup/teardown

Page 15: Developer Tests - Things to Know (Vilnius JUG)

DON’Ts - #2

Do not mix setup code from different tests - shared code must be relevant to

the tests that use it

Page 16: Developer Tests - Things to Know (Vilnius JUG)

DON’Ts - #3

Setup and teardown are there to solve DRY problem and to help structure your

test code

Page 17: Developer Tests - Things to Know (Vilnius JUG)

How many colors are there in the picture?

Source: Josef Albers Interaction of Color

Page 18: Developer Tests - Things to Know (Vilnius JUG)

Context matters!

Source: Josef Albers Interaction of Color

Page 19: Developer Tests - Things to Know (Vilnius JUG)

Typical setup inside @Before@Before public void before() {

server = new NotesServer(); // setup

note = new Note("test note"); // setup

}

@Test public void serverShouldAddNoteSuccessfully() {

Status status = server.add(note); // exercise

assertEquals(SUCCESS, status); // verify

}

@After public void after() { server.shutdown(); // teardown }

Page 20: Developer Tests - Things to Know (Vilnius JUG)

Setup pollution - #1

● Every line in setup must be relevant to all tests.

Page 21: Developer Tests - Things to Know (Vilnius JUG)

Setup pollution - #2

● It is tempting to add additional setup tuning just to fix/enhance one test.

Page 22: Developer Tests - Things to Know (Vilnius JUG)

Setup pollution - #3

● Rich setup makes tests slow!

Page 23: Developer Tests - Things to Know (Vilnius JUG)

Setup pollution - #4

● Setup is to bootstrap your SUT.

Page 24: Developer Tests - Things to Know (Vilnius JUG)

Setup pollution - #5

● Setup hides details! Do not hide test preconditions.

Page 25: Developer Tests - Things to Know (Vilnius JUG)

Setting up “job” inside test method@Before public void before() {

server = new NotesServer(); // setup

}

@Test public void serverShouldAddNoteSuccessfully() {

note = new Note("test note"); // setup

Status status = server.add(note); // exercise

assertEquals(SUCCESS, status); // verify

}

Page 26: Developer Tests - Things to Know (Vilnius JUG)

But then be more specific@Test

public void serverShouldAddSingleLineNoteSuccesfully() {

// * set up which is actual for the current method

// * use scope specific name

Note singleLineNote = new Note("test note"); // setup

Status status = server.add(singleLineNote); // exercise

assertEquals(SUCCESS, status); // verify

}

Page 27: Developer Tests - Things to Know (Vilnius JUG)

Give good names to setup methods

@Before public void createNotesServer() { server = new NotesServer(); }

Page 28: Developer Tests - Things to Know (Vilnius JUG)

Summary of test code organization

● DRY principle.● Readability. BDD vs. DRY● Consistency. Maintain the same style across

your codebase. ● Complexity. It may dictate the way you go.

Page 29: Developer Tests - Things to Know (Vilnius JUG)

Refactoring

Refactoring is about improving the design of existing code. It is the process of changing a software system in such a way that it does not alter the external behavior of the code, yet improves its internal structure.

Martin FowlerRefactoring: Improving the Design of Existing Code

Page 30: Developer Tests - Things to Know (Vilnius JUG)

Thing #2What do we test?

Page 31: Developer Tests - Things to Know (Vilnius JUG)

Test behaviour not methods

● Think of a contract

Page 32: Developer Tests - Things to Know (Vilnius JUG)

Test behaviour not methods

● Think of a contract ● And responsibilities

Page 33: Developer Tests - Things to Know (Vilnius JUG)

Test behaviour not methods

● Think of a contract ● And responsibilities● Specify requirements as tests

Page 34: Developer Tests - Things to Know (Vilnius JUG)

Test behaviour not methods

● Think of a contract ● And responsibilities● Specify requirements as tests● Happens naturally when done in test-first

approach

Page 35: Developer Tests - Things to Know (Vilnius JUG)

Thing #3Matchers

Page 36: Developer Tests - Things to Know (Vilnius JUG)

Matchers

● Enhance readability● Asserts on the right level of abstraction● Encapsulate testing logic● Reusable● Detailed match error messages (do not

leave them out in your custom matchers!)

Page 37: Developer Tests - Things to Know (Vilnius JUG)

Matchers

● Enhance readability● Asserts on the right level of abstraction● Encapsulate testing logic● Reusable● Detailed match error messages (do not

leave them out in your custom matchers!)

Page 38: Developer Tests - Things to Know (Vilnius JUG)

Matcher libraries

● Hamcrest - standard matcher lib for JUnit● AssertJ - fluent assertions (IDE friendly)

● Provides common matchers● You can write your own custom matchers

Page 39: Developer Tests - Things to Know (Vilnius JUG)

HamcrestassertThat(frodo.getName(), equalTo("Frodo"));

assertThat(frodo.getName(), is(equalTo("Frodo")));

assertThat(frodo.getName(), is("Frodo"));

Page 40: Developer Tests - Things to Know (Vilnius JUG)

AssertJassertThat(frodo.getName()).isEqualTo("Frodo");

assertThat(frodo).isNotEqualTo(sauron).isIn(fellowshipOfTheRing);

assertThat(sauron).isNotIn(fellowshipOfTheRing);

Page 41: Developer Tests - Things to Know (Vilnius JUG)

Thing #4Custom matchers

Page 42: Developer Tests - Things to Know (Vilnius JUG)

Custom matchers

Are matchers that we write specifically for our projects.

Page 43: Developer Tests - Things to Know (Vilnius JUG)

Custom matchers

● Help communicate test intention● Abstract assertion logic in case standard

matchers are not enough● Are reusable and save time in large projects● You may have a custom message to be

more specific about test failure

Page 44: Developer Tests - Things to Know (Vilnius JUG)

Custom matchers

● Help communicate test intention● Abstract assertion logic in case standard

matchers are not enough● Are reusable and save time in large projects● You may have a custom message to be

more specific about test failure

Page 45: Developer Tests - Things to Know (Vilnius JUG)

Custom matchers@Test

public void shouldHaveIsbnGenerated() {

Book book = new Book(1l, "5555", "A book");

assertThat(book, hasIsbn("1234"));}

Page 46: Developer Tests - Things to Know (Vilnius JUG)

Thing #5Failing a test

Page 47: Developer Tests - Things to Know (Vilnius JUG)

fail()

In some cases (e.g. testing exceptions) you may want to force test to fail if some expected situation does not happen

Page 48: Developer Tests - Things to Know (Vilnius JUG)

fail()

try { // do stuff... fail("Exception not thrown");} catch(Exception e){ assertTrue(e.hasSomeFlag());}

Page 49: Developer Tests - Things to Know (Vilnius JUG)

fail()

● Fundamentally not bad, but better use matchers for expected failure

● Matchers help to clarify test intention● Don’t forget - expected behaviour is an

opposite of a failing test

Page 50: Developer Tests - Things to Know (Vilnius JUG)

Thing #6Anti-pattern: The Ugly Mirror

Page 51: Developer Tests - Things to Know (Vilnius JUG)

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Vilkas", "Pilkas");

String expected =

"Person[" + person.getName() + " " + person.getSurname() + "]"

assertEquals(expected, person.toString());

}

Page 52: Developer Tests - Things to Know (Vilnius JUG)

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Vilkas", "Pilkas");

String expected =

"Person[" + person.getName() + " " + person.getSurname() + "]"

assertEquals(expected, person.toString());

}

Page 53: Developer Tests - Things to Know (Vilnius JUG)

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Vilkas", "Pilkas");

assertEquals("Person[Vilkas Pilkas]", person.toString());

}

Page 54: Developer Tests - Things to Know (Vilnius JUG)

Thing #7How to turn off the test?

Page 55: Developer Tests - Things to Know (Vilnius JUG)

Why would you want to turn off the test?

Page 56: Developer Tests - Things to Know (Vilnius JUG)

Why would you want to turn off the test?

Well, because it fails… :)

Page 57: Developer Tests - Things to Know (Vilnius JUG)

Ignoring tests

● Always use ignore/pending API from your test library (JUnit @Ignore)

Page 58: Developer Tests - Things to Know (Vilnius JUG)

Ignoring tests

● Always use ignore/pending API from your test library (JUnit @Ignore)

● Do not comment out or false assert your test

Page 59: Developer Tests - Things to Know (Vilnius JUG)

Ignoring tests

● Always use ignore/pending API from your test library (JUnit @Ignore)

● Do not comment out or false assert your test● If you do not need a test - delete it

Page 60: Developer Tests - Things to Know (Vilnius JUG)

Thing #8What to do with exceptions?

Page 61: Developer Tests - Things to Know (Vilnius JUG)

Exceptions

● If you can, use matchers instead of○ @Test(expected=?)○ try-catch approach

Page 62: Developer Tests - Things to Know (Vilnius JUG)

JUnit expected exception@Test(expected=IndexOutOfBoundsException.class)public void shouldThrowIndexOutOfBoundsException() { ArrayList emptyList = new ArrayList(); Object o = emptyList.get(0);}

//matcher in Specs2 (Scala)

server.process(None) must throwA[NothingToProccess]

Page 63: Developer Tests - Things to Know (Vilnius JUG)

try and catch

public void shouldThrowIndexOutOfBoundsException() { ArrayList emptyList = new ArrayList();

try { Object o = emptyList.get(0);

fail("Should throw IndexOutOfBoundsException");

} catch(IndexOutOfBoundsException e)){

//consider asserting message!

}}

Page 64: Developer Tests - Things to Know (Vilnius JUG)

Exceptions

● catch-exception lib

Page 65: Developer Tests - Things to Know (Vilnius JUG)

catch-exception libList myList = new ArrayList();

catchException(myList).get(1);

assertThat(caughtException(),

allOf(

is(IndexOutOfBoundsException.class),

hasMessage("Index: 1, Size: 0"),

hasNoCause()

)

);

Page 66: Developer Tests - Things to Know (Vilnius JUG)

Exceptions

● What about ExpectedException Rule?○ My personal opinion - not that intuitive○ breaks arrange/act/assert flow

Page 67: Developer Tests - Things to Know (Vilnius JUG)

ExpectedException rule@Rule public ExpectedException exception = ExpectedException.none();

@Testpublic void testExpectedException() { exception.expect(IllegalArgumentException.class); exception.expectMessage(containsString('Invalid age')); new Person('Vilkas', -1);}

//Person constructor

public Person(String name, int age) { if (age <= 0) throw new IllegalArgumentException('Invalid age:' + age);

// ...

}

Page 68: Developer Tests - Things to Know (Vilnius JUG)

Thing #9Testing with time

Page 69: Developer Tests - Things to Know (Vilnius JUG)

Problempublic class MyService {

...

public void process(LocalDate date) { if (date.isBefore(LocalDate.now()) { ... } }

}

Page 70: Developer Tests - Things to Know (Vilnius JUG)

Testing with Time

● Design your system where time is a collaborator

● Inject test specific time provider in your test○ constant time○ slow time○ boundary cases time

Page 71: Developer Tests - Things to Know (Vilnius JUG)

Control time with Clockpublic class MyService { private Clock clock; // dependency inject

...

public void process(LocalDate date) { if (date.isBefore(LocalDate.now(clock)) { ... } }

}

Page 72: Developer Tests - Things to Know (Vilnius JUG)

Thing #10Collections

Page 73: Developer Tests - Things to Know (Vilnius JUG)

Collections - multiple properties to assert

● Is null?● Size● Order● Content

Page 74: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content

Page 75: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching

Page 76: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions

Page 77: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions● Do not sort just because it is easier to assert!

Page 78: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions● Do not sort just because it is easier to assert!● Multiple assertions are worse than single content

assertion

Page 79: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions● Do not sort just because it is easier to assert!● Multiple assertions are worse than single content

assertion● Unless you want to say something important in your test!

Page 80: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions● Do not sort just because it is easier to assert!● Multiple assertions are worse than single content

assertion● Unless you want to say something important in your test!● Use matchers!

Page 81: Developer Tests - Things to Know (Vilnius JUG)

Collections● Most of the time you want to assert on collection content● Prefer exact content matching● Avoid incomplete assertions● Do not sort just because it is easier to assert!● Multiple assertions are worse than single content

assertion● Unless you want to say something important in your test!● Use matchers!

Page 82: Developer Tests - Things to Know (Vilnius JUG)

Thing #11Random values

Page 83: Developer Tests - Things to Know (Vilnius JUG)

Random values in tests

● Most of the time you do not want it

Page 84: Developer Tests - Things to Know (Vilnius JUG)

Random values in tests

● Most of the time you do not want it● Unless you depend on randomness a lot (eg.

password generation*)

*Thanks to Aleksandar Tomovski for a good example

Page 85: Developer Tests - Things to Know (Vilnius JUG)

Random values in tests

● Most of the time you do not want it● Unless you depend on randomness a lot● Use property based testing (which is also

hard)

Page 86: Developer Tests - Things to Know (Vilnius JUG)

Random values in tests

● Most of the time you do not want it● Unless you depend on randomness a lot● Use property based testing (which is also

hard)● Do not make dummy values random

Page 87: Developer Tests - Things to Know (Vilnius JUG)

What if we still need random cases?

Page 88: Developer Tests - Things to Know (Vilnius JUG)

Generate Multiple Test Cases

● Quality over quantity

Page 89: Developer Tests - Things to Know (Vilnius JUG)

Generate Multiple Test Cases

● Quality over quantity● Think of boundary cases, that you may want

to detect with random test

Page 90: Developer Tests - Things to Know (Vilnius JUG)

Generate Multiple Test Cases

● Quality over quantity● Think of boundary cases, that you may want

to detect with random test● Use parameterized tests

Page 91: Developer Tests - Things to Know (Vilnius JUG)

Generate Multiple Test Cases

● Quality over quantity● Think of boundary cases, that you may want

to detect with random test● Use parameterized tests● Random is hard to repeat

Page 92: Developer Tests - Things to Know (Vilnius JUG)

Generate Multiple Test Cases

● Quality over quantity● Think of boundary cases, that you may want

to detect with random test● Use parameterized tests● Random is hard to repeat● Flickering tests

Page 93: Developer Tests - Things to Know (Vilnius JUG)

Thing #12How many assertions per test?

Page 94: Developer Tests - Things to Know (Vilnius JUG)

How many assertions per test?

● Unit test - one assertion per test. Must be clear and readable

● Proper test should fail for exactly one reason● End to end - best case one assertion per

test, but more are allowed● Consider custom matchers

Page 95: Developer Tests - Things to Know (Vilnius JUG)

How many assertions per test?

● Unit test - one assertion per test. Must be clear and readable

● Proper test should fail for exactly one reason

● End to end - best case one assertion per test, but more are allowed

● Consider custom matchers

Page 96: Developer Tests - Things to Know (Vilnius JUG)

Thing #13Decoupling in End-to-end tests

Page 97: Developer Tests - Things to Know (Vilnius JUG)

What can be better in this test [pseudocode]

@Test shouldRetrieveUserByLogin() {

String userJson = "{\"username\": \"vaidas\"}";

HttpRequest post = new Post("https://localhost:8080/users", userJson);

HttpResponse postResp = HttpClient().execute(post);

assertThat(postResp.status, is(200));

HttpRequest get = new Get("https://localhost:8080/users/vaidas");

HttpResponse getResp = HttpClient().execute(get);

User user = mapper.readValue(getResp, User.class);

assertThat(username, is("vaidas"));

}

Page 98: Developer Tests - Things to Know (Vilnius JUG)

Decoupling from low level details [pseudocode]

@Test shouldRetrieveUserByUsername() {

CreateUserResponse createResp = aCreateUserRequest().withUsername("vaidas").execute();

assertThat(createResp, hasStatus(OK));

GetUserResponse getResp = aGetUserRequest().withUsername("vaidas").execute();

assertThat(getResp, allOf(hasStatus(OK), hasUsername("vaidas")));

}

Page 99: Developer Tests - Things to Know (Vilnius JUG)

Thoughts on end-to-end testing

● What is real e2e? REST vs. UI● Testing by using system itself ● Vs. using low level DB driver to verify data

persistence

Page 100: Developer Tests - Things to Know (Vilnius JUG)

Thing #14Comments

Page 101: Developer Tests - Things to Know (Vilnius JUG)

Comments in Test code

● Fundamentally good option to explain complicated parts, but:

● better use good method naming● custom matcher● do less, so that intention is clear● comments are not so bad in isolated well

named methods

Page 102: Developer Tests - Things to Know (Vilnius JUG)

Summary

● Context matters● Tests are controversial topic● And very opinionated

Page 103: Developer Tests - Things to Know (Vilnius JUG)

Thanks!Q & A