在实习过程中,首先要熟悉所在组所做的项目,而单元测试就是一个比较好的方法。

# 什么是单元测试

单元测试( Unit Testing )是软件开发中的一种测试方法,旨在验证代码中最小可测试单元(通常是函数、方法或类)的正确性。它的主要目的是确保每个单元在各种情况下都能按预期工作,从而及早发现和纠正错误。

# 单元测试的特点

独立性:单元测试通常是独立的,不依赖于其他部分的代码。
自动化:单元测试可以通过自动化工具运行,减少人为错误。
快速反馈:由于测试范围小,单元测试能够快速提供反馈,帮助开发者及时修复问题。

# 单元测试的优点

提高代码质量:通过早期发现和修复错误,单元测试可以显著提高代码质量。
简化调试:由于测试范围小,定位和修复错误变得更加容易。
文档作用:单元测试代码可以作为文档,帮助其他开发者理解代码的功能和使用方法。

# 如何进行单元测试

# 使用 squareTest 插件

  • 安装插件

  • 找到要生成的类,点击 generate Test

  • 更换给定的模板为以下文件中内容

  • 补充必备代码

    • Public Class xxx extends NecpBaseContextTest 首先继承这个类

    • 添加以下静态代码块:

      static {
          (new SqlResource()).handleEvent(Database.MYSQL, null);
          (new SqlResource()).handleEvent(Database.ORACLE, null);
          }
      
  • 注意事项
    tearDown() 方法相当于回收垃圾的作用,其中 reset 方法参数只放被 @Mock 注解的对象, @InjectMock 不需要,如果整个测试类没有 @Mock 注解的对象,那么就不需要编写 tearDown() 方法。例如:

    @Mock
    private ArchivesCsRepository mockArchivesCsRepository;
    @After
    public void tearDown() {
    reset(mockArchivesCsRepository);
    }

# Mockito 框架用法要点

# 参数匹配和打桩

前言:
当你在测试中设置 mock 对象的行为(即打桩,使用 when(...).thenReturn(...) 等语句),你指定了当特定方法被特定参数调用时应该返回什么。
如果在实际执行测试时传递的参数与你设置的参数不匹配,那么打桩的行为将不会发生, mock 对象将返回默认值(如 null0false 等)。

例如:
--- 被测试方法片段 ---

String sql = ARCH_QUERY_DOCNO_SQL;
sql = MessageFormat.format(sql, tableName);
Map<String, Object> param = new HashMap<>();
List<ArchMoveBO> result = moveQueryService.findListByParam(sql, param , ArchMoveBO.class);
if (null == result || result.isEmpty()) {
return "T000001";
}

---- 错误的测试方法片段 ----

final List<ArchMoveBO> archMoveBOS = Arrays.asList(archMoveBO);
when(queryService.findListByParam("sql",newHashMap<>(), archMoveBO.class)).thenReturn(archMoveBOS);
final String result = archivesMoveQueryServiceUnderTest.getNextProcessInstId("year");

执行结果: 被测方法返回的 result 为 null,这就说明测试方法打桩失效了。
---- 正确的测试方法片段 ----

final List<ArchMoveBO> archMoveBOS = Arrays.asList(archMoveBO);
when(queryService.findListByParam(any(),anyMap(), any())).thenReturn(archMoveBOS);
final String result = archivesMoveQueryServiceUnderTest.getNextProcessInstId("year");

执行结果: 被测方法返回的 resultarchMoveBOS ,这就说明测试方法打桩成功了。

结论: 当使用 mockmock 方法的时候,一定要注意参数匹配, 否则打桩不成功。

补充: 在使用参数任意匹配符时,也要注意匹配,有的时候 Any() 可以成功,但是有的时候需要具体匹配,比如使用 anyString() ;

# 如何测试 private 方法?

(1) 使用这个方法 Whitebox.invokeMethod() 调用私有方法即可。
例如:

boolean[] result = Whitebox.invokeMethod(archivesPrintQueryService, "isPrintEleOrOther", nodeTypeCodeMap, isVoucher);

注意点:
(1) 使用 Whitebox 调用私有方法时不能 debug 调试。
(2) 一定要注意方法的返回值,如果返回值不对应就会报错,但是这个错是运行时的错误,不会在编译的时候爆出来,一旦出错难以找出,一定要小心。
Java 中, Whitebox 类通常来自于 org.jmockit.whitebox 包,它是 Mockit 框架的一部分,用于测试时访问私有方法。 Whitebox.invokeMethod() 方法用于调用一个类的私有方法,即使这些方法不是公开的,也可以通过反射机制来访问它们。
下面是 Whitebox.invokeMethod() 方法的签名:

public static <T> T invokeMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object[] parameters)

