点击卡片“大数据实战演练”,选择“设为星标”或“置顶”
回复“资料”可领取独家整理的学习资料!
作者:JMCui
原文链接:https://cloud.tencent.com/developer/article/1623689
相信做过开发的同学,都多多少少写过下面的代码,很长一段时间我一直以为这就是单元测试...
@SpringBootTest
@RunWith(SpringRunner.class)
public class UnitTest1 {
@Autowired
private UnitService unitService;
@Test
public void test() {
System.out.println("----------------------");
System.out.println(unitService.sayHello());
System.out.println("----------------------");
}
}
但这是单元测试嘛?unitService 中可能还依赖了 Dao 的操作;如果是微服务,可能还要起注册中心,那么这个“单元”也太大了吧!如果把它称为集成测试,可能更恰当一点,那么有没有可能最小粒度进行单元测试呢?
单元测试应该是一个带有隔离性的功能测试。在单元测试中,应尽量避免其他类或系统的副作用影响。
单元测试的目标是一小段代码,例如方法或类。方法或类的外部依赖关系应从单元测试中移除,而改为测试框架创建的 mock 对象来替换依赖对象。
单元测试一般由开发人员编写,通过验证或断言目标的一些行为或状态来达到测试的目的。
JUnit 是一个测试框架,它使用注解来标识测试方法。JUnit 是 Github 上托管的一个开源项目。
一个 JUnit 测试指的是一个包含在测试类中的方法,要定义某个方法为测试方法,可以使用 @Test 注解标注该方法。该方法执行被测代码,可以使用 JUnit 或另一个 Assert 框架提供的 assert 方法来检查预期结果与实际结果是否一致,这些方法调用通常称为断言或断言语句。
public class UnitTest2 {
@Test
public void test() {
String sayHello = "Hello World";
Assert.assertEquals("Hello World", sayHello);
}
}
以下是一些常用的 JUnit 注解:
注解 |
描述 |
---|---|
@Test |
将方法标识为测试方法 |
@Before |
在每次测试之前执行。用于准备测试环境(例如,读取输入数据,初始化类) |
@After |
每次测试之后执行。用于清理测试环境(例如,删除临时数据,恢复默认值) |
@BeforeClass |
用于 static方法,在所有测试开始之前执行一次。它用于执行耗时的活动,例如:连接到数据库 |
@AfterClass |
用于 static方法,在完成所有测试之后,执行一次。它用于执行清理活动,例如:与数据库断开连接 |
@Ignore |
指定要忽略的测试 |
@Test(expected = Exception.class) |
如果该方法未引发命名异常,则失败 |
@Test(timeout=100) |
如果该方法花费的时间超过100毫秒,则失败 |
以下是一些常用的 Assert 断言:
声明 |
描述 |
---|---|
fail([message]) |
使方法失败。在执行测试代码之前,可用于检查未到达代码的特定部分或测试失败 |
assertTrue([message,]布尔条件) |
检查布尔条件是否为真 |
assertFalse([message,]布尔条件) |
检查布尔条件是否为假 |
assertEquals([message,]预期,实际) |
测试两个值是否相同。注意:对于数组,会检查引用而不是数组的内容 |
assertNull([message,]对象) |
检查对象是否为空 |
assertNotNull([message,]对象) |
检查对象是否不为空 |
assertSame([message,]预期,实际) |
检查两个变量是否引用同一对象 |
assertNotSame([message,]预期,实际) |
检查两个变量是否引用了不同的对象 |
从上面的介绍我们可以认识到,如何减少对外部的依赖才是实践单元测试的关键。而这正是 Mockito 的使命,Mockito 是一个流行的 mock 框架,可以与 JUnit 结合使用,Mockito 允许我们创建和配置 mock 对象,使用 Mockito 将大大简化了具有外部依赖项的类的测试开发。spring-boot-starter-test 中默认集成了 Mockito,不需要额外引入。
在测试中使用 Mockito,通常会:
mock 外部依赖关系并将 mock 对象插入待测代码
执行被测代码
验证代码是否正确执行
Mockito 提供了几种创建 mock 对象的方法:
使用静态 mock() 方法
使用 @Mock 注解
如果使用 @Mock 注解,则必须触发创建带有 @Mock 注解的对象。使用 MockitoRule 可以做到,它通过调用静态方法 MockitoAnnotations.initMocks(this) 来填充带 @Mock 注解的字段。或者可以使用 @RunWith(MockitoJUnitRunner.class)。
public class UnitTest3 {
// 触发创建带有 @Mock 注解的对象
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
// 1. 使用 @Mock 注解创建 mock 对象
@Mock private UnitDao unitDao;
@Test
public void test() {
// 2. 使用静态 mock() 方法创建 mock 对象
Iterator iterator = mock(Iterator.class);
// when...thenReturn / doReturn...when 模拟依赖调用
when(iterator.next()).thenReturn("hello");
doReturn(1).when(unitDao).delete(anyLong());
// 断言
Assert.assertEquals("hello", iterator.next());
Assert.assertEquals(new Integer(1), unitDao.delete(1L));
}
}
我们要单元测试的内容,常常包含着对数据库的访问等等,那么我们要如何 mock 掉这部分调用呢?我们可以使用 @InjectMocks 注解创建实例并使用 mock 对象进行依赖注入。
@Service
public class UnitServiceImpl implements UnitService {
@Autowired
private UnitDao unitDao;
@Override
public String sayHello() {
Integer delete = unitDao.delete(1L);
System.out.println(delete);
return "hello unit";
}
}
@RunWith(MockitoJUnitRunner.class)
public class UnitTest2 {
@Mock
private UnitDao unitDao;
@InjectMocks
private UnitServiceImpl unitService;
@Test
public void unitTest() {
// mock 调用
when(unitDao.delete(anyLong())).thenReturn(1);
Assert.assertEquals("hello unit", unitService.sayHello());
}
}
Mockito 还有很多有趣的实践,比如:@Spy或spy()方法、verify()验证等等,鉴于篇幅原因,读者可自行挖掘。
Mockito 也有一些局限性。例如:不能 mock 静态方法和私有方法。有关详细信息,请参阅 Mockito限制的常见问题解答。这个时候我们就要用到 PowerMock,PowerMock 支持 JUnit 和 TestNG,扩展了 EasyMock 和 Mockito 框架,增加了mock static、final 方法的功能。
首先需要引入 PowerMock 的依赖:
org.powermock
powermock-module-junit4
2.0.7
test
org.powermock
powermock-api-mockito2
2.0.7
接下来就能愉快的 mock 静态方法了。
@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class UnitTest4 {
@Test
public void test() {
mockStatic(StringUtils.class);
when(StringUtils.getFilename(anyString())).thenReturn("localhost");
Assert.assertEquals("localhost", StringUtils.getFilename(""));
}
}
---------- END ----------
往期推荐
我有好几种办法让你访问 github 速度起飞,不信进来看!
天呐,你生产环境中的密码还在裸奔吗?
spring boot 项目中自动执行 sql 语句
spring boot 如何统一处理 Filter、Servlet 中的异常信息
企业都在用的 spring boot 打包插件,真的超好用!
后端字段校验告别 if else,快来用下 @Valid 注解,省事又方便
懒人:使用 idea 插件 Easy Code 自定义 MybatisPlus 模板一键快速生成所需代码
用心整理 | Spring AOP 干货文章,图文并茂,附带 AOP 示例 ~
扫一扫,我们的故事就开始了。
文章有用,点赞、转发、在看都是一种支持,求三连!
另外公众号改变了推送规则,大家看文章不要忘记点击最下方的在看,点赞按钮,这样微信自动识别为常看公众号,否则很可能推送的文章可能淹没在别的文章找不到,谢谢大家。
动动小手,让更多需要的人看到~