單元測試介紹

簡介

軟件測試是執行的軟件以驗證代碼狀態(state testing)或事件序列(behavior testing)符合預期。html

軟件單元測試幫助開發人員驗證程序的部分邏輯是正確的。java

單元測試在計算機編程中單元測試(Unit Testing)又稱爲模塊測試,是針對代碼模塊(軟件設計的最小單位)來進行正確性檢驗的測試工做。在過程化編程多是整個模塊、但更可能是函數或過程等;對於面向對象編程一般是整個接口,好比類,也多是方法。使用代碼來測試代碼,關注狀態和行爲測試。單元測試所測試的百分比一般被稱爲測試覆蓋率。python

單元測試基於測試用例進行組織。理想的狀況下,測試用例互相獨立,爲此使用樁、mockfake、服務虛擬化和測試框架等,一般由開發或者白盒測試人員書寫。對於數據庫和緩存等操做,傳統建議用mock的方式進行單元測試。可是依賴較多的狀況下mock比較耗時耗力,爲此儘可能採用本地構建實際數據、緩存等中間件的方式,儘可能少使用mock,對於網絡依賴,則儘可能採用服務虛擬化。android

測試代碼與實際代碼分開。
 git

 

單元測試主要針對各類輸入組合進行測試,經常使用的方法有等價類劃分和邊界值等。同時特別關注控制流和數據流,經過關注ifforswitchwhile等分支條件,儘可能提升覆蓋率。另外單元測試要格外關注異常處理邏輯。github

單元測試重視覆蓋率,可是不爲了追求覆蓋率而不顧實際狀況。一般通常的公司在覆蓋率50%左右能達到比較好的性價比。追求90%以上的覆蓋率一般比較有難度,即使是google之類的領導性的公司,代碼覆蓋率也只達到了80%多。web

單元測試能提早發現一些集成、系統測試階段的問題,可是不能替代後續測試。數據庫

單元測試在代碼高內聚鬆耦合及公共庫提取完整等狀況比較容易進行測試編程

單元測試一般與小提交、每次提交觸發測試等持續集成配合使用。flask

單元測試自動生成用例是較前沿的技術,相關商業工具備:AgitarOne、Jtest 、SilverMark Test Mentor
開源工具備:CodePlex AnalytiX、EvoSuite、Randoop、Palus、Daikon, Eclat, Javalanche, Java PathFinder, JCrasher, jWalk, Korat, RecGen和ReCrash等。固然有很多公司直接用python控制各類語言生成單元測試用例。

單元調用系統公共方法到產生結果之間的部分。結果能夠是:返回值(非void)、系統的狀態或行爲改變、不受控制的第三方系統調用。單元測試的範圍能夠小到一個方法,大到多個類。測試單元不是越小越好,把工做單元縮到最小,最後會不得不僞造一堆東西,這些東西並非使用公共API的真實最終結果,而是生成結果過程當中的一些中間狀態。近年來,單元測試有測試功能、性能、安全等趨向,好比安卓中就有本地單元測試和基於仿真的單元測試。

參考資料:https://en.wikipedia.org/wiki/Unit_testing

單元測試的組織:AAA(arrange, act, and assert) 可能還有After。

 

 

哪些項目最適合單元測試?
* 平臺類庫性質的項目
* 規模大的,須要長時間維護的
* 正確性要求極高的項目

哪些代碼須要測試?
* UI層
* UI Business
* 業務層
* 數據層

通常只須要測公開方法

 

單元測試的前提
* 程序層次結構清楚
* 層間解耦
* 外部依賴小

 

最最糟糕的被測方法
* 業務代碼混在UI層方法中
* 數據庫操做混在業務代碼中
* 外部依賴不少

 

UI層的測試
* 不須要具體的業務實現
* 能夠看到在不一樣業務下的UI表現
* 關鍵字: 靜態Mock

業務層的測試
* 不須要具體的數據層
* 不須要UI層來驗證
* 關鍵字: 動態Mock

