+ All Categories
Home > Software > Developer Tests - Things to Know

Developer Tests - Things to Know

Date post: 15-Jul-2015
Category:
Upload: vaidas-pilkauskas
View: 273 times
Download: 3 times
Share this document with a friend
102
Developer Tests Things to Know Vaidas Pilkauskas 2014 Dnipropetrovsk
Transcript

Developer TestsThings to Know

Vaidas Pilkauskas 2014Dnipropetrovsk

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

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

“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/

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

“Legacy Code is code without Tests”

Michael FeathersWorking Effectively with Legacy Code

So what is test?

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

Test structure

http://healthysimulation.com/2743/structure-collapse-simulator-constructed-by-strategic-response-initiatives/

Test phases

SetupExercise

VerifyTeardown

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

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

}

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

}

Rule #1

Do not start from setup/teardown

@Test public void shouldServeEmptyUserList() { server.start(); assertThat(server.getUsers(), is(empty())); server.stop();}

@Before public void startServer() { server.start();}

@Test public void shouldServeEmptyUserList() {

//todo: implement

}

@After public void stopServer() { server.stop();}

BAD

GOOD

How many colors are there in the picture?

Source: Josef Albers Interaction of Color

Context matters!

Source: Josef Albers Interaction of Color

Let’s talk about setup in @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 }

http://www.worldwildlife.org/threats/pollution

Setup pollution

Setup pollution - #1

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

BAD@Before public void preparePets() {

cat = new Cat(); // only needed by dogShouldSayFoo

dog = new Dog(); // only needed by catShouldSayBar

}

@Test public void dogShouldSayFoo() {

assertThat(dog.says(), is("foo"))

}

@Test public void catShouldSayBar() {

assertThat(cat.says(), is("bar"))

}

Setup pollution - #2

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

BAD@Before public void preparePets() {

cat = new Cat().withHairstyle(CURLY);

dog = new Dog();

}

@Test public void curlyCatShouldFeelBeautiful(){

assertThat(cat.isFeelingBeautiful(), is(true));

}

@Test public void dogShouldSayFoo() {

assertThat(dog.says(), is("foo"));

}

@Test public void catShouldSayBar() {

assertThat(cat.says(), is("bar"));

}

Setup pollution - #3

● Rich setup makes tests slow!

Setup pollution - #4

● Setup is to bootstrap your SUT.

Setup pollution - #5

● Setup hides details! Do not hide test preconditions.

Setting up note 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

}

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

}

Give good names to setup methods

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

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.

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

What do we test?http://www.dumpaday.com/random-pictures/daily-randomness-40-pics/

Test behaviour not methods@Test public void testGetBalance() { assertEquals(0, account.getBalance())

}

@Test

public void shouldBeEmptyAfterCreation() { assertEquals(0, account.getBalance());

}

Test behaviour not methods

● Think of a contract

Test behaviour not methods

● Think of a contract ● And responsibilities

Test behaviour not methods

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

Test behaviour not methods

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

approach

Matchershttp://www.allthatiknow.com/2013/10/comparing-google-analytics-standard-vs-premium/

Matchers

● Assert on the right level of abstraction● Encapsulate testing logic

Matcher libraries

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

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

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

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

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

//JUnit

assertEquals("Frodo", frodo.getName())

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

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

assertThat(sauron).isNotIn(fellowshipOfTheRing);

//JUnit

assertEquals("Frodo", frodo.getName())

Custom matchers

http://mimiandeunice.com/2011/04/19/compare/

Custom matchers

Are matchers that we write specifically for our projects.

Custom matchers

● Help communicate test intention● Abstract assertion logic

Custom matchers

● Help communicate test intention● Abstract assertion logic● Are reusable and save time in large projects● You may have a custom message to be

more specific about test failure

Custom matchers@Test

public void shouldHaveIsbnGenerated() {

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

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

Failing a test

fail()

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

fail()

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

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

Anti-pattern: The Ugly Mirror

http://www.iwillnotdiet.com/?attachment_id=2605

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Волк", "Серый");

String expected =

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

assertEquals(expected, person.toString());

}

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Волк", "Серый");

String expected =

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

assertEquals(expected, person.toString());

}

Anti-pattern: The Ugly Mirror@Test

