走進JavaWeb技術世界11:單元測試框架Junit

JUnit你不知道的那些事兒

轉自 老劉 碼農翻身 2016-02-24

話說有一次Eric Gamma 坐飛機的時候偶遇Kent Beck(對,就是極限編程和TDD的發起人) ,  兩位大牛見面寒暄過以後就覺得很無聊了。

旅途漫漫,乾點啥好呢。

Kent Beck當時力推測試驅動開發,  但是沒有一個工具或者框架能讓大家輕鬆愉快的寫測試,並且自動的運行測試。

兩人勾兌了一下:不如自己挽起袖子寫一個, 於是兩人就在飛機上結對編程 ,  等到飛機的時候,一個劃時代的單元測試工具就新鮮出爐了,這就是JUnit:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

添加描述

JUnit當然是用Java寫的, 其他語言一看, 這東西好, 咱也搞一套, 於是就有了一大批工具,統稱xUnit

NUnit (針對.Net 平臺) ,  CUnit (針對C語言) , CppUnit(針對C++語言), PyUnit (針對Python), PHPUnit, OCUnit, DUnit, JSUnit ......

超級大牛出馬,親自敲出來的代碼自然非同凡響, 它的設計簡直就是使用設計模式的典範:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

添加描述

更牛的是, 他們是以一種叫做“模式生成架構”的方法來創建JUnit的, 換句話說,就是從零應用模式, 然後一個接一個, 直到你獲取最終的系統架構:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1

添加描述

我第一次看到這種方式, 真是驚爲天人,  我知道代碼要向模式進行重構, 還從來沒聽過由模式來構建系統!

但一想到Eric Gamma的背景, 就沒有什麼可驚訝的了, 因爲Eric Gamma 實際上是劃時代的書籍《設計模式:可複用面向對象軟件基礎》四位合著者(稱爲GoF,Gang of Four)之一

這本書的經典地位就不用說了, 像JUnit繁衍出來的xUnit一樣, 這本書也有很多的“繁衍品”, 例如

《Head First Degisn Pattern》 , 《設計模式解析》,《大話設計模式》。。。。

JUnit超級流行,幾乎是事實上的Java 單元測試和TDD的工具標準, 有人選擇了GitHub上最熱的三門語言Java,Ruby和Javascript , 對每個語言分析了1000個項目,找出了最常用的組件,可以看到JUnit 以30.7%並列第一

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1

添加描述

所以JUnit已經變成了程序員必備的技能, 不會JUnit就太Out了。

Junit入門

1、什麼是Junit4

JUnit4是一個易學易用的Java單元測試框架,一般我們在寫完一段代碼或一個方的時候,都要測試一下這段代碼和這個方法的邏輯是不是正確,輸入一定的數據,返回的數據是不是我們想要的結果,即我們在寫單個業務代碼針對結果進行測試。這時Junit就派上了大用場了。

2、爲何使用Junit4

也許有的初學者會說,項目完成之後測試不行嗎?如果你要這麼想的話,那就了,因爲隨着你代碼的增加,你牽扯到的模塊和項目中的邏輯就會越來越多,這樣測試起來就會非常的麻煩,而且容易出錯。Junit看起來是增加了代碼量,可是它可以大大的減少後期的測試時間,提升代碼的質量,讓代碼更易於維護。

3、Junit4的快速入門

下載並導入Junitjar包:

首先得需要去網上下載一個Junit4的一個jar包,保存到自己本地電腦裏面打開myEclipse新建一個Java項目,通過右擊項目-->Build Path --->Configure Build Path -->Add External JARs--->找到自己剛保存的jar路徑,點擊OK就可以啦

創建測試目錄:

接下來就要爲我們的測試建立特殊的路徑,這個特殊特殊在哪裏呢,因爲我們的測試代碼要單獨保存不能與源代碼進行混淆,到項目結束後可以直接把它刪除,而且不會對項目造成影響。怎麼創建呢:右擊項目名稱--->new---->suorce folder--->輸入你想要的測試文件名點擊OK就可以啦。

接下來我們得在項目中的src目錄下創建一個包,注意這兒的包名和test裏面的包名要保持一致,在我們src的包下寫我們項目的邏輯方法,在test的報下寫我們的測試方法,結構如圖所示,

339

添加描述

image.png

下面我們來寫一段簡單的邏輯代碼進行測試演練

package com.junit;public class method_junti {
    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }

    public int division(int a, int b) {
        return a / b;
    }}