業務層的測試,爲了去除依賴,mock是必須的。
但在使用每個Mock前,請想一想,是否能夠經過重構,擺脫這個依賴。

先定好接口,而後對於具體實現,就可使用TDD


自動化
關鍵字:人工干預(好比須要網絡鏈接、數據庫等),經過Mock,去除干預。
關鍵字:DailyBuild 。

好的測試:可重複、獨立的
 

 

單元測試框架

開源的以xunit爲主。商業工具備Typemock Isolator.NET/Isolator++, TBrun, JustMock, Parasoft Development Testing (Jtest, Parasoft C/C++test, dotTEST), Testwell CTA++ and VectorCAST/C++等。

Python: unittest、pytest(推薦)、nose
Java: Junit(推薦)、TestNG、Spock。能夠理解Junit是TestNG的子集,可是Junit使用更普遍。Java的主流測試框架是JUnit和TestNG。前者用戶最多,對mock框架兼容好。後者可讀性更好。

單元測試的好處

不寫單元測試的藉口:
* 編寫單元測試太花時間了
* 運行測試的時間太長了
* 測試代碼並非個人工做
* 代碼太複雜了,依賴太多,不能作單元測試

* 沒有單元測試,這些問題照樣能發現。


1, 儘早發現bug,下降bug修復成本。在比較理想的狀況下,單元測試能發現佔比40%左右的BUG。
2, 驗證功能符合預期
3, 文檔化
4, 確認代碼修改沒有影響其餘功能。
5, 理解當前系統行爲
6, 驗證第三方代碼。
7, 簡化集成(自底向上)
8,TDD

9,加強自信,減小沒必要需要的時間浪費。

10,及早發現架構等設計方面的問題。

 

什麼是好的單元測試?

單元測試常見的問題:

•測試毫無心義
•測試不穩定
•測試須要很長的時間來執行
•測試沒有充分覆蓋的代碼
•測試耦合太緊,修改麻煩
•測試準備工做複雜。

 

• [F]ast
測試執行控制在3分鐘之內。儘可能減小依賴。
• [I]solated
• [R]epeatable
好比當前時間。必要時可使用Sandbox。
• [S]elf-validating
 print、System.out.println(),測試框架能夠減小代碼、減小沒必要要的輸出。要能自驗證、自安排、自動化。一般與buildbot、jenkins、TeamCity之類的集成系統,有提交的時候觸發構建與測試,甚至持續交付。

固然還有簡單Simple的意思。能簡單地書寫用例、定位問題。
• [T]imely
及時更新測試代碼,免得後期對代碼生疏了更耗時。必要時有評審也自動系統監控。後期補單元測試的價值這沒有前期大。

 

單元測試的內容

Right-BICEP

Right 結果正確
B 邊界條件
I 反向關係
C 經過其餘方法交叉檢查結果
E 錯誤條件
P 性能

邊界值:

•虛假或不一致的輸入值,如文件名
「* W:!點¯x\&GI / W $→> $ G / H#@ WQ。
•格式錯誤,如電子郵件地址缺乏頂級域名( fred@foobar. )
•計算中可能致使數值溢出。
•空或沒有值,如0,0.0,"」或null。
•值遠遠超過合理的指望,好比年齡180歲。
•列表中有不應有的重複名單。
•對有序列表進行排序。
•亂序

涉及Conformance、Ordering、Range、Reference、Existence、Cardinality、Time(相對,好比、絕對時間、併發)。

好比索引:

•起始和結束索引具備相同的值
•起始值比結束值大
•指數爲負
•結束值超過容許
•不能匹配具體項。

引用:

•跨範圍引用了什麼
•外部依賴
•是否須要對象在某一狀態
•必須存在的任何其餘條件

反向關係

好比用加法驗證加法,用除法驗證乘法。數據庫的插入與查詢、加密與解密等。百分比的總數必須等於1。

