Note: Pragmatic Unit Testing in Java 8 with JUnit

I read the first version when I was in college. It introduced TDD and Junit to me just fine. I’d like to read the latest version to refresh my memory. In contrast to a decade ago, TDD and Java 8 are really hot nowadays.

— Part I: Foundation —

Determine What Tests to Write

Look at loops, if branches and complex conditions. Think about data variants, side effects. Think about a path of execution.

3 Step Test Method Construction

Arrange -> Act -> Assert

Tests with Exception

Use @Rule annotation with ExpectedException object. Do not wrap checked exception with try/catch. Instead rethrow the exception ” @Test public void method() throws Exception {}”. In this way, JUnit can report an error instead of a failure.

Organise Tests

Test behaviour rather than methods.

Physically separate tests from production code.

Exposing private behavior is worse than exposing private data. This means redesign is needed.

One test for one case with descriptive naming.

Tests are documents

Do not use comments to clarify test purpose. Instead, improve test name, local-variable name, use constants, hamcrest asserstion, and split tests.

Tests Naming

doOpsGneratesResult | someResultCooursWhenCondition

WhenDoBehaviorThenResult (short for given when then) – BDD

Before and After

The order of executing multiple before is unknown. After will execute even after “fail”.

Slow Tests

Annotate slow tests with category annotation.

Not run all the tests – caution though.

— Part II: Advanced —

FIRST: Characteristics of quality tests

  1. Fast and First (TDD)
  2. Isolated
  3. Repeatable – Java provides “fixed clock” Clock.fixed(Instant, zone) besides the “real clock” Clock.systemUTC()
  4. Self-validating – Assertion and CI tool
  5. Timely – Test more frequently.

Right-BICEP: what kinds of tests to write

  1. Right results: adapt unit tests to the latest requirements. test “happy path” here aiming for right answer.
  2. Boundary conditions
  3. Inverse relationships – sqrt(25) * sqrt(25) == 25
  4. Cross-check results – check with complement cases
  5. force Error conditions – write test that forces error to happen, this includes system load, network error, etc
  6. Performance within bounds – JMeter JUnitPerf

CORRECT: boundary conditions

  1. Conform to an expected format
  2. Ordered / unorderd
  3. Range
  4. Reference – anything external under no direct control. Behave gracefully when preconditions and postconditions are not met.
  5. Existence
  6. Cardinality – enough values. “0-1-n( 1 < x < N case + N case )” rule.
  7. Time – order, timing

— Part III: Refactor, Design and Mock —

JUnit helps with refactoring, which yields more maintainable clean code.

Single method serves single purpose. Command-Query Separation: a method either executes a command or answers a query, not both.

Single class serves single purpose. Map class to concept not concrete notions. “Profile and MatchProfile” example shows how to delegate the matching procedure to a MatchProfile class.

SOLID Class-design

  • Single Responsibility
  • Open-closed Principle – open for extension but closed for modification.
  • Liskov Substitution – overrided methods do not break functionality
  • Interface segregation – Split large interface into smaller interfaceS.
  • Dependency inversion – high level modules do Not depend on low level modules. Instead, both depend on abstractions. Details depend on abstraction.

Mock

Methods to inject dependency: new constructor, setter method, override factory, abstract factories, use tools such as Google Guice or Spring.

While speeding up tests, Mock introduces a gap between tests and production code. Hence write integration tests to cover these gaps.

Test cleanupRefactoring Tests besides refactoring production code.

Make clean test code – organised, short and simple.

  • Unnecessary Test Code: let test method throws exception instead of wrapping using try/catch
  • Missing Abstractions: write customised matcher that extends TypeSafeMatcher
  • Irrelevant Information: eliminate unnecessary data and use good naming
  • Multiple assertions: use multiple assertions only if they have to be used all together to complete a single behaviour. Only single purpose test is allowed.
  • Irrelevant Details in Test: Move irrelevant “to test” code to @before and @after.
  • Misleading organization: organise as arrange/act/assert AAA.
  • Implicit Meaning: make text and names explicit.

— Part IV: TDD, Test Threads and Persistence, CI —

– TDD  – Test often while making SMALL steps. Refactor often. Split Tests to multiple TestClasses even if I am testing the same class. This will make the methods’ name shorter. Test names serve as documentation. Naming is of great importance.

– Testing Threads and Persistence –

Quote: A> “rework the design to better support testing by separating concerns”; B> “Break dependencies using stubs and mocks”

– Testing on a Project 

Code coverage and CI. 70% coverage is usually a guideline. Use CI to improve code quality of the whole team.