方法很簡單,就是一般的加減乘除,下面我們就可以進行測試了,怎麼測試呢: 在我們的測試目錄下新建測試類junit_test,然後定義測試方法。代碼如下:

package com.junit;import static org.junit.Assert.*;import org.junit.Test;public class junit_test {

    
    //測試方法必須有@test;
    //該測試方法必須由public void修飾,沒有返回值;
    //該方法不帶任何參數;
    //新建一個源代碼測試文件單獨存放測試代碼;
    //測試類的包和被測試類的包保持一致;
    //測試方法間互相獨立沒有任何依賴;
    @Test    public void testAdd(){
        
        assertEquals(4, new method_junti().add(3, 0));
    }
    
    @Test    public void testSubtract(){
        assertEquals(3, new method_junti().subtract(6, 3));
    }
    
    @Test    public void testMultiply(){
        assertEquals(6, new method_junti().multiply(6, 1));
    }
    
    @Test    public void testDivision(){
        assertEquals(6, new method_junti().division(6, 1));
    }}

下面來講解一下assertEquals()這個函數,它的第一個參數是你預期的一個結果,第二個參數使我們想測試的函數,這個函數我們要通過先new出函數所在的類,然後通過類來調用方法,方法裏面的參數就是你在寫該方法是傳進來的參數。在這裏最好在你需要測試的方法的前面加上test,這樣的話就會比較規範一些

寫完之後你可以點擊測試類,然後在點擊run as,再點擊Junit test,你就能在彈出的窗口中看到你的測試結果,它會提示你失敗的個數和錯誤的個數。如果你只想測試一個方法怎麼辦呢,在你創建的測試類的下面還有目錄,列表裏面的會列出你所有的測試方法,你就可以右擊你想測試的方法,然後run as ---> junit test,測試成功後就會看到一個綠色的條,結果如圖:

700

添加描述

image.png

在這裏如果我們每一個方法都要自己手動的敲出它的測試方法,在這裏我們只是簡單的測試了幾個方法,在項目中如果我們有很多的方法需要測試,一個一個的敲的話會有些浪費時間了,這裏給大家介紹一個快速生成測試方法的一個方法: 點擊src下的method_junit.java--->右擊new--->看看後面的提示框中有麼有Junit test case,如果沒有的話點擊other,在提示框中輸入Junit 就會出現--->在彈出的對話框中找到Source folder,點擊後面的Browse將其改爲含有test的那個目錄,這裏有些可能會提示名字重複,把下面的 Name改改就行--->點擊next--->你會看到method_junit裏面的所有法,這時候你就可以選中它們,成成測試方法如下:

package com.junit;import static org.junit.Assert.*;import org.junit.Test;public class method_juntiTest2 {

    @Test    public void testAdd() {
        fail("Not yet implemented");
    }

    @Test    public void testSubtract() {
        fail("Not yet implemented");
    }

    @Test    public void testMultiply() {
        fail("Not yet implemented");
    }

    @Test    public void testDivision() {
        fail("Not yet implemented");
    }}

再把裏面fail後面的語句刪了就可以寫你想測試的東西呢!

4、junit測試失敗的兩種情況:

在前面的情況中我們都是測試的是成功的例子,但是Junit的作用只是測試的方法裏的返回數據是不是正確的,但是在數據返回正確的情況下我們未必是正確的,就比如如果你要求的是長方形的面積,但是你用的是周長公式,當你在測試的時候他也會給你測試成功,得到預期的結果,也就是說我們的測試用例對於邏輯錯誤是無能爲力的

測試失敗的情況一當我們預期值和程序執行的結果不一樣的時候就會測試失敗: 比如我們上面的測試加法函數的方法:

@Testpublic void testAdd(){
      assertEquals(3, new method_junti().add(3, 0));}

如果把預期結果3,改爲4,就會測試失敗,failure的後面就會出現一個1,提示一個測試失敗,運行時就能看到,這個應該不難理解。如果你仔細觀察的話,下面還會有相關的提示,如圖所示:

700

添加描述

image.png

測試失敗情況二:下面我們在來測試除法:

@Testpublic void testDivision(){
    assertEquals(6, new method_junti().division(6, 1));}

如果在這裏把除數改爲0,會出現什麼情況呢:後面的提示框中的error數就會變1;提示有一個錯誤。於是我們得出下面的兩種情況:

1、failure一般由單元測試使用的斷言方法判斷失敗所引起的,這表示測試點發現了問題,也就是說程序輸出的結果和預期的不一樣 2、error是由代碼異常引起的,他可能是由於測試代碼本身的錯誤,也可能是被測試代碼中隱藏的一個bug