錯誤條件:
內存滿、磁盤滿、網線斷、郵件進入黑名單、程序崩潰、時間錯誤、系統負載高、色彩位數低、分辨率低等。

性能:單元測試中計時。JUnitPerf  JMeter等。

 

以上部分主要來源junit8,須要有空時再次閱讀。

 

簡單的getter和setter之類代碼一般不須要測試。JVM相關內容一般能夠認爲是可靠的。對已有代碼的測試,一般從出錯最多的地方開始。

 

測試驅動開發

要把測試當作設計工具,不只僅是質量保證工具。

 

測試驅動開發概念參考:http://osherove.com/blog/2007/10/8/the-various-meanings-of-tdd.html

服務虛擬化

在軟件工程中,服務虛擬化是在基於異構組件(如API驅動、基於雲計算的應用及SOA等)的應用中仿真具體應用行爲的方法。

使用場景:

  • 還沒有完成

  • 仍在發展

  • 第三方或合夥人控制

  • 測試的時間和容量有限制

  • 很難規定或在測試環境中配置

  • 須要由不一樣的團隊具備不一樣的測試數據設置和其餘要求的同時訪問

  • 負載和性能測試的成本很高

 

常見虛擬資產的建立的方法:
1,錄製真實通訊
2,日誌
3,分析接口規格
4,自定義行爲控制接口

 

在SOA等體系中,使用mock工做量很大,敏捷開發加快了迭代速度,爲此mock較多的狀況下,一般不如採用服務虛擬化。

簡單的虛擬化一般經過配置hosts文件,把目標服務器指向自定義服務器。python的flask框架由於開發速度快,常常被採用。在TCP和UDP層,python socket常用。這塊不只是對單元,對接口甚至是系統等測試都意義重大。

經常使用的框架以下:

https://github.com/oarrabi/mockpy(貌似只支持MAC平臺),以HTTP模擬爲主。

阿里巴巴出品:https://github.com/thx/RAP  主要爲幫助web工程師管理API。RAP經過GUI工具幫助WEB工程師更高效的管理接口文檔,同時經過分析接口結構自動生成Mock數據、校驗真實接口的正確性,使接口文檔成爲開發流程中的強依賴。有告終構化的API數據,RAP能夠作的更多,而咱們能夠避免更多重複勞動。

wiremock: http://wiremock.org/ https://github.com/tomakehurst/wiremock (推薦)

 

  • HTTP響應打樁,可匹配URL,標題和正文內容

  • 請求驗證

  • 在單元測試運行,做爲一個獨立的進程或爲WAR應用

  • Java API,JSON文件和JSON加HTTP配置

  • 樁錄製/回放

  • 故障注入

  • 每一個請求條件代理

  • 瀏覽器代理支持請求檢查和更換

  • 狀態的行爲模擬

  • 可配置的響應延遲 

 

junitperf用於單元性能測試。
Feed4junit用於提供數據驅動。

 

https://github.com/jamesdbloom/mockserver 在Java, JavaScript and Ruby中mockHTTP或HTTPS。MockServer內置代理內省流量並支持端口轉發、web轉發等。

 

https://github.com/splunk/eventgen 是Splunk的事件生成器,基於Python。

 

https://github.com/dreamhead/moco (比較推薦,支持HTTP和socket)

 

快速入門

 

@FunctionalInterface
public interface Scoreable {
   int getScore();
}
import java.util.*;

public class ScoreCollection {
   private List<Scoreable> scores = new ArrayList<>();
   
   public void add(Scoreable scoreable) {
      scores.add(scoreable);
   }
   
   public int arithmeticMean() {
      int total = scores.stream().mapToInt(Scoreable::getScore).sum();
      return total / scores.size();
   }
}
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*; 
import org.junit.*;

public class ScoreCollectionTest {
   @Test
   public void answersArithmeticMeanOfTwoNumbers() {
      // Arrange
      ScoreCollection collection = new ScoreCollection();
      collection.add(() -> 5);
      collection.add(() -> 7);
      
      // Act
      int actualResult = collection.arithmeticMean();
      
      // Assert
      assertThat(actualResult, equalTo(6));
   }
}

