当前位置: 首页 > news >正文

单元测试(TestNG+PowerMock)

在程序开发完成后,我们往往不能保证程序 100%
的正确,通过单元测试的编写,我们可以通过自动化的测试程序将我们的输入输出程序进行定义,通过断言来 Check 各个 Case
的结果,检测我们的程序,以提高程序的正确性,稳定性,可靠性。

我们将使用TestNG+PowerMock来完成我们的单元测试

  • TestNG:是一套根据 JUnit 思想构建,利用注释来强化测试功能的一个测试框架,即可以用来做单元测试,也可以用来做集成测试。
  • Mockito:是一种单元测试模拟框架,主要是用来做 Mock 测试,它可以模拟任何 Spring 管理的 Bean、模拟方法的返回值、模拟抛出异常等等
  • PowerMock:是在单元测试模拟框架的基础上做出的扩展。通过提供定制的类加载器以及一些字节码篡改技巧的应用,PowerMock 实现了对静态方法、构造方法、私有方法以及 Final 方法的模拟支持。

POM

    <testng.version>6.9.10</testng.version><powermock.version>2.0.9</powermock.version><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>${testng.version}</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>${powermock.version}</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>${powermock.version}</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-testng</artifactId><version>${powermock.version}</version><scope>test</scope></dependency>
</dependencies>

常用注解

TestNG 注解

  1. @BeforeClass 在调用当前类的第一个测试方法之前运行,注释方法仅运行一次
  2. @AftereClass 在调用当前类的第一个测试方法之后运行,注释方法仅运行一次
  3. @BeforeMethod 注释方法将在每个测试方法之前运行
  4. @AfterMethod 注释方法将在每个测试方法之后运行
  5. @BeforeTest 注释的方法将在属于test标签内的类的所有测试方法运行之前运行
  6. @AfterTest 注释的方法将在属于test标签内的类的所有测试方法运行之后运行
  7. @Test 将类或方法标记为测试的一部分

PowerMock 注解

  1. @Mock 创建一个模拟;
  2. @InjectMocks 创建该类的实例,并将使用@Mock(或@Spy)注释创建的模拟注入该实例
  3. @Spy 封装一个真实的对象,以便可以像其他 mock 的对象一样追踪、设置对象的行为;
  4. @PrepareForTest对于静态方法,私有方法,final 方法,在用powermock做单元测试的时候,需要增加注解;

使用

mock 对象

为对象加上一层代理

@Test
public void testTestWhen() {User user = new User();User userMock = PowerMockito.mock(User.class);
}

在这里插入图片描述

打桩

通过打桩,可以让被我们mock的对象,在调用指定方法的时候,按照我们的需要返回。

  • 隔离:单测的时候需要保证我们测试的单元一定要独立,不能依赖于外部的方法或接口,单元中调用到的方法也需要有对应的单测来保证功能。
  • 补齐:如果我们依赖的外部方法还没开发,或者是项目是并行开发,那此时打桩就可以把未完成的方法给代替掉。
  • 控制:测试时,我们对外部方法的实现细节不需要去理解,就像个黑盒,但外部方法的返回结果可能跟我们测试结果有直接关系,那此时我们可以通过打桩,控制外部方法按要求返回我们希望的结果。

以下示例,得到结果并不会是真实的返回结果,而是mock对象的方法所提供的默认结果,默认值为 null

@Override
public String getTest() {// 1、xxx// 2、xxxreturn testDemoDao.getTestObj();
}
//-----------------
@InjectMocks
TestDemoServiceImpl testDemoService;
@Test
public void getTest() {String test = testDemoService.getTest();System.out.println(test);
}
//-------结果-------
null

通过打桩,我们可以实现对mock类方法自定义返回结果

@InjectMocks
TestDemoServiceImpl testDemoService;
@Mock
TestDemoDao testDemoDao;
@Test
public void testTestWhen() {PowerMockito.when(testDemoDao.getTestObj()).thenReturn("测试模拟数据");String test = testDemoService.getTest();System.out.println(test);
}
//-------结果-------
测试模拟数据

mock 静态方法