5、Junit的運行流程:

首先我們先在test包下的com.junit新建一個junit case,和並且直接命名junit_case。在創建的時候把setUpBeforeClass(),tearDownAfterClass(),setUp() ,tearDown() 選上就會得到下面的代碼:

package com.junit;import org.junit.After;import org.junit.AfterClass;import static org.junit.Assert.*;import org.junit.Before;import org.junit.BeforeClass;import org.junit.Test;public class Junit_case {
    @BeforeClass    public static void setUpBeforeClass() throws Exception {
    }

    @AfterClass    public static void tearDownAfterClass() throws Exception {
    }

    @Before    public void setUp() throws Exception {
    }

    @After    public void tearDown() throws Exception {
    }

    @Test    public void test() {
        fail("Not yet implemented");
    }}

下面我們在每個方法中輸入一句簡單的輸出語句,看看他們的運行狀態,如下:

package com.junit;import static org.junit.Assert.*;import org.junit.After;import org.junit.AfterClass;import org.junit.Before;import org.junit.BeforeClass;import org.junit.Test;public class Junit_test1 {

    
    /* 1、BeforeClass修飾的方法會在所有方法被調用前執行
     * 而且該方法是靜態的,所以當測試類被加載後接着就執行它
     * 在內存中它只會存在一份,適合加載配置文件
     * 2、AfterClass修飾的方法用來對資源的清理,如關閉數據庫的連接
     * befoer和after修飾的方法在每個test修飾的方法執行前會被各執行一次,假如有兩個
     * test文件,before和after會被各執行兩次;
     * */
    @BeforeClass    public static void setUpBeforeClass() throws Exception {
        System.out.println("this is beforeclass");
    }

    @AfterClass    public static void tearDownAfterClass() throws Exception {
        System.out.println("this is afterclass");
    }

    @Before    public void setUp() throws Exception {
        System.out.println("this is before");
    }

    @After    public void tearDown() throws Exception {
        System.out.println("this is after");
    }

    @Test    public void test1() {
        System.out.println("this is test1");
    }}

如果運行上面的代碼,就會得到下面的結果

6、junit的常用註解:

上面我已經講解了@test,@BeforeClass,@AfterClass,@Before,@After這些註解的詳解可見這位仁兄的博客,下面提供相關地址:http://blog.csdn.net/zixiao217/article/details/52951679對於@test,他除了將一個普通的方法修飾爲測試方法外,還可以處理異常,設置超時。下面來對test的異常處理做講解test有兩個參數:expected和timeout,即異常處理和設置超時如果對我們上面的除數爲0的那個方法進行異常處理,那麼我們就可以看到代碼能夠正常,測試通過,代碼如下:

@Test(expected=ArithmeticException.class)public void testDivision(){
     assertEquals(6, new method_junti().division(6, 0));}

在測試一些對性能有要求的方法中設置超時是很有必要的,它可以檢測你的代碼能否在這個 時間段內運行出結果,設置方法如下:

@Test(timeout=2000)//單位是毫秒
 public void testWhile(){
    while(true){
        System.out.println("run forever");
    }}

@Ignore:在test方法上加上該修飾,測試的時候就會不執行該測試方法; @RunWith可以更改測試運行器;我們除了使用junit提供的測試運行器之外,還可以自定義 我們的運行器,只要繼承org.junit.runner.Runner 代碼如下:

import static org.junit.Assert.*;import org.junit.Test;import org.junit.runner.RunWith;import org.junit.runners.Suite;import org.junit.runners.Suite.SuiteClasses;@RunWith(Suite.class)@Suite.SuiteClasses({TaskTest1.class,TaskTest2.class,TaskTest3.class})public class SuitTest {
    
    public void test(){
        /*
         * 由於在開發的項目中,測試的類很多,一個一個運行很浪費時間,於是可以寫一個測試
         * 套件把所有需要測試的類組合在一起測試運行
         * 1、寫一個測試入口,這個類不含其它的方法;
         * 2、更改測試運行器@RunWith(Suite.class)
         * 3、將要測試的類作爲數組放在@Suite.SuiteClasses({})中;
         */
        
    }}

7、junit的參數設置

在上面的測試中,我們對一個方法都是隻測試了一組數據,可是在真正的項目中,一組數據往往是不夠的,我們需要很多組數據,如果每一組數組寫一個測試方法的話那可把我們的工作人員累死了!這時我們可以使用參數設置來解決這個問題。 代碼如下:

package com.junit;import static org.junit.Assert.*;import java.util.Arrays;import java.util.Collection;import org.junit.Test;import org.junit.runner.RunWith;import org.junit.runners.Parameterized;import org.junit.runners.Parameterized.Parameters;@RunWith(Parameterized.class)public class ParameterTest {
    //聲明變量存放預期值和測試數據;
    int expected=0;
    int input1=0;
    int input2=0;
    
    @Parameters    public static Collection<Object[]> test(){
    
        return Arrays.asList(new Object[][]{
                {3,1,2},
                {4,2,2}
            });     
    }

    public ParameterTest(int expected,int input1,int input2){
        this.expected=expected;
        this.input1=input1;
        this.input2=input2;
    }
    @Test    public void testAdd(){
        assertEquals(expected, new method_junti().add(input1, input2));
    }   }

我們需要測試多組數據,那麼我們就需要用到數組來存放多組數據,這裏用Arrays.asList來接收。

聊聊單元測試在SpringMVC中的運用

轉自 杜琪

遇到問題多思考、多查閱、多驗證,方能有所得,再勤快點樂於分享,才能寫出好文章。

一、單元測試

1. 定義與特點

單元測試(unit testing):是指對軟件中的最小可測試單元進行檢查和驗證。

這個定義有點抽象,這裏舉幾個單元測試的特性,大家感受一下:一般是一個函數配幾個單元測試、單元測試不應該依賴外部系統、單元測試運行速度很快、單元測試不應該造成測試環境的髒數據、單元測試可以重複運行。

2. 優點

單元測試使得我們可以放心修改、重構業務代碼,而不用擔心修改某處代碼後帶來的副作用。

單元測試可以幫助我們反思模塊劃分的合理性,如果一個單元測試寫得邏輯非常複雜、或者說一個函數複雜到無法寫單測,那就說明模塊的抽象有問題。

單元測試使得系統具備更好的可維護性、具備更好的可讀性;對於團隊的新人來說,閱讀系統代碼可以從單元測試入手,一點點開始後熟悉系統的邏輯。

3. 本文要解決的痛點

  1. 單測何時寫? 如果你的團隊在堅持TDD的風格,那就是在編碼之前寫;如果沒有,也不建議在全部業務代碼編寫完成之後再開始補單元測試。單元測試比較(最)合適的時機是:一塊業務邏輯寫完後,跟着寫幾個單元測試驗證下。

  2. 單測怎麼寫? 分層單測:數據庫操作層、中間件依賴層、業務邏輯層,各自的單元測試各自寫,互相不要有依賴。

  3. 單測運行太慢?

  • dao層測試,使用H2進行測試,做獨立的BaseH2Test、獨立的test-h2-applicationContext.xml,只對dao的測試

  • service層測試,依賴mockito框架,使用@RunWith(MockitoJUnitRunner.class)註解,就無需加載其他spring bean,具體用法

  • 對於依賴外部的中間件(例如redis、diamond、mq),在處理單測的時候要注意分開加載和測試,尤其是與dao的測試分開

二、Spring項目中的單元測試實踐

我們基於unit-test-demo這個項目進行單元測試的實踐。

1. dao層單元測試