archivesPrintQueryService : 这是你要调用私有方法的对象实例。在这个例子中,它是一个实现了 archivesPrintQueryService 接口或类的实例。
" isPrintEleOrOther ": 这是你要调用的私有方法的名称。在这个字符串中,方法名是 isPrintEleOrOther ,注意,方法名不需要加任何修饰符,即使是私有方法。
nodeTypeCodeMap : 这是第一个参数传递给 isPrintEleOrOther 方法的值。 nodeTypeCodeMap 通常是一个映射(如 HashMapMap ),它包含了节点类型代码和其它相关数据的键值对。
isVoucher : 这是第二个参数传递给 isPrintEleOrOther 方法的值。这个参数通常是布尔值,表示是否是凭证( isVouchertrue 表示是凭证,为 false 表示不是)。
下面是如何使用 Whitebox.invokeMethod() 调用的详细步骤:

  • archivesPrintQueryService 对象是一个包含私有方法 isPrintEleOrOther 的类的实例。
  • Whitebox.invokeMethod() 被用来调用这个实例的私有方法 isPrintEleOrOther
  • nodeTypeCodeMapisVoucher 作为参数传递给这个私有方法。
  • 私有方法 isPrintEleOrOther 执行其逻辑,可能基于传入的参数。
  • 执行结果是一个 boolean[] 数组,这个数组被赋值给 result 变量。
  • 总结一下, Whitebox.invokeMethod() 调用了 archivesPrintQueryService 对象的私有方法 isPrintEleOrOther ,并传递了两个参数 nodeTypeCodeMapisVoucher ,然后返回了一个 boolean[] 类型的值,这个值被存储在 result 变量中。

# 如何测试 static 方法

(1) 添加 @PrepareForTest(Class.class) 注解
(2) 模拟 static 方法: PowerMockito.mockStatic(ClassWithstaticMethod.class)
(3) 定义 static 方法的行为: when().thenReturn() ;

注意点:如果出现问题,比如 not prepared for test.
可能是 PowerMockitoMockito 版本兼容性问题。

# 如何 Mock 被测试类的方法中调用的方法

无需 mock ,直接陷入调用的方法即可。

# 如何设置待测方法的属性值

例如: @Value("${formatFileTagLimit:10000}")
private Integer formatFileTagLimit;

解决: 我们可以通过反射来进行属性注入。

@Before
public void setUp() throws Exception {
ReflectionTestUtils.setField(archFormatFileTagServiceUnderTest, "formatFileTagLimit", 100);
}

# this.getDao () 为 NULL 的解决办法

ArchivesDepotQueryServiceTest 类为例子:

@InjectMocks
private ArchivesDepotQueryService service;
@Mock
private ArchivesDepotRepository repository;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
Field daoField = GeneralContext.class.getDeclaredField("dao");
daoField.setAccessible(true);
daoField.set(service, repository);
}

解释(通义灵码生成):
这段标红代码的作用是通过反射机制,修改 GeneralContext 类中 dao 字段的值为 repository 。具体步骤如下:
(1) 使用 getDeclaredField 方法获取 GeneralContext 类中名为 dao 的私有字段;
(2) 调用 setAccessible 方法将该字段的访问权限设置为可访问;
(3) 使用 set 方法将 dao 字段的值设置为 repository ,并将 service 对象作为参数传入,表示要修改的是 service 对象中的 dao 字段值。

# 如何解决微服务调用的问题

ArchivesAttachServiceTest 类为例:
步骤:
(1) 加入 @PrepareForTest({MappService.class})
(2) 对 MapperService 进行 mock 注入

@Mock
MappService mappService;

(3) 在 setUp() 方法中添加如下代码:

@Before
public void setUp() throws Exception {
Whitebox.setInternalState(MappService.class, "INSTANCE", mappService);
when(MappService.INSTANCE.doPost(anyString(),anyMap(),eq(String.class), anyObject())).thenReturn("");
}

缺陷:
虽然 mock 成功,不会报错,但是不能设定返回值, eturn 为”” 空的。

# 解决 DBUtil.isOracle () 报错的方式

(1) 在 @PrepareForTest(DBUtil.class) 中添加 DBUtil.class

(2)在相应的方法中添加这两句即可

PowerMockito.mockStatic(DBUtil.class);
PowerMockito.when(DBUtil.isOracle()).thenReturn( true);

# 解决静态常量为空的情况

例如:

PowerMockito.mockStatic(SQLConst.class);
Field field = PowerMockito.field(SQLConst.class, "QYVO_STA_MITEM_SY_MCOM_SITEM");
field.set(null, "your_sql_query_here");

# 让某个方法什么也不做,从而跳过该方法

doNothing().when(spy).saveList(anyList());

# 已知会抛出某个异常.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;
Exception exception = assertThrows(RuntimeException.class, () -> {
// 在这里调用可能抛出 NullPointerException 的方法
CommentSaveStatusEnum.valueOf(2);
});

# 使用 when 来抛出异常

when(mockSystemParamContext.findByNameAndTenantGid(anyString(), anyString())).thenThrow(new RuntimeException("Error fetching parameter"));
更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

KagurazakaAsahi 微信支付

微信支付