參考資料:https://en.wikipedia.org/wiki/Service_virtualization

 

Java Junit簡介

JUnit的4.x版使用註解來指定測試。JUnit的主頁:http://junit.org/,代碼託管:https://github.com/junit-team/junit。JUnit測試是類中用於測試的方法。使用註解@org.junit.Test。方法中使用的斷言方法(JUnit或其餘斷言框架提供),檢查代碼的執行的實際結果與預期。

 

下面的代碼演示JUnit測試。

建立java工程first,並在src目錄下建立test目錄。

建立類MyClass:

package first;

public class MyClass {
    
    public int multiply(int x, int y) {
        
        // the following is just an example
        if (x > 999) {
          throw new IllegalArgumentException("X should be less than 1000");
        }
        return x / y;
  }
}

建立測試類MyClassTest,建立方法參見下面的安裝配置部分。

Selecting the methods to test

Eclipse prompt for adding JUnit to the project classpath

package first;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class MyClassTest {
  
  @Test(expected = IllegalArgumentException.class)
  public void testExceptionIsThrown() {
    MyClass tester = new MyClass();
    tester.multiply(1000, 5);
  }
  
  @Test
  public void testMultiply() {
    MyClass tester = new MyClass();
    assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
  }
}

選中測試類, 右鍵選擇Run-As → JUnit Test.          

Result of running a unit test

                         

 

在普遍使用的Junit命名方法是類名稱下測試和「測試」加Test後綴。should經常使用於測試方法名,如ordersShouldBeCreated,menuShouldGetActive,以加強可讀性。Maven經過surfire插件自動生成Tests後綴的測試類。


多個測試類能夠組合成test suite。運行test suite將在該suite按照指定的順序執行全部測試類。下面演示包含兩個測試類 (MyClassTest和MySecondClassTest) 的測試集,經過@Suite.SuiteClasses statement能夠增長測試類。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests {

}

test suite中也能夠包含test suite。

經過標準的Java代碼能夠在Eclipse以外運行JUnit測試。Apache Maven的或Gradle框架之類的構建框架一般與持續集成服務器(如Hudson或Jenkins)組合按期自動執行測試。
org.junit.runner.JUnitCore類的runClasses()方法容許運行測試類,返回爲org.junit.runner.Result對象,包含測試結果信息。

下面類演示如何運行MyClassTest。這個類將執行測試類,輸出潛在錯誤到控制檯。

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class MyTestRunner {

  public static void main(String[] args) {
    Result result = JUnitCore.runClasses(MyClassTest.class);
    for (Failure failure : result.getFailures()) {
      System.out.println(failure.toString());
    }
  }
}

以上類能夠像Java類同樣從命令行運行,你只須要添加JUnit Jar到classpath便可。

 

Junit基礎

  • 註解

Junit4.*使用註解(annotation)標識測試方法和配置測試,如下是經常使用相關annotation的簡要說明。特別重要的註解以下:

註解 描述
@Test  
public void method()
標識方法爲測試方法
@Test  (expected = Exception.class) 標識拋出指定的異常
@Test(timeout=100) 超時,單位ms
@Before 
public void method()
每一個測試執行以前的準備工做。用來準備測試環境,如讀取輸入數據,初始化類等。
@After  
public void method()
每一個測試執行以後的清理工做。如刪除臨時文件,恢復初始設置,釋放內存等。
@BeforeClass 
public static void method()
每一個測試集執行以前的準備工做。用來執行費時任務,如鏈接數據庫。必須是Static方法。
@AfterClass 
public static void method()
每一個測試集執行以後的清理工做。用來清理,如斷開數據庫鏈接,必須是Static方法。
@Ignore or @Ignore("Why disabled") 忽略指定測試方法,用於測試類還沒有準備好等狀況,使用時最好標明忽略的緣由。

 @RunWith會替代默認的org.junit.runner.Runner類,好比:

@RunWith(Suite.class)
public class MySuite {
}

Mock也須要使用註解。

  • 測試用例組織
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

 

@RunWith(Suite.class)
@Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class, Assumption.class })
public class TestSuite {
}

斷言

 

JUnit會假定全部測試方法按任意順序執行,也就是說,一個測試不該該依賴其它測試。

Junit 4.11容許你用Annoation以字母順序對測試方法進行排序,使用方法爲用@FixMethodOrder(MethodSorters.NAME_ASCENDING)。默認使用固定但不可預期的順序,對應參數爲`MethodSorters.DEFAULT`,也可使用`MethodSorters.JVM`,表明JVM的默認方式,每次運行順序會不一樣。

自定義的實例:

 

package org.hamcrest.examples.tutorial;

import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

public class IsNotANumber extends TypeSafeMatcher<Double> {

  @Override
  public boolean matchesSafely(Double number) {
    return number.isNaN();
  }

  public void describeTo(Description description) {
    description.appendText("not a number");
  }

  @Factory
  public static <T> Matcher<Double> notANumber() {
    return new IsNotANumber();
  }

}

 

使用:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;

import junit.framework.TestCase;

public class NumberTest extends TestCase {

  public void testSquareRootOfMinusOneIsNotANumber() {
    assertThat(Math.sqrt(-1), is(notANumber()));
  }
}

切記,要靜態導入notANumber, 參考資料:https://code.google.com/p/hamcrest/wiki/Tutorial。

稍微複雜點的實例:

package com.example;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> {
    
    private final Comparable<T> expValue;
    
    public LessThanOrEqual(T expValue) {
        this.expValue= expValue;
    }
    
    @Override
    public void describeTo(Description desc) {
        desc.appendText(" less than or equal(<=)"
        +expValue);
    }
    
    @Override
    public boolean matches(Object t) {
        int compareTo = expValue.compareTo((T)t);
        return compareTo > -1;
    }
    
    @Factory
    public static<T extends Comparable<T>> Matcher<T> lessThanOrEqual(T t) {
        return new LessThanOrEqual(t);
    }
}

使用:

    @Test
    public void lessthanOrEquals_matcher() throws Exception
    {
        int actualGoalScored = 2;
        int expGoalScored= 4;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored));
        expGoalScored =2;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored ));
        
        double actualDouble = 3.14;
        double expDouble = 9.00;
        assertThat(actualDouble, lessThanOrEqual(expDouble));
        String authorName = "Sujoy";
        String expAuthName = "Zachary";
        assertThat(authorName, lessThanOrEqual(expAuthName));
    }

安裝配置

在Gradle編譯時使用Junit:
 

apply plugin: 'java'
dependencies {
  testCompile 'junit:junit:4.12'
}

 

Maven:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

 

Eclipse 等IDE自帶Junit。


Eclipse對Junit的支持

Eclipse有建立JUnit測試的嚮導。例如,要爲現有類建立JUnit測試或測試類,在類單擊鼠標右鍵,New → JUnit Test Case。File → New → Other... → Java→ JUnit也可打開相似窗口。


執行:在類單擊鼠標右鍵Run-as →JUnit Test,執行類中全部測試。Alt+Shift+X, ,T,若是光標在測試裏面,只會執行當前測試。

 

 

只看失敗的測試:

測試失敗時才彈出:

 

拷貝錯誤信息

Copy failed tests into clipboard

 JUnit的靜態導入
靜態導入容許在類中定義的public static字段和方法不指定類就可使用。

// without static imports you have to write the following statement
Assert.assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

// alternatively define assertEquals as static import
import static org.junit.Assert.assertEquals;

// more code
// use assertEquals directly because of the static import
assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

Eclipse中能夠配置自動靜態導入:Window → Preferences and select Java → Editor → Content Assist → Favorites.

  • org.junit.Assert

  • org.hamcrest.CoreMatchers

  • org.hamcrest.Matchers

