不敢想象,你写了3年程序,竟然很少写单元测试!

一、前言

有同学可能看到这个标题,又以为是标题党了,你真莫笑,你可以小声去问问你在中小型公司的同学,有多少在写单测?可能有的同学就只知道Junit,连Mockito是什么都不知道,希望本文能帮助你深入的了解单测框架。

相信做过开发的同学,都多多少少写过下面的代码,很长一段时间我一直以为这就是单元测试...

@SpringBootTest@RunWith(SpringRunner.class)publicclassUnitTest1{@AutowiredprivateUnitService unitService;@Testpublicvoid test() {System.out.println("----------------------");System.out.println(unitService.sayHello());System.out.println("----------------------");    }}

但这是单元测试嘛?unitService 中可能还依赖了 Dao 的操作;如果是微服务,可能还要起注册中心。那么这个“单元”也太大了吧!如果把它称为集成测试,可能更恰当一点,那么有没有可能最小粒度进行单元测试嘛?

单元测试应该是一个带有隔离性的功能测试。在单元测试中,应尽量避免其他类或系统的副作用影响。

单元测试的目标是一小段代码,例如方法或类。方法或类的外部依赖关系应从单元测试中移除,而改为测试框架创建的 mock 对象来替换依赖对象。

单元测试一般由开发人员编写,通过验证或断言目标的一些行为或状态来达到测试的目的。

二、JUnit 框架

JUnit 是一个测试框架,它使用注解来标识测试方法。JUnit 是 Github 上托管的一个开源项目。

一个 JUnit 测试指的是一个包含在测试类中的方法,要定义某个方法为测试方法,请使用 @Test 注解标注该方法。该方法执行被测代码,可以使用 JUnit 或另一个 Assert 框架提供的 assert 方法来检查预期结果与实际结果是否一致,这些方法调用通常称为断言或断言语句。

publicclassUnitTest2{@Testpublicvoidtest(){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 的使命,Mockito 是一个流行的 mock 框架,可以与 JUnit 结合使用,Mockito 允许我们创建和配置 mock 对象,使用 Mockito 将大大简化了具有外部依赖项的类的测试开发。spring-boot-starter-test 中默认集成了 Mockito,不需要额外引入。

在测试中使用 Mockito,通常会:

mock 外部依赖关系并将 mock 对象插入待测代码

执行被测代码

验证代码是否正确执行

3.1 使用 Mockito 创建 mock 对象

Mockit o提供了几种创建 mock 对象的方法:

使用静态 mock() 方法

使用 @Mock 注解

如果使用 @Mock 注解,则必须触发创建带有 @Mock 注解的对象。使用 MockitoRule 可以做到,它通过调用静态方法 MockitoAnnotations.initMocks(this) 来填充带 @Mock 注解的字段。或者可以使用 @RunWith(MockitoJUnitRunner.class)。

publicclassUnitTest3{// 触发创建带有 @Mock 注解的对象@RulepublicMockitoRule mockitoRule = MockitoJUnit.rule();// 1. 使用 @Mock 注解创建 mock 对象@MockprivateUnitDao unitDao;@Testpublicvoidtest(){// 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(newInteger(1), unitDao.delete(1L));    }}

3.2 使用 mock 对象实践单元测试

我们要单元测试的内容,常常包含着对数据库的访问等等,那么我们要如何 mock 掉这部分调用呢?我们可以使用 @InjectMocks 注解创建实例并使用 mock 对象进行依赖注入。

@ServicepublicclassUnitServiceImplimplementsUnitService {@AutowiredprivateUnitDao unitDao;@OverridepublicStringsayHello() {Integerdelete= unitDao.delete(1L);System.out.println(delete);return"hello unit";    }}

@RunWith(MockitoJUnitRunner.class)publicclassUnitTest2{    @MockprivateUnitDao unitDao;@InjectMocksprivateUnitServiceImpl unitService;@TestpublicvoidunitTest(){// mock 调用when(unitDao.delete(anyLong())).thenReturn(1);Assert.assertEquals("hello unit", unitService.sayHello());    }}

Mockito 还有很多有趣的实践,比如:@Spy或spy()方法、verify()验证等等,鉴于篇幅原因,读者可自行挖掘。

3.3 使用 PowerMock mock 静态方法。

Mockito 也有一些局限性。例如:不能 mock 静态方法和私有方法。这个时候我们就要用到 PowerMock,PowerMock 支持 JUnit 和 TestNG,扩展了 EasyMock 和 Mockito 框架,增加了mock static、final 方法的功能。

首先需要引入 PowerMock 的依赖:

<!-- PowerMock -->org.powermockpowermock-module-junit42.0.7testorg.powermockpowermock-api-mockito22.0.7

接下来就能愉快的 mock 静态方法了。

@RunWith(PowerMockRunner.class)@PrepareForTest({StringUtils.class})publicclassUnitTest4{@Testpublicvoid test() {mockStatic(StringUtils.class);when(StringUtils.getFilename(anyString())).thenReturn("localhost");Assert.assertEquals("localhost", StringUtils.getFilename(""));    }}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342