public class OnlineUserUtil {private final static ThreadLocal<UserInfo> threadLocal = new ThreadLocal<>();public static void set(UserInfo userInfo) {threadLocal.set(userInfo);}public static UserInfo get() {return threadLocal.get();}
}
@Test
public void orderCancel() {PowerMockito.mockStatic(OnlineUserUtil.class);UserInfo userInfo = new UserInfo();PowerMockito.when(OnlineUserUtil.get()).thenReturn(userInfo);
}

注意如果是静态方法需要在类上加注解@PrepareForTest注解

@PrepareForTest({DateUtils.class})
public class DemoImplTest extends PowerMockTestCase{...}

mock 私有方法

public class World {private Banana banana = new Banana();public Banana getBanana() {return banana;}    private String secretGreeting(String s){return "i am secretly saying: " + s;}public String getLog(String s){return secretGreeting(s);}
}
@PrepareForTest({World.class})
public class WordTest extends PowerMockTestCase{@Mockprivate Banana banana;@InjectMocksprivate World world;@Testpublic void test1() throws Exception {// 用的是powermock的spyworld = PowerMockito.spy(world);// mock私有方法// 先when再thenReturn也行PowerMockito.doReturn("11111").when(world, "secretGreeting", "pp");System.out.println(world.getLog("pp"));// 自动注入仍然成功PowerMockito.when(banana.shout(Mockito.anyString())).thenReturn("22222");System.out.println(world.getBanana().shout("wow"));}
}

mock Void方法

public interface SmsService {void checkMsg(String code, String phone);
}
@PrepareForTest({World.class})
public class WordTest extends PowerMockTestCase{@InjectMocksprivate World world;@MockSmsService smsService;@Testpublic void test1() throws Exception {PowerMockito.doNothing().when(smsService).checkMsg(Mockito.any(),Mockito.any());}
}

Mock redisTemplate

@Resource
RedisTemplate<String,String> redisTemplate;public void getUser(String vipOrderNo) {Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(formatKey, "1");
}
@PrepareForTest(RedisTemplate.classpublic class UserImplTest extends PowerMockTestCase {@InjectMocksUserImpl user;@MockRedisTemplate<String,String> redisTemplate;@Testpublic void testGetUser() {ValueOperations<String, String> valueOperations= PowerMockito.mock(ValueOperations.class);PowerMockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);PowerMockito.when(valueOperations.setIfAbsent(Mockito.any(),Mockito.any())).thenReturn(true);user.getUser("A");}
}

mock 系统配置

User user = new User();
user .setMemo(sysConfig.get(SysConfigKey.USER_MEMO.getKey()));
@PrepareForTest({World.class})
public class WordTest extends PowerMockTestCase{@InjectMocksprivate World world;@MockSysConfig sysConfig;@Testpublic void test1() throws Exception {PowerMockito.when(sysConfig.get(SysConfigKey.USER_MEMO.getKey())).thenReturn("备注");}
}

mock @Value值

@Service
public class BillServiceImpl implements BillServic {@Value("${cash.bill.savePath}")private String billSavePath;@Overridepublic List<PlatformOrderBill> downloadBillFile(String checkDate) {File saveDir = new File(billSavePath);}}
@InjectMocks
BillServiceImpl billService;// 单元测试类里面加上这一行代码
ReflectionTestUtils.setField(billService, "billSavePath", "D://xxx.txt");

mock 异常

@Override
public String getTest() {try{testDemoDao.getTestObj();}catch(RuntimeException e){testDemoService.getTest();}
}
@Test
public void testTestWhen() {PowerMockito.when(testDemoDao.getTestObj()).thenThrow(new RuntimeException());
}

参数匹配器

如果只是用到上述打桩方式,一定程度上来说是不够灵活的,所以也就有了参数匹配器。

String getTestObj(String str);
//-----------------
@Test
public void testTestWhen() {PowerMockito.when(testDemoDao.getTestObj(Mockito.any())).thenReturn("BBB");String test = testDemoService.getTest();System.out.println(test);
}
//-------结果-------
BBB

验证是否执行了某些操作

public String getTest() {String str = testDemoDao.getTestObj("BBB");if (str.equals("BBB")){testDemoDao.getFailMsg();}return str;
}
//-----------------
@Test
public void testTestWhen() {PowerMockito.when(testDemoDao.getTestObj(Mockito.any())).thenReturn("CCC");testDemoService.getTest();Mockito.verify(testDemoDao).getFailMsg();
}
//-------结果-------
Wanted but not invoked:
testDemoDao.getFailMsg();

其他

在对MybatisPlus的baseMapper.selectList进行mock时遇到的问题。
mapper.selectList(new LambdaQueryWrapper<User>().eq(User::getName,"name").orderByAsc(User::getAge));
//-------结果-------
xxx.xxx.MybatisPlusException: can not find lambda cache for this entity。

原因是TableInfo的初始化过程被中止,或者被Mock替换了,根本就没有触发

解决方案
@BeforeTest
public void initTable() {TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(),""), User.class);
}
如果dao层实际执行sql语句,而不想要mock的话

