如何编写Comparator的JUnit用例提升Sonar代码覆盖率至90%
问题背景
基于Comparator<Employee>实现员工ID倒序排序逻辑,相关代码如下:
- 排序器实现类
SortByEmployeeIdByDesc
public class SortByEmployeeIdByDesc implements Comparator<Employee> { public int compare(Employee a, Employee b) { return b.getEmpId().compareTo(a.getEmpId()); } }
- 关联
Employee实体类,使用Lombok注解简化样板代码
@Builder @AllArgsConstructor @NoArgsConstructor public class Employee { @Getter private String empId; private String empName; private Date createdTime; @Getter private Date lastUpdatedTime; }
- 原有基于JUnit5+Mockito编写的测试用例可正常执行通过,但Sonar代码质量检测时未统计到Comparator实现类的覆盖率,需要优化测试方案将覆盖率提升至90%以上
@ExtendWith(MockitoExtension.class) public class EmployeeSortTest{ @Test public void sortEmpDescloyeeTest() { Employee emp1 = new Employee("abc", "test", "2020-10-10", "2010-10-10"); Employee emp2 = new Employee("xyz", "test2", "2022-03-04", "2022-03-04"); List<Employee> sortEmp = new ArrayList<>(); sortEmp.add(emp1); sortEmp.add(emp2); Collections.sort(sortEmp, new SortByEmployeeIdByDesc()); assertEquals(emp1, sortEmp.get(1)); } }
问题根因
- 原有测试仅覆盖了
compare方法中a.empId < b.empId(返回正数)的单一场景,没有覆盖返回负数、返回0的分支,本身覆盖率不足。 - 依赖
Collections.sort间接调用比较器的方式,会受JDK不同版本排序内部逻辑优化的影响,可能导致Sonar字节码插桩无法精准关联到自定义比较器的执行路径,出现覆盖率统计遗漏。 - 原有测试代码存在隐性编译错误:
Employee的createdTime、lastUpdatedTime为Date类型,直接传入字符串不符合类型要求。 - 无Mock依赖的场景下强行加载Mockito扩展,属于不必要的测试开销。
优化后的测试方案
直接对比较器的compare方法做全分支断言,不依赖排序工具类间接调用,确保所有执行路径都被精准统计,代码如下:
import org.junit.jupiter.api.Test; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.*; class EmployeeSortTest { private final SortByEmployeeIdByDesc idDescComparator = new SortByEmployeeIdByDesc(); private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); @Test void compare_whenFirstEmpIdSmaller_shouldReturnPositive() throws Exception { // 覆盖场景:a.empId < b.empId,倒序场景下a应排在b后,返回正数 Employee empSmall = Employee.builder() .empId("abc") .empName("test") .createdTime(dateFormat.parse("2020-10-10")) .lastUpdatedTime(dateFormat.parse("2010-10-10")) .build(); Employee empLarge = Employee.builder() .empId("xyz") .empName("test2") .createdTime(dateFormat.parse("2022-03-04")) .lastUpdatedTime(dateFormat.parse("2022-03-04")) .build(); assertTrue(idDescComparator.compare(empSmall, empLarge) > 0); // 同步验证排序结果符合预期 List<Employee> empList = new ArrayList<>(List.of(empSmall, empLarge)); empList.sort(idDescComparator); assertEquals(empLarge, empList.get(0)); assertEquals(empSmall, empList.get(1)); } @Test void compare_whenFirstEmpIdLarger_shouldReturnNegative() throws Exception { // 覆盖场景:a.empId > b.empId,倒序场景下a应排在b前,返回负数 Employee empLarge = Employee.builder() .empId("xyz") .empName("test2") .createdTime(dateFormat.parse("2022-03-04")) .lastUpdatedTime(dateFormat.parse("2022-03-04")) .build(); Employee empSmall = Employee.builder() .empId("abc") .empName("test") .createdTime(dateFormat.parse("2020-10-10")) .lastUpdatedTime(dateFormat.parse("2010-10-10")) .build(); assertTrue(idDescComparator.compare(empLarge, empSmall) < 0); List<Employee> empList = new ArrayList<>(List.of(empLarge, empSmall)); empList.sort(idDescComparator); assertEquals(empLarge, empList.get(0)); assertEquals(empSmall, empList.get(1)); } @Test void compare_whenEmpIdEqual_shouldReturnZero() throws Exception { // 覆盖场景:a.empId = b.empId,两元素顺序相等,返回0 Employee emp1 = Employee.builder() .empId("abc") .empName("test") .createdTime(dateFormat.parse("2020-10-10")) .lastUpdatedTime(dateFormat.parse("2010-10-10")) .build(); Employee emp2 = Employee.builder() .empId("abc") .empName("test_copy") .createdTime(dateFormat.parse("2022-03-04")) .lastUpdatedTime(dateFormat.parse("2022-03-04")) .build(); assertEquals(0, idDescComparator.compare(emp1, emp2)); } }
优化效果
- 直接调用比较器目标方法做断言,完全规避JDK排序内部逻辑对覆盖率统计的干扰,Sonar可以精准识别到所有执行代码。
- 覆盖
compare方法的三个全部分支,代码覆盖率可达100%,满足质量检测要求。 - 修正原有测试的类型错误,使用Lombok Builder构造实体不需要记忆全参构造的参数顺序,代码可读性更高。
- 移除不必要的Mockito扩展加载,测试启动速度更快。
内容的提问来源于stack exchange,提问作者Tech_sharma
相关产品推荐
相关产品推荐

