effective unit test style guide

19
Effective Unit Test Style Guide - Jacky Lai

Upload: jacky-lai

Post on 20-Mar-2017

120 views

Category:

Software


1 download

TRANSCRIPT

Effective Unit Test Style Guide

- Jacky Lai

Table of Contents

1. What NOT to Unit Test

2. What to Unit Test then?

3. Criteria of a Good Unit Test

4. Unit Test Method Name

5. Unit Test Content

1. What NOT to Unit Test

u Before we delve into this style guide, we need to know what not to unit test. "What not to test" is very important because any unnecessary test code in our code base will create noise in our code base and increase our maintenance cost.

u We can roughly divide all classes into 3 types: Entity, Control, Boundary (http://www.cs.sjsu.edu/~pearce/modules/patterns/enterprise/ecb/ecb.htm).

u Entity - Data Container, e.g. DTO

u Boundary - Classes that communicate directly to external services, via network connection, e.g. REST API, DAO, HttpClient, MessageHandler etc.

u Control - Classes that normally make decision.

Fig 1. Unit test and CBE

u Out of these 3 types of classes, in general, we don't need to write unit test for:

u Entity – Since it contains only logic-less getters and setters method, there is no value in testing all these getters / setters.

u Boundary - We need to cover boundary class with integration test, we just don't need to write unit test for it. If you need to unit test it just because your boundary class (handler class etc.) contains processing logic, you probably need to delegate the processing logic into control class.

u There are some exceptions for the rules above. We may need to write unit test for the following conditions:

u If the Entity class has validation rules. We need unit test validation logic.

u If the Boundary class has event broadcasting capability, as the example below. We need to verify that new event is being broadcasted.

u publishEvent(new DataUpdatedEvent()); // example

2. What to Unit Test then?

u Control - It makes decision, it has processing logic inside. It's like our brain. We need to make sure it's processing logic is correct and yield correct output.

3. Criteria of a Good Unit Test

u A good unit test satisfy 3 criteria:

1. Test a single feature in isolation.

2. Easy to understand by other engineers.

3. Help to identify issue effectively during test failure.

u Most unit tests satisfy #1. This styling guide will focus on #2 and #3.

4. Unit Test Method Name

u A test method name should consist of<targetMethod>_<expectedResult>_<initialState>.

u Example:

@Testpublic void computeTotalWidth_shouldReturnTotalWidth_givenPositiveNumbers() {…}

@Testpublic void computeTotalWidth_shouldThrowException_givenNegativeNumbers() {…}

@Testpublic void generate_shouldGenerateXYZ_forABConly() {…}

method_expectedResult_initialState() naming style

testTargetMethod() naming style

u Engineers can fix test failures with confidence if they understand what is the correct behavior of the code.

u Code reviewers on the other hand can easily spot on any missing test cases.

u As a side benefit, this naming style help programmer to keep his/her mind flowing when he/she is writing the test. For example, when he/she is stuck at what to test, he/she can think out loud saying "this method should return something when xyz is given”.

u In contrast, using names like “testComputeTotalWidth()” gives less context about it’s intent.

5. Unit Test Content

1. We use AAA style where every unit test has 3 sections - Arrange, Act, Assert:

u Arrange - Set up initial state (this will normally occupy more than 80% of your test code)

u Act - Perform action

u Assert - Verify the result

2. It is important to clearly identify these three sections with comments, allowing us to quickly jump between the “arrange", "act" and "assert" sections.

3. We use SUT (System Under Test) to denote target instance.

u This helps us identify the target instance from a swarm of mock objects.

✔️Do this

@Testpublic void testSearch_shouldReturnTargetIndex_ifFoundInInputValues() {

// 1. arrangeList<Integer> sortedNumbers = Arrays.asList(1, 2, 3);int targetValue = 2;

BinarySearcher sut = new BinarySearcher();

// 2. assertint targetIndex = sut.search(sortedNumbers, targetValue);

// 3. assertAssertions.assertThat(targetIndex).isEqualTo(1);

}

✔️ AAA Pattern@Test

public void test() {

// 1. arrange

final int TARGET_VALUE = 2;

Node node1 = new Node(8);

Node node2 = new Node(4);

Node node3 = new Node(5);

Node node4 = new Node(3);

Node node5 = new Node(9);

Node node6 = new Node(2);

node1.addChildNode(node2);

node1.addChildNode(node3);

node3.addChildNode(node4);

node1.addChildNode(node5);

node5.addChildNode(node6);

BFSearcher sut = new BFSearcher();

// 2. act

Node node = sut.search(node1, TARGET_VALUE);

// 3. assert

Assertions.assertThat(node).isEqualTo(node6);

}

❌ Without AAA Patter @Test

public void test() {

final int TARGET_VALUE = 2;

Node node1 = new Node(8);

Node node2 = new Node(4);

Node node3 = new Node(5);

Node node4 = new Node(3);

Node node5 = new Node(9);

Node node6 = new Node(2);

node1.addChildNode(node2);

node1.addChildNode(node3);

node3.addChildNode(node4);

node1.addChildNode(node5);

node5.addChildNode(node6);

BFSearcher sut = new BFSearcher();

Node node = sut.search(node1, TARGET_VALUE);

Assertions.assertThat(node).isEqualTo(node6);

}

u AAA Pattern:

u The Arrange section of AAA takes up over 80% of the code in our test, but we can still easily jump to Act sections by looking at the comments.

u ”sut" naming makes the test target stand out.

u Without AAA Pattern

u We have to start reading the code from beginning.

u Test target buried in the sea of mock objects.

1. A test should be self-contained. We want to understand the code fast. We don't want to jump around different methods to understand the test flow.

u It is preferred that you do not follow the DRY (Don't Repeat Yourself) principle. Striving for code reuse will lead to tightly coupled and inflexible test, which discourages refactoring.

❌ Don’t do this

// Do not do this. Code reader has to scroll

// up and down to understand the whole test logic.

@Test

public void testXYZ(){

// 1. arrange

setup1();

setup2();

doSomething();

Target sut = new Target();

// 2. act

actualResult = sut.doSomething();

// 3. assert

Asserts.assertThat(222, actualResult);

}

private void setup1(){ … }

private void setup2(){ … }

private void doSomething(){ … }

The End.