如果想要走实际的sql语句,就需要注入对象,而我们的@Test注入对象,就需要加载ApplicationContext,启动spring容器。

@SpringBootTest(classes = ApiApplication.class)
public class DemoImplTest{
}

如果我们想要走实际sql,又不想对库造成影响,就需要对@Test进行回滚操作

@SpringBootTest(classes = ApiApplication.class)
public class DemoImplTest extends AbstractTransactionalTestNGSpringContextTests{@ResourceUserDaoImpl userDao;@Test@Rollback()//@Transactionalpublic void testSaveUser() {User user = new User;userDao.saveUser(user);}
}

如果是多模块项目的话需要将test类放到启动类在的包下

即使用容器对象又使用mock的方法
@SpringBootTest(classes = ApiApplication.class)
@TestExecutionListeners(MockitoTestExecutionListener.class)
public class ImgConfigServiceImplTest extends AbstractTransactionalTestNGSpringContextTests{@Resourceprivate ImgConfigServiceImpl service;@MockBeanSysConfig sysConfig;@Testpublic void testGetVipMainImg() {PowerMockito.when(sysConfig.get(SysConfigKey.VIP_MAIN_IMG.getKey())).thenReturn("测试用力会员卡主图");service.getVipMainImg();}
}

参考网址

TestNg官网:https://github.com/cbeust/testng/
Mockito官网:https://github.com/mockito/mockito
Powermock官网:https://github.com/powermock/powermock
Jacoco官网:https://github.com/jacoco/jacoco
Extentreports官网:https://www.extentreports.com/

http://www.dtcms.com/a/569671.html

相关文章:

  • 哪家公司网站建设好点襄樊网站建设哪家好
  • 公司网站做推广预装wordpress主机
  • CSS 文本和字体属性、列表属性
  • 网站布局结构图百度关键词排名销售
  • 小兔自助建站设计网站做海报
  • Camera成像原理
  • QuickMagicApi
  • 海洋承德网站建设公司店铺推广语
  • elasticsearch学习笔记-02
  • 构建一个自主深度思考的RAG管道以解决复杂查询--创建多阶段检索漏斗(5)
  • 网站开发方案 文档视频网站设计论文
  • 做钢材什么网站好个人装修设计软件
  • MFC - Picture Control 控件显示图片
  • 同一个网口有两个同名相机
  • mfc140.dll文件的丢失问题怎么处理?mfc140.dll文件的具体作用是什么
  • 第6章 支持向量机
  • 网站建设与管理大作业总结如何建设一个公众号电影网站
  • 网站模板去哪下载软件公司是干嘛的
  • rag:给大模型更精确的开端
  • Linux权限(5)
  • CVE-2025-4334 深度分析:WordPress wp-registration 插件权限提升漏洞
  • 【题解】洛谷 P3980 [NOI2008] 志愿者招募 [最大流最小费用]
  • Fastapi服务在高并发情况下大量超时问题排查
  • 分类与回归算法(二) - 线性回归
  • 中国建设银行官网站企业企业信息网查询
  • [创业之路-709]:管理与经营的异同
  • 网站的数据库在哪里专业上海网站建设
  • 使用IOT-Tree Server通过S7 Eth协议连接西门子PLC S7-1200
  • 59网一起做网站如何把字体安装在wordpress
  • PostgreSQL 之上的开源时序数据库 TimescaleDB 详解