最開始寫單測的時候,要連着DEV的數據庫,這時候會有兩個煩惱:網絡有問題的時候單測運行不通過、數據庫裏造成髒數據的時候會導致應用程序異常。這裏我們選擇H2進行DAO層的單元測試。有如下幾個步驟:

  • 在resources下新建目錄h2,存放schema.sql和data-prepare-user.sql文件,前者用於保存建表語句,後者用於準備初始數據

  • test-data-source.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"   xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">


    <!-- 初始化數據表結構 -->
    <jdbc:initialize-database data-source="dataSource">
        <jdbc:script location="classpath:h2/schema.sql" encoding="UTF-8"/>
        <jdbc:script location="classpath:h2/data-prepare-*.sql" encoding="UTF-8"/>
    </jdbc:initialize-database>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="${user.jdbc.url}"/>
        <property name="username" value="${user.jdbc.username}"/>
        <property name="password" value="${user.jdbc.password}"/>
        <!-- 連接池初始連接數 -->
        <property name="initialSize" value="3"/>
        <!-- 允許的最大同時使用中(在被業務線程持有,還沒有歸還給druid) 的連接數 -->
        <property name="maxActive" value="30"/>
        <!-- 允許的最小空閒連接數,空閒連接超時踢除過程會最少保留的連接數 -->
        <property name="minIdle" value="3"/>
        <!-- 從連接池獲取連接的最大等待時間 5 秒-->
        <property name="maxWait" value="5000"/>

        <!-- 強行關閉從連接池獲取而長時間未歸還給druid的連接(認爲異常連接)-->
        <property name="removeAbandoned" value="true"/>
        <!-- 異常連接判斷條件,超過180 秒 則認爲是異常的,需要強行關閉 -->
        <property name="removeAbandonedTimeout" value="180"/>

        <!-- 從連接池獲取到連接後,如果超過被空閒剔除週期,是否做一次連接有效性檢查 -->
        <property name="testWhileIdle" value="true"/>
        <!-- 從連接池獲取連接後,是否馬上執行一次檢查 -->
        <property name="testOnBorrow" value="false"/>
        <!-- 歸還連接到連接池時是否馬上做一次檢查 -->
        <property name="testOnReturn" value="false"/>
        <!-- 連接有效性檢查的SQL -->
        <property name="validationQuery" value="SELECT 1"/>
        <!-- 連接有效性檢查的超時時間 1 秒 -->
        <property name="validationQueryTimeout" value="1"/>

        <!-- 週期性剔除長時間呆在池子裏未被使用的空閒連接, 10秒一次-->
        <property name="timeBetweenEvictionRunsMillis" value="10000"/>
        <!-- 空閒多久可以認爲是空閒太長而需要剔除 30 秒-->
        <property name="minEvictableIdleTimeMillis" value="30000"/>

        <!-- 是否緩存prepareStatement,也就是PSCache,MySQL建議關閉 -->
        <property name="poolPreparedStatements" value="false"/>
        <property name="maxOpenPreparedStatements" value="-1"/>

        <!-- 是否設置自動提交,相當於每個語句一個事務 -->
        <property name="defaultAutoCommit" value="true"/>
        <!-- 記錄被判定爲異常的連接 -->
        <property name="logAbandoned" value="true"/>
        <!-- 網絡讀取超時,網絡連接超時 -->
        <property name="connectionProperties" value="connectTimeout=1000;socketTimeout=3000"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mybatis/mapper/*Mapper.xml"/>
        <property name="typeAliasesPackage" value="org.learnjava.dq.core.dal.bean"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="org.learnjava.dq.core.dal.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean></beans>
  • test-h2-applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 激活自動代理功能 -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <!-- spring容器啓動時,靜態配置替換 -->
    <context:property-placeholder location="classpath*:*.properties" ignore-unresolvable="true"/>

    <context:component-scan base-package="org.learnjava.dq.core.dal.dao"/>

    <import resource="test-data-sources.xml"/></beans>
  • UserInfoDAOTest 這個文件是DAO層單元測試的主要內容,我只寫了一個,讀者朋友可以下載代碼自己練習,把剩餘的幾個寫了。

PS:這裏我們只有一個DAO,所以spring容器加載就放在這個文件裏了,如果DAO多的話,建議抽出一個BaseH2Test文件,這樣所有的DAO單元測試只需要加載一次spring容器。

package org.learnjava.dq.core.dal.dao;import org.junit.Test;import org.junit.runner.RunWith;import org.learnjava.dq.core.dal.bean.UserInfoBean;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4Cla***unner;import java.util.Date;import javax.annotation.Resource;import static org.junit.Assert.*;/**
 * 作用:
 * User: duqi
 * Date: 2017/6/24
 * Time: 09:33
 */@RunWith(SpringJUnit4Cla***unner.class)@ContextConfiguration("classpath:test-h2-applicationContext.xml")public class UserInfoDAOTest {

    @Resource    private UserInfoDAO userInfoDAO;

    @Test    public void saveUserInfoBean() throws Exception {
        UserInfoBean userInfoBean = new UserInfoBean();
        userInfoBean.setUserId(1003L);
        userInfoBean.setNickname("wangwu");
        userInfoBean.setMobile("18890987675");
        userInfoBean.setSex(1);
        userInfoBean.setUpdateTime(new Date());
        userInfoBean.setCreateTime(new Date());

        int rows = userInfoDAO.saveUserInfoBean(userInfoBean);

        assertEquals(1, rows);
    }

    @Test    public void updateUserInfoBean() throws Exception {
    }

    @Test    public void getUserInfoBeanByUserId() throws Exception {
    }

    @Test    public void getUserInfoBeanByMobile() throws Exception {
    }

    @Test    public void listUserInfoBeanByUserIds() throws Exception {
    }

    @Test    public void removeUserInfoBeanByUserId() throws Exception {
    }}

