2. I am Hsiao Yu-Cheng
Blockchain Believer / Java Enthusiast / Agile
Lover / Portrait Photographer / Growth Hacker
You can find me at
https://www.linkedin.com/in/swanky/ and
https://swanky.github.io/
Hello!
3. “
I get paid for code that works, not for tests,
so my philosophy is to test as little as
possible to reach a given level of confidence.
- Kent Beck
Ref: tdd - How deep are your unit tests? - Stack Overflow
15. From: <<Agile Testing: A Practical Guide for Testers and Agile Teams>>
Image from “Building Microservices” Ch.7
Brian Marick的測試象限
業務面向
支援編程
技術面向
評價產品
25. Test-Driven Development (TDD)
Test-Driven Development (TDD) is a computer programming
technique that involves repeatedly first writing a test case and then
implementing only the code necessary to pass the test.
◉ The goal of test-driven development is to achieve rapid feedback.
◉ The technique began to receive publicity in the early 2000s as an aspect of
Extreme Programming, but more recently is creating more general interest in its
own right.
26. The Rhythm of TDD
1. Quickly add a test.
2. Run all test and see the new one fail.
3. Make a little change.
Run all tests and see them all succeed.
4. Refactor to remove duplication.
27. Inner and outer feedback loops in TDD
Acceptance: Does the whole system work?
Integration: Does our code work against code we can't
change?
Unit: Do our objects do the right thing, are they convenient
to work with? Ref: Growing Object-Oriented Software, Guided by Tests
29. “
I'm not a great programmer; I'm just a
good programmer with great habits.
- Kent Beck in: Martin Fowler, Kent Beck, John Brant (2012) Refactoring: Improving the Design of Existing Code.
30. “
To write good tests
first we need to learn
how to program
- Alvaro Videla, Co-author of <<RabbitMQ in Action>>
31. Coupling and Cohesion
Coupling and cohesion are metrics that (roughly) describe how easy it will be to change the behavior of some code.
● Elements are coupled if a change in one forces a change in the other
○ For example, if two classes inherit from a common parent, then a change in one class might require a change in the
other. Think of a combo audio system: It's tightly coupled because if we want to change from analog to digital radio,
we must rebuild the whole system. If we assemble a system from separates, it would have low coupling and we
could just swap out the receiver. "Loosely" coupled features (i.e., those with low coupling) are easier to maintain.
● An element's cohesion is a measure of whether its responsibilities form a meaningful unit
○ For example, a class that parses both dates and URLs is not coherent, because they're unrelated concepts. Think of a
machine that washes both clothes and dishes—it's unlikely to do both well. At the other extreme, a class that parses
only the punctuation in a URL is unlikely to be coherent, because it doesn't represent a whole concept. To get anything
done, the programmer will have to find other parsers for protocol, host, resource, and so on. Features with "high"
coherence are easier to maintain.
32. SOLID Principles
Single Responsibility Principle
(SRP)
A class should have only one
reason to change.
Open Closed Principle (OCP)
Software entities (classes,
modules, functions, etc.) should
be open for extension, but
closed for modification.
Liskov Substitution Principle
(LSP)
Objects in a program should be
replaceable with instances of
their subtypes without altering
the correctness of that program.
Interface Segregation Principle
(ISP)
Many client-specific interfaces
are better than one
general-purpose interface.
Dependency Inversion Principle
(DIP)
High-level modules should not
depend on low-level modules.
Both should depend on
abstractions.
Abstractions should not depend
on details. Details should
depend on abstractions.
Ref: SOLID Principles : The Definitive Guide
https://android.jlelse.eu/solid-principles-the-d
efinitive-guide-75e30a284dea
33. 如何進行?
◉ 熟悉測試相關API, Mock工具, IDE環境
JUnit, Spring MVC Test Framework, AssertJ, Mockito, ...
◉ 學習測試相關技術:Testing、Refactoring、TDD、ATDD & BDD
○ Test First可以確保測試的高涵蓋率,因為總是先有測試的程式,才有實際的成品
○ Test First可以確保API的設計完善,因為你是先從 API使用者角度的開始延伸
○ Test First可以確保程式高模組化,因為如果不先規畫好模組,測試很難進行
Ref:
Testing modern web applications
http://g00glen00b.be/testing-modern-web-applications/
Spring MVC Test Tutorial
http://www.petrikainulainen.net/spring-mvc-test-tutorial/
Test Driven Development 經驗整理
https://ingramchen.io/blog/2014/04/how-i-do-test-driven-development.html
35. Automating Tests
List fixture = new ArrayList();
// fixture should be empty
Object element = new Object();
fixture.add(element);
// fixture should have one element
Ref: JUnit Pocket Guide
36. Automating Tests
Step 1: Just Print It!
List fixture = new ArrayList();
System.out.println(fixture.size());
Object element = new Object();
fixture.add(element);
System.out.println(fixture.size());
Ref: JUnit Pocket Guide
37. Automating Tests
Step 2: Print with boolean expr
List fixture = new ArrayList();
System.out.println(fixture.size() == 0);
Object element = new Object();
fixture.add(element);
System.out.println(fixture.size() == 1);
Ref: JUnit Pocket Guide
38. Automating Tests
Step 3: Test with assertion method
void assertTrue(boolean condition) throws Exception {
if(!condition)
throw new Exception("Assertion failed");
}
List fixture = new ArrayList();
assertTrue(fixture.size() == 0);
Object element = new Object();
fixture.add(element);
assertTrue(fixture.size() == 1);
Ref: JUnit Pocket Guide
39. JUnit
JUnit is a regression testing framework written by Erich Gamma and Kent Beck.
In 1997, Erich Gamma and Kent Beck created a simple but effective unit testing framework for Java, called JUnit.
Framework – A framework is a semi-complete application. A framework provides a reusable, common structure that
can be shared between applications. Developers incorporate the framework into their own application and extend it to
meet their specific needs. Frameworks differ from toolkits by providing a coherent structure, rather than a simple set of
utility classes.
Erich Gamma is well known as one of the “Gang of Four” who gave us the now classic Design Patterns book.
Kent Beck is equally well known for his groundbreaking work in the software discipline known as Extreme Programming.
Regression testing is any type of software testing which seeks to uncover regression bugs. Regression bugs occur
whenever software functionality that previously worked as desired stops working or no longer works in the same way
that was previously planned. Typically regression bugs occur as an unintended consequence of program changes.
40. JUnit is a framework that automates the tedious,
repetitive parts of writing tests
◉ Runs tests automatically
◉ Runs many tests together and summarizes the results
◉ Provides a convenient place to collect the tests you’ve written
◉ Provides a convenient place for sharing the code used to
create the objects you are going to test
◉ Compares actual results to expected results and reports
differences
41. “
When I write automated tests I feel more confident
in my work than when I don’t write automated tests.
When I program test-first I can have confidence
throughout the process, Tests give me confidence that
I know what problem I’m trying to solve.
- Kent Beck
42. JUnit Annotations
Annotation Description
@Test
public void method()
The @Test annotation identifies a method as a test method.
@Test(expected = Exception.class) Fails if the method does not throw the named exception.
@Test(timeout = 100) Fails if the method takes longer than 100 milliseconds.
@Before
public void method()
This method is executed before each test. It is used to prepare the test environment (e.g., read input data, initialize the class).
@After
public void method()
This method is executed after each test. It is used to cleanup the test environment (e.g., delete temporary data, restore
defaults). It can also save memory by cleaning up expensive memory structures.
@BeforeClass
public static void method()
This method is executed once, before the start of all tests. It is used to perform time intensive activities, for example, to
connect to a database. Methods marked with this annotation need to be defined as static to work with JUnit.
@AfterClass
public static void method()
This method is executed once, after all tests have been finished. It is used to perform clean-up activities, for example, to
disconnect from a database. Methods annotated with this annotation need to be defined as static to work with JUnit.
@Ignore or
@Ignore("Why disabled")
Ignores the test method. This is useful when the underlying code has been changed and the test case has not yet been
adapted. Or if the execution time of this test is too long to be included. It is best practice to provide the optional description,
why the test is disabled.
43. JUnit Assert Statements
Statement Description
fail(message)
Let the method fail. Might be used to check that a certain part of the code is not reached or to
have a failing test before the test code is implemented. The message parameter is optional.
assertTrue([message,] boolean condition) Checks that the boolean condition is true.
assertFalse([message,] boolean condition) Checks that the boolean condition is false.
assertEquals([message,] expected, actual)
Tests that two values are the same. Note: for arrays the reference is checked not the content
of arrays.
assertEquals([message,] expected, actual,
tolerance)
Test that float or double values match. The tolerance is the number of decimals which must
be the same.
assertNull([message,] object) Checks that the objects is null.
assertNotNull([message,] object) Checks that the objects is not null.
assertSame([message,] expected, actual) Checks that both variables refer to the same object.
assertNotSame([message,] expected, actual) Checks that both variables refer to different objects.
44. Creating Tests with
IntelliJ
◉ Use the intention action
○ Place the cursor on the class name
○ Press Alt+Enter
◉ Use Navigation
○ On the main menu, choose Navigate | Test
○ On the context menu, choose Go to | Test
45. Creating Tests with
Eclipse + MoreUnit
Ref: Walkthrough for a TDD Kata in Eclipse
https://advancedweb.hu/2015/10/27/eclipse_tdd/
46. “
Bowling Game Kata
Ref: TheBowlingGameKata
http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata
47. AssertJ
ThoughtWorks Technology Radar - TRIAL (NOV 2017)
AssertJ is a Java library that provides a fluent interface for assertions, which makes it
easy to convey intent within test code. AssertJ gives readable error messages, soft
assertions, and improved collections and exception support. We're seeing some
teams default to its use instead of JUnit combined with Hamcrest.
NORMAL
private void makeNormal(Customer customer) {
Order o1 = new Order();
customer.addOrder(o1);
OrderLine line1 = new OrderLine(6, Product.find("TAL"));
o1.addLine(line1);
OrderLine line2 = new OrderLine(5, Product.find("HPK"));
o1.addLine(line2);
OrderLine line3 = new OrderLine(3, Product.find("LGV"));
o1.addLine(line3);
line2.setSkippable(true);
o1.setRush(true);
}
FLUENT
private void makeFluent(Customer customer) {
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priorityRush();
}
48. // entry point for all assertThat methods and utility methods (e.g. entry)
import static org.assertj.core.api.Assertions.*;
// basic assertions
assertThat(frodo.getName()).isEqualTo("Frodo");
assertThat(frodo).isNotEqualTo(sauron);
// chaining string specific assertions
assertThat(frodo.getName()).startsWith("Fro")
.endsWith("do")
.isEqualToIgnoringCase("frodo");
// collection specific assertions (there are plenty more)
// in the examples below fellowshipOfTheRing is a List<TolkienCharacter>
assertThat(fellowshipOfTheRing).hasSize(9)
.contains(frodo, sam)
.doesNotContain(sauron);
// as() is used to describe the test and will be shown before the error message
assertThat(frodo.getAge()).as("check %s's age", frodo.getName()).isEqualTo(33);
49. // Java 8 exception assertion, standard style ...
assertThatThrownBy(() -> { throw new Exception("boom!"); }).hasMessage("boom!");
// ... or BDD style
Throwable thrown = catchThrowable(() -> { throw new Exception("boom!"); });
assertThat(thrown).hasMessageContaining("boom");
// using the 'extracting' feature to check fellowshipOfTheRing character's names (Java 7)
assertThat(fellowshipOfTheRing).extracting("name")
.contains("Boromir", "Gandalf", "Frodo", "Legolas")
// same thing using a Java 8 method reference
assertThat(fellowshipOfTheRing).extracting(TolkienCharacter::getName)
.doesNotContain("Sauron", "Elrond");
// extracting multiple values at once grouped in tuples (Java 7)
assertThat(fellowshipOfTheRing).extracting("name", "age", "race.name")
.contains(tuple("Boromir", 37, "Man"),
tuple("Sam", 38, "Hobbit"),
tuple("Legolas", 1000, "Elf"));
50. // filtering a collection before asserting in Java 7 ...
assertThat(fellowshipOfTheRing).filteredOn("race", HOBBIT)
.containsOnly(sam, frodo, pippin, merry);
// ... or in Java 8
assertThat(fellowshipOfTheRing).filteredOn(character -> character.getName().contains("o"))
.containsOnly(aragorn, frodo, legolas, boromir);
// combining filtering and extraction (yes we can)
assertThat(fellowshipOfTheRing).filteredOn(character -> character.getName().contains("o"))
.containsOnly(aragorn, frodo, legolas, boromir)
.extracting(character -> character.getRace().getName())
.contains("Hobbit", "Elf", "Man");
// and many more assertions: iterable, stream, array, map, dates (java 7 and 8), path,
file, numbers, predicate, optional ...
52. Test Double
Test Double
Test Double is a generic term for
any case where you replace a
production object for testing
purpose. There are various kinds
of double list : Dummy、Fake、
Stub、Mock and Spy.
Dummy
Objects are passed around but
never actually used. Usually they
are just used to fill parameter
lists.
Fake
Objects actually have working
implementations, but usually
take some shortcut which makes
them not suitable for
production.
Stub
Stub provide canned answers to
calls made during the test.
Mock
Mocks are pre-programmed
with expectations which form a
specification of the calls they are
expected to receive.
Spy
Spies are stubs that also record
some information based on how
they were called.
Ref: Martin Fowler - TestDouble
https://martinfowler.com/bliki/TestDouble.html
53. Mockito
Mockito is a mocking framework for unit tests in Java. It has been designed to be
intuitive to use when the test needs mocks.
Verify interactions
import static org.mockito.Mockito.*;
// mock creation
List mockedList = mock(List.class);
// using mock object - it does not throw any
"unexpected interaction" exception
mockedList.add("one");
mockedList.clear();
// selective, explicit, highly readable verification
verify(mockedList).add("one");
verify(mockedList).clear();
Stub method calls
// you can mock concrete classes, not only interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing appears before the actual execution
when(mockedList.get(0)).thenReturn("first");
// the following prints "first"
System.out.println(mockedList.get(0));
// the following prints "null" because get(999) was
not stubbed
System.out.println(mockedList.get(999));
54. List<String> mockedList = mock(List. class);
//using mock
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice" );
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
/**
* 基本的驗證方法
* verify方法驗證mock對像是否有沒有調用mockedList.add("once")方法
* 不關心其是否有返回值,如果沒有調用測試失敗。
*/
verify(mockedList).add("once");
//默認調用一次,times(1)可以省略
verify(mockedList, times(1)).add("once");
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
//never()等同於time(0),一次也沒有調用
verify(mockedList, times(0)).add("never happened");
//atLeastOnece/atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("twice");
verify(mockedList, atMost(5)).add("three times");
參數匹配器
//stubbing using built-in anyInt() argument matcher
when(mockedList.get(anyInt())).thenReturn("element");
//stubbing using custom matcher (let's say isValid()
returns your own matcher implementation):
when(mockedList.contains(argThat(isValid()))).thenReturn(
"element");
//following prints "element"
System.out.println(mockedList.get(999));
//you can also verify using an argument matcher
verify(mockedList).get(anyInt());
55. “
Inside every well-written large
program is a well-written small
program.
- Charles Antony Richard Hoare, computer scientist