Adding static imports to the preferences

這樣就可使用Content Assist(快捷方式Ctrl+Space)添加方法導入。

異常測試

註解:@Test (expected = Exception.class)能夠測試單個異常。

測試多個異常的方法:

try {
   mustThrowException(); 
   fail();
} catch (Exception e) {
   // expected
   // could also check for message of exception, etc.
}

插件測試

JUnit的插件測試爲插件書寫單元測試。這些測試運行特殊的測試執行器,在單獨的虛擬機中生成Eclipse實例。

 

高級用法

參數化(數據驅動)

使用註解@RunWith(Parameterized.class)便可。

測試類必須包含@Parameters註解的靜態方法返回數組的集合,用於做爲測試方法的參數。public域使用@parameter註解能夠取得測試注入測試值。

package first;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
import static org.junit.runners.Parameterized.*;

@RunWith(Parameterized.class)
public class ParameterizedTestFields {

    // fields used together with @Parameter must be public
    @Parameter
    public int m1;
    @Parameter (value = 1)
    public int m2;
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}


使用構造方法也能夠實現相似的效果:

package first;

import static org.junit.Assert.assertEquals;

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 ParameterizedTestUsingConstructor {

    private int m1;
    private int m2;
    
    public ParameterizedTestUsingConstructor(int p1, int p2) {
        m1 = p1;
        m2 = p2;
    }
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}

 

Rule

Rule能夠靈活的增長或者重定義測試類每一個測試方法的行爲。經過@Rule註解能夠建立和配置在測試方法使用的對象。好比靈活地指定異常:

package first;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class RuleExceptionTesterExample {

  @Rule
  public ExpectedException exception = ExpectedException.none();
  
  @Test
  public void throwsIllegalArgumentExceptionIfIconIsNull() {
    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("Negative value not allowed");
    ClassToBeTested t = new ClassToBeTested();
    t.methodToBeTest(-1);
  }
}

 

注意上述代碼只作演示,不能實際執行。

JUnit的已經提供了規則的幾個有用的實現。例如, TemporaryFolder類在測試執行完畢後會刪除文件。

package first;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class RuleTester {

  @Rule
  public TemporaryFolder folder = new TemporaryFolder();
  
  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFolder = folder.newFolder("newfolder");
    File createdFile = folder.newFile("myfilefile.txt");
    assertTrue(createdFile.exists());
  }
}

 

實現TestRule接口可自定義Rule。這個接口的apply(Statement, Description)方法返回Statement實例。Statement即JUnit運行時中的測試,Statement#evaluate()運行它們。Description 描述了單個測試。它經過反射閱讀測試信息。

 

下例子添加日誌語句到Android應用。

import android.util.Log;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class MyCustomRule implements TestRule {

    private Statement base;
    private Description description;
    
    @Override
    public Statement apply(Statement base, Description description) {
        this.base = base;
        this.description = description;
        return new MyStatement(base);
    }
    
    public class MyStatement extends Statement {
    
        private final Statement base;
        public MyStatement(Statement base) {
            this.base = base;
        }
        
        @Override
        public void evaluate() throws Throwable {
            Log.w("MyCustomRule",description.getMethodName() + "Started");
            try {
                base.evaluate();
            } finally {
                Log.w("MyCustomRule",description.getMethodName() + "Finished");
            }
        }
    }
}

 

使用Rule:

@Rule
public MyCustomRule myRule = new MyCustomRule();

 

更多關於Rule的資料:https://github.com/junit-team/junit/wiki/Rules

分類

public interface FastTests { /* category marker */
}

public interface SlowTests { /* category marker */
}

public class A {
  @Test
  public void a() {
    fail();
  }
  
  @Category(SlowTests.class)
  @Test
  public void b() {
  }
}