2. service層單元測試

  • MockitoMocktio是一個非常易用的mock框架。開發者可以依靠Mockito提供的簡潔的API寫出漂亮的單元測試。

Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API. Mockito doesn’t give you hangover because the tests are very readable and they produce clean verification errors.

  • UserInfoManagerImplTest 單元測試,不應該依賴於DAO層的執行邏輯是否正確【否則就是集成測試】,需要假設DAO的行爲是什麼樣子,然後再看本層的邏輯是否正確。 這裏使用@RunWith(MockitoJUnitRunner.class)修飾當前的單元測試類,如果有多個單元測試類的話,可以考慮抽出一個基礎的BaseBizTest類。

package org.learnjava.dq.biz.manager.impl;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.learnjava.dq.biz.domain.UserInfo;import org.learnjava.dq.biz.manager.UserInfoManager;import org.learnjava.dq.core.dal.bean.UserInfoBean;import org.learnjava.dq.core.dal.dao.UserInfoDAO;import org.mockito.InjectMocks;import org.mockito.Mock;import org.mockito.MockitoAnnotations;import org.mockito.runners.MockitoJUnitRunner;import static org.junit.Assert.*;import static org.mockito.Mockito.*;/**
 * 作用:
 * User: duqi
 * Date: 2017/6/24
 * Time: 09:55
 */@RunWith(MockitoJUnitRunner.class)public class UserInfoManagerImplTest {

    @Mock //用於定義被Mock的組件
    private UserInfoDAO userInfoDAO;

    @InjectMocks //用於定義待測試的組件
    private UserInfoManager userInfoManager = new UserInfoManagerImpl();

    private UserInfo userInfoToSave;

    @Before    public void setUp() throws Exception {
        //用於初始化@Mock註解修飾的組件
        MockitoAnnotations.initMocks(this);

        userInfoToSave = new UserInfo();
        userInfoToSave.setMobile("18978760099");
        userInfoToSave.setUserId(7777L);
        userInfoToSave.setSex(1);
    }

    @Test    public void saveUserInfo_case1() throws Exception {
        //step1 準備數據和動作
        doReturn(1).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));

        //step2 運行待測試模塊
        Boolean res = userInfoManager.saveUserInfo(userInfoToSave);

        //step3 驗證測試結果
        assertTrue(res);
    }

    @Test    public void saveUserInfo_case2() throws Exception {
        //step1 準備數據和動作
        doReturn(0).when(userInfoDAO).saveUserInfoBean(any(UserInfoBean.class));

        //step2 運行待測試模塊
        Boolean res = userInfoManager.saveUserInfo(userInfoToSave);

        //step3 驗證測試結果
        assertFalse(res);
    }

    @Test    public void updateUserInfo() throws Exception {
    }

    @Test    public void getUserInfoByUserId() throws Exception {
    }

    @Test    public void getUserInfoByMobile() throws Exception {
    }

    @Test    public void listUserInfoByUserIds() throws Exception {
    }

    @Test    public void removeUserInfoByUserId() throws Exception {
    }}
  • Mockito要點

  • MockitoJUnitRunner:用於提供單元測試運行的容器環境

  • Mock:用於模擬待測試模塊中依賴的外部組件

  • InjectMock:用於標識待測試組件

  • org.mockito.Mockito.*:這個類裏的方法可以用於指定Mock組件的預期行爲,包括異常處理。

三、總結

  1. 單元測試的三個步驟

  • 準備數據、行爲

  • 測試目標模塊

  • 驗證測試結果

  1. 除了本文中提到的Junit、Mockito、H2,還有很多其他的單元測試框架,例如TestNGspock等。

  2. 在Java Web項目中,controller層一般不寫業務邏輯,也就沒有必要寫單元測試,但是如果要寫,也有辦法,可以參考我之前的文章:在Spring Boot項目中使用Spock框架

  3. 單元測試代碼也是線上代碼,要和業務代碼一樣認真對待,也需要注意代碼和測試數據的複用。

一位阿里 Java 工程師的技術小站。作者黃小斜,專注 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!(關注公衆號後回覆”Java“即可領取 Java基礎、進階、項目和架構師等免費學習資料,更有數據庫、分佈式、微服務等熱門技術學習視頻,內容豐富,兼顧原理和實踐,另外也將贈送作者原創的Java學習指南、Java程序員面試指南等乾貨資源)


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章