Fundamentals 16 min read

Mastering Unit Testing: Practical Tips, Common Pitfalls, and Best Practices

This article explores how to effectively use unit testing in daily development, defining its purpose, debunking common misconceptions, presenting the AIR principles, offering coverage level guidelines, and sharing practical tips, code examples, and solutions for handling mocks, static classes, and integration challenges.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering Unit Testing: Practical Tips, Common Pitfalls, and Best Practices

The author combines daily work experience to discuss how to make the most of unit testing.

Preface

Learning unit testing should go beyond the technical layer—frameworks and mocking libraries—because the goal is to maximize the return on time invested while minimizing effort, which is not easy.

Definition of Unit Testing

Unit testing is the verification and validation of the smallest testable parts of software. In Java, a "unit" often refers to a class.

In plain language, unit testing validates the correctness of a class, distinct from integration or system testing, and is a developer‑led, minimal‑scale test.

Key Statistics

85% of defects originate during the design phase.

The later a bug is discovered, the exponentially higher its fixing cost.

Therefore, writing unit tests significantly impacts delivery quality and labor cost.

Defect statistics chart
Defect statistics chart

Common Pitfalls

Wasting Time and Slowing Development

Test effort must be balanced with code lifecycle, debugging ability, and review time. Without proper unit tests, code becomes hard to maintain.

Testing Should Be a Developer’s Responsibility

Developers, being most familiar with the code, should write unit tests during design to increase confidence and reduce downstream testing issues.

Ignoring Legacy Code

Writing unit tests for old code forces deeper understanding and improves code quality; tests should be added during refactoring.

How to Write Good Unit Tests

AIR Principle

The tests should be Automatic , Independent , and Repeatable , like air—imperceptible yet essential.

My interpretation:

Automate execution via CI, use assertions instead of prints, and avoid manual intervention.

Ensure each test is independent; no ordering or shared state.

Mock external dependencies (databases, middleware) so tests are environment‑agnostic.

Coverage Guidelines

Statement coverage should reach 70%; core modules require 100% statement and branch coverage. – Alibaba Java Development Manual

Coverage levels:

Level 1: Normal flow works with correct inputs.

Level 2: Invalid inputs raise logical exceptions, not system crashes.

Level 3: Edge cases and boundary data are tested.

Level 4: All branches and loops are exercised.

Level 5: Every field of complex output data is verified.

In practice, achieving 95%+ coverage demands testing many branches and exception paths, but diminishing returns apply; unnecessary tests (e.g., trivial getters) can be excluded.

Practical Example

public ApiResponse<List<Long>> getInitingSolution() {
    List<Long> solutionIdList = new ArrayList<>();
    SolutionListParam solutionListParam = new SolutionListParam();
    solutionListParam.setSolutionType(SolutionType.GRAPH);
    solutionListParam.setStatus(SolutionStatus.INIT_PENDING);
    solutionListParam.setStartId(0L);
    solutionListParam.setPageSize(100);
    List<OperatingPlan> operatingPlanList = operatingPlanMapper.queryOperatingPlanListByType(solutionListParam);
    for (; !CollectionUtils.isEmpty(operatingPlanList);) {
        // do something
        solutionListParam.setStartId(operatingPlanList.get(operatingPlanList.size() - 1).getId());
        operatingPlanList = operatingPlanMapper.queryOperatingPlanListByType(solutionListParam);
    }
    return ResponsePackUtils.packSuccessResult(solutionIdList);
}

When testing, mock the database query and handle the hidden boundary where the loop runs only once for >100 records.

Mocking Time

Use Mockito to mock Date objects: when(date.getTime()).thenReturn(time). For static Calendar.getInstance(), employ PowerMock or Mockito 4.x to mock static methods.

@RunWith(PowerMockRunner.class)
@PrepareForTest({Calendar.class, ImpServiceTest.class})
public class ImpServiceTest {
    @InjectMocks
    private ImpService impService = new ImpServiceImpl();
    @Before
    public void setup(){
        MockitoAnnotations.initMocks(this);
        Calendar now = Calendar.getInstance();
        now.set(2022, Calendar.JULY, 2, 0, 0, 0);
        PowerMockito.mockStatic(Calendar.class);
        PowerMockito.when(Calendar.getInstance()).thenReturn(now);
    }
}

Testing Final/Static Classes

Static or final classes require Mockito 4.x or PowerMock; version compatibility must be verified to avoid NoSuchMethodError.

Handling External Dependencies (e.g., Tair, MetaQ)

Use @RunWith(PandoraBootRunner.class) to start the Pandora container, or mock the SDK classes directly with PowerMock.

@RunWith(PowerMockRunner.class)
@PrepareForTest({DataEntry.class})
public class MockTair {
    @Mock
    private DataEntry dataEntry;
    @Before
    public void hack() throws Exception {
        PowerMockito.whenNew(DataEntry.class).withNoArguments().thenReturn(dataEntry);
    }
    @Test
    public void mock() throws Exception {
        String value = "value";
        PowerMockito.when(dataEntry.getValue()).thenReturn(value);
        DataEntry tairEntry = new DataEntry();
        Assert.assertEquals(value, tairEntry.getValue());
    }
}

Using IoC for Test Isolation

Inject dependencies so static utility classes can be mocked, reducing coupling between services and helpers.

@Service
public class LoginServiceImpl implements LoginService {
    public Boolean login(String username, String password, String ip) {
        if (!IpUtil.verify(ip)) {
            return false;
        }
        return true;
    }
}

Mock IpUtil in tests to isolate LoginServiceImpl.

Avoid Testing Trivial Code

Skip unit tests for generated getters/setters or toString methods; exclude such packages from CI coverage.

Testing Void Methods

Verify side effects on the database using an embedded H2 database.

Use verify(mock, times(1)).method() to ensure interactions.

Mock exceptions with doThrow and assert expected behavior.

References:

https://scottming.github.io/2021/04/07/unit-testing/

https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html#integration-testing

https://yuque.antfin-inc.com/fangqintao.fqt/pu2ycr/eabim6

https://yuque.antfin-inc.com/aone613114/en7p02/pdtwmb

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

code coverageunit testingMocking
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.