public void personToStringShouldIncludeNameAndSurname() {

Person person = new Person("Волк", "Серый");

assertEquals("Person[Волк Серый]", person.toString());

}

How to turn off the test?http://namo-rekonstrukcija.blogspot.com/2013/05/elektra-izeminimas.html

Why would you want to turn off the test?

Why would you want to turn off the test?

Well, because it fails… :)

Ignoring tests

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

@Ignore("charge simplicator not ready")

@Test public void shouldChargeMonthly() {

//implementation which fails

}

Ignoring tests

● Do not comment out or false assert your test//Will fix it later™

//@Test public void shouldBeZeroInitialy() {

// assertEquals(0, account.getBalance());

//}

@Test public void shouldBeZeroInitialy() {

assertEquals(1, account.getBalance());

}

Ignoring tests

● Do not comment out or false assert your test//Will fix it later™

//@Test public void shouldBeZeroInitialy() {

// assertEquals(0, account.getBalance());

//}

@Test public void shouldBeZeroInitialy() {

assertEquals(1, account.getBalance());

} Who is Will?

Ignoring tests

● If you do not need a test - delete it

What to do with exceptions?

http://s273.photobucket.com/user/eliteskater08/media/meateatingrabbit.jpg.html

Exceptions

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

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]

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!

}}

Exceptions

● catch-exception lib

catch-exception libList myList = new ArrayList();

catchException(myList).get(1);

assertThat(caughtException(),

allOf(

is(IndexOutOfBoundsException.class),

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

hasNoCause()

)

);

Exceptions

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

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);

// ...

}

Testing with timehttp://wisconsinrelocation.net/post/1295775/tickle-your-funny-bone-----time-management-is-everything--

Problempublic class MyService { ...

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

...

} }

}

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

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

...

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

...

} }

}

Collectionshttp://mycoolguitars.com/cool-guitar-collections/

Collections - multiple properties to assert

● Is null?● Size● Order● Content

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

assertThat(users, containsInAnyOrder("andrius", "vaidas"))

Collections● Prefer exact content matching

assertThat(users, contains("andrius", "vaidas"))

Collections● Avoid incomplete assertions

//users=List.of("andrius", "vaidas", "valdemaras", "jonas")

assertThat(users, hasItems("andrius", "vaidas"))

Collections● Do not sort just because it is easier to assert!

assertThat(sort(users), contains("andrius", "vaidas"))

Collections● Multiple assertions are worse than single content

assertion

assertThat(users, hasSize(2))

assertThat(users, hasItems("andrius", "vaidas"))

Collections● Use matchers!

Random values

Random values in tests

● Most of the time you do not want it

@Test public void shouldStartWithFirst() {

String a = randomString();

String b = randomString();

assertThat(a + b, startsWith(a));

}

Random values in tests

● Unless you depend on randomness a lot (eg. password generation*)

*Thanks to Aleksandar Tomovski for a good example

Random values in tests

● Use property based testing (which is also hard)

//example from ScalaCheck

property("startsWith") = forAll { (a: String, b: String) =>

(a+b).startsWith(a)

}

Random values in tests

● Do not make dummy values randomcat = new Cat(randomString());

cat = new Cat("dummyName");

What if we still need random cases?

Generate Multiple Test Cases

● Quality over quantity

Generate Multiple Test Cases

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

to detect with random test

Generate Multiple Test Cases

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

to detect with random test● Use parameterized tests

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

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

How many assertions per test?http://dailypicksandflicks.com/2012/05/11/daily-picdump-455/4-beers-at-the-same-time/

How many assertions per test?

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

● Proper tests should fail for exactly one reason

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

● Consider custom matchers

How many assertions per test?

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

● Proper tests should fail for exactly one reason

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

● Consider custom matchers

Thing #13

Decoupling in End-to-end tests

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(user.username, is("vaidas"));

}

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(user.username, is("vaidas"));

}

Decoupling from low level details [pseudocode]

@Test shouldRetrieveUserByUsername() {

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

assertThat(createResp, isSuccessful());

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

assertThat(getResp, allOf(isSuccessful(), hasUsername("vaidas")));

}

Summary

● Context matters● Testings is controversial topic● And very opinionated

Thanks!Q & A


Recommended