@Category({ SlowTests.class, FastTests.class })
public class B {
  @Test
  public void c() {
  }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b and B.c, but not A.a
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b, but not A.a or B.c
}

  本部分參考資料:https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.8.md

Java Junit斷言


標準斷言:

JUnit的Assert類提供一些靜態方法,通常以assert開頭,參數分別爲錯誤信息,指望值,實際值。若是指望與實際不符,將拋出AssertionException異常。

如下給出這類方法的概覽,[]中的參數爲可選,類型爲String。

 

語句 描述
assertTrue([message,] boolean condition) 確認布爾條件爲真。
assertFalse([message,] boolean condition) 確認布爾條件爲假
assertEquals([message,] expected, actual) 確認返回等於指望,數組等比較的是地址而非內容。
assertEquals([message,] expected, actual, tolerance) 測試float或double相等。   tolerance爲允許的偏差。
assertNull([message,] object) 對象爲null
assertNotNull([message,] object) 對象非null
assertSame([message,] expected, actual) 變量指向同一對象.
assertNotSame([message,] expected, actual) 變量指向不一樣對象

assertEquals(double expected,double actual, double delta)能夠解決double的精度偏差,對於錢,建議使用BigDecimal類型。另外還有個特殊的fail(message)

 

Hamcrest對斷言進行了擴展

assertThat擴展了斷言,語法以下:

public static void assertThat(Object actual, Matcher matcher)。

導入import static org.hamcrest.CoreMatchers.*;

Matcher實現了org.hamcrest.Matcher接口,而且能夠組合,好比:
•  assertThat(calculatedTax, is(not(thirtyPercent)) );
•  assertThat(phdStudentList, hasItem(DrJohn) );
•  assertThat(manchesterUnitedClub, both( is(EPL_Champion)).and(is(UEFA_Champions_League_Champion)) );

Matcher列表以下:
 allOf ,  anyOf ,  both ,  either ,  describedAs ,  everyItem ,  is ,  isA ,  anything ,
hasItem ,  hasItems ,  equalTo ,  any ,  instanceOf ,  not ,  nullValue ,  notNullValue ,
sameInstance ,  theInstance , startsWith ,  endsWith , and  containsString。

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.List;

import org.junit.Test;

public class AssertThatTest {
    
    @Test
    public void test_matcher_behavior() throws Exception {
        
        int myAge = 30;
        
        //examine the exact match with equalTo and is
        assertThat(myAge, equalTo(30));
        assertThat(myAge, is(30));
        
        //examine partial match with not()
        assertThat(myAge, not(equalTo(33)));
        assertThat(myAge, is(not(33)));
    }
    
    @Test
    public void verify_multiple_values() throws Exception {
        
        double myMarks = 100.00;
        assertThat(myMarks, either(is(100.00)).or(is(90.9)));
        assertThat(myMarks, both(not(99.99)).and(not(60.00)));
        assertThat(myMarks, anyOf(is(100.00),is(1.00),is(55.00),is(88.00),is(67.8)));
        assertThat(myMarks, not(anyOf(is(0.00),is(200.00))));
        assertThat(myMarks, not(allOf(is(1.00),is(100.00),is(30.00))));
    }
    
    @Test
    public void verify_collection_values() throws Exception {
        
        List<Double> salary =Arrays.asList(50.0, 200.0, 500.0);
        assertThat(salary, hasItem(50.00));
        assertThat(salary, hasItems(50.00, 200.00));
        assertThat(salary, not(hasItem(1.00)));
    }
    
    @Test
    public void verify_Strings() throws Exception {
        
        String myName = "John Jr Dale";
        assertThat(myName, startsWith("John"));
        assertThat(myName, endsWith("Dale"));
        assertThat(myName, containsString("Jr"));
    }
}

參考資料

Pragmatic Unit Testing in Java 8 with JUnit -2015 英文 pdf

Pragmatic Unit Testing in Java with JUnit -2004 英文 pdf -- 中文翻譯 《單元測試之道Java版》 掃描版

相關文章
相關標籤/搜索