1. 程式人生 > >Powermock學習之基本用法

Powermock學習之基本用法

本文主要內容:
1@PrepareForTest註釋
2、訪問私有狀態內容
3、抑制(禁止載入),即不執行,不需要的內容
4、測試聽眾
5、模擬策略
6、模擬系統類

PowerMock是一個Java模擬框架,可用於解決通常認為很難甚至無法測試的測試問題。使用PowerMock,可以模擬靜態方法,刪除靜態初始化程式,允許模擬而不依賴於注入,等等。PowerMock通過在執行測試時在執行時修改位元組碼來完成這些技巧。PowerMock還包含一些實用程式,可讓您更輕鬆地訪問物件的內部狀態。

PowerMock由兩個擴充套件API組成。
一個用於EasyMock,一個用於Mockito。要使用PowerMock,您需要依賴這些API中的一個以及測試框架。
目前PowerMock支援JUnit

TestNG。有三種不同的JUnit測試執行程式可供使用,一種適用於JUnit 4.4+,一種適用於JUnit 4.0-4.3,一種適用於JUnit 3(已棄用)。
TestNG有一個測試執行器,它需要版本5.11+,具體取決於您使用的PowerMock版本。

@PrepareForTest這個註釋告訴PowerMock準備測試某些類。需要使用此批註定義的類通常是需要進行位元組碼操作的類。這包括final類,帶有finalprivatestatic本地方法的類,這些方法應該被mock,並且類應該在例項化時返回一個模擬物件。

這個註釋可以放在測試類或者單獨的測試方法中。

  • 如果放在一個類上,這個測試類中的所有測試方法都將由PowerMock
    處理(以便測試)。
  • 如果要為單個方法重寫此行為,只需在特定測試方法上放置@PrepareForTest註釋。例如,如果您想在測試方法A中修改類X,但在測試方法B中希望X完好無損,那麼這很有用。在這種情況下,您在方法B上放置@PrepareForTest,並從value()列表中排除類X.
  • 有時你需要準備內部類來進行測試,這可以通過提供應該模擬到fullyQualifiedNames()列表的內部類的完全限定名來完成。
@Target( { ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited public @interface PrepareForTest { Class<?>[] value() default IndicateReloadClass.class; String[] fullyQualifiedNames() default ""; }

訪問內部狀態:

  • 使用Whitebox.setInternalState(..)設定一個例項或類的非公共成員。
  • 使用Whitebox.getInternalState(..)得到的例項或類的非公共成員。
  • 使用Whitebox.invokeMethod(..)呼叫例項或類的非公共方法。
  • 用於使用Whitebox.invokeConstructor(..)私有建構函式建立類的例項。

注意:
所有這些都可以在不使用PowerMock的情況下實現,這只是普通的Java反射。然而,反射需要大量的鍋爐程式碼,並且容易出錯,因此PowerMock會為您提供這些實用方法。PowerMock讓您可以選擇是否重構程式碼並新增getter / setter方法來檢查/更改內部狀態,或者是否使用其實用方法在不更改生產程式碼的情況下完成相同的操作。

抑制不需要的行為:

有時候你想甚至需要抑制某些建構函式,方法或靜態初始化器的行為,以便單元測試你自己的程式碼。一個典型的例子是當你的類需要在某種第三方框架中從另一個類擴充套件時。當這個第三方類在建構函式中做些什麼來阻止你單元測試你自己的程式碼時,就會出現問題。例如,框架可能會嘗試載入一個DLL或出於某種原因訪問網路或檔案系統。

抑制建構函式:

假設我們要測試ExampleWithEvilParent類的getMessage()方法,看起來好像很簡單。但是這個父類試圖載入一個dll檔案,當你為這個ExampleWithEvilParent類執行一個單元測試時它將不會出現。使用PowerMock,您可以禁止EvilParent的建構函式,以便您可以單元測試ExampleWithEvilParent類。

public class ExampleWithEvilParent extends EvilParent {

    private final String message;

    public ExampleWithEvilParent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}
public class EvilParent {
    public EvilParent() {
        System.loadLibrary("evil.dll");
    }
}

測試程式碼如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ExampleWithEvilParent.class)
public class ExampleWithEvilParentTest {

    @Test
    public void testSuppressConstructorOfEvilParent() throws Exception {
        //抑制建構函式
        suppress(constructor(EvilParent.class));
        final String message = "myMessage";
        ExampleWithEvilParent tested = new ExampleWithEvilParent(message);
        assertEquals(message, tested.getMessage());
    }
}

上面的例子在抑制超類建構函式和被測類時起作用。另一種抑制被測試類的建構函式,我們通過Whitebox.newInstance方法實現。例如,如果你自己的程式碼在它的建構函式中做了一些事情,那麼很難進行單元測試。這例項化該類而不呼叫建構函式。

public class ExampleWithEvilConstructor {

    private final String message;

    public ExampleWithEvilConstructor(String message) {
        System.loadLibrary("evil.dll");
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

通過下面的方式抑制:

ExampleWithEvilConstructor tested = Whitebox.newInstance(ExampleWithEvilConstructor.class);

請注意,您不需要使用@RunWith(..)註釋或將類傳遞給@PrepareForTest註釋。這樣做並沒有傷害,但沒有必要。

抑制私有方法:
在某些情況下,你只是想壓制一個方法並使其返回一些預設值,在其他情況下,你可能需要壓制或模擬一個方法,因為它會阻止你對自己的類進行單元測試。看看下面的組裝示例:

public class ExampleWithEvilMethod {

    private final String message;

    public ExampleWithEvilMethod(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message + getEvilMessage();
    }

    private String getEvilMessage() {
        System.loadLibrary("evil.dll");
        return "evil!";
    }
}

如果System.loadLibrary(“evil.dll”)在測試getMessage()方法時執行語句,則測試將失敗。避免這種情況的一個簡單方法是簡單地抑制該getEvilMessage方法。你可以使用

suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));

完整的測試如下:

@RunWith(PowerMockRunner.class)
@PrepareForTest(ExampleWithEvilMethod.class)
public class ExampleWithEvilMethodTest {

    @Test
    public void testSuppressMethod() throws Exception {
        //抑制私有方法
        suppress(method(ExampleWithEvilMethod.class, "getEvilMessage"));
        final String message = "myMessage";
        ExampleWithEvilMethod tested = new ExampleWithEvilMethod(message);
        assertEquals(message, tested.getMessage());
    }
}

禁止靜態初始化器:

public class ExampleWithEvilStaticInitializer {

    static {
        System.loadLibrary("evil.dll");
    }

    private final String message;

    public ExampleWithEvilStaticInitializer(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

這個大家應該比較熟悉,直接貼出完整測試:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("org.mycompany.ExampleWithEvilStaticInitializer")
public class ExampleWithEvilStaticInitializerTest {

    @Test
    public void testSuppressStaticInitializer() throws Exception {
        final String message = "myMessage";
        ExampleWithEvilStaticInitializer tested = new ExampleWithEvilStaticInitializer(message);
        assertEquals(message, tested.getMessage());
    }
}

抑制欄位:

public class MyClass {
    private MyObject myObject = new MyObject();


    public MyObject getMyObject() {
        return myObject;
    }
}
suppress(field(MyClass.class, "myObject"));

注意:

在powermock+mockit中,抑制語法如下:

PowerMockito.suppress(PowerMockito.method(類.class,"方法名"));

測試聽眾

PowerMock 1.1及以上版本具有測試監聽器的概念。測試監聽器可用於從測試框架獲取事件,例如測試方法開始和結束以及測試執行的結果。這些測試監聽器的目的是提供獨立於測試框架的方式,通過實現org.powermock.core.spi.PowerMockTestListener並將其傳遞給PowerMockListener註釋來獲取和響應這些通知。PowerMock有一些內建的測試監聽器供您使用。

1、AnnotationEnabler

考慮下面的一段程式碼:

@RunWith(PowerMockRunner.class)
@PowerMockListener(AnnotationEnabler.class)
 public class PersonServiceTest {

    @Mock
    private PersonDao personDaoMock;

    private PersonService classUnderTest;

    @Before
    public void setUp() {
        classUnderTest = new PersonService(personDaoMock);
    }
   ...
  }

使用@Mock註釋消除了手動設定和拆卸模擬的需要,這可以最大限度地減少重複測試程式碼並使測試更具可讀性。AnnotationEnabler適用於EasyMock和Mockito API。在EasyMock版本中,如果要建立部分模擬,還可以提供您希望模擬的方法的名稱,例如:

@RunWith(PowerMockRunner.class)
@PowerMockListener(AnnotationEnabler.class)
 public class PersonServiceTest {

    @Mock("getPerson")
    private PersonDao personDaoMock;

    private PersonService classUnderTest;

    @Before
    public void setUp() {
        classUnderTest = new PersonService(personDaoMock);
    }
   ...
  }

這段程式碼將指示PowerMock建立一個PersonDao只模擬”getPerson”方法的部分模擬。由於EasyMock支援好的和嚴格的模擬,你可以使用@MockNice和@MockStrict註釋來獲得這個好處。

在Mockito中,你只是spy(..)用來部分地模擬一個類或例項。

2、FieldDefaulter

此測試監聽器實現可用於在每次測試之後為junit測試中的每個成員欄位設定預設值。對於許多開發人員來說,使用JUnit時,建立tearDown方法和使所有引用無效的標準過程大致相同(有關此問題的更多資訊,請參閱此處)。但是,可以使用FieldDefaulter自動完成此操作。舉個例子,假設你有5個合作者想要在你的測試中進行模擬,並且你想確保每次測試之後它們都被設定為null,以允許它們被垃圾收集。所以不要這樣做:

@RunWith(PowerMockRunner.class)
public class MyTest {

    private Collaborator1 collaborator1Mock;
    private Collaborator2 collaborator2Mock;
    private Collaborator3 collaborator3Mock;
    private Collaborator4 collaborator4Mock;
    private Collaborator5 collaborator5Mock;

        ...
    @After
    public void tearDown() {
            collaborator1Mock = null;
            collaborator2Mock = null;
            collaborator3Mock = null;
            collaborator4Mock = null;
            collaborator5Mock = null;
    }
        ...

}

您可以使用FieldDefaulter測試監聽器徹底擺脫拆除方法:

@RunWith(PowerMockRunner.class)
@PowerMockListener(FieldDefaulter.class)
public class MyTest {

    private Collaborator1 collaborator1Mock;
    private Collaborator2 collaborator2Mock;
    private Collaborator3 collaborator3Mock;
    private Collaborator4 collaborator4Mock;
    private Collaborator5 collaborator5Mock;

        ...
}

模擬策略:

一個模擬策略可以用來更容易地將某些程式碼與PowerMock單獨測試到一個特定的框架中。模擬策略實現可以例如抑制一些方法,抑制靜態初始化器或攔截方法呼叫,並改變它們對於特定框架或一組類或介面的返回值(例如返回模擬物件)。例如,可以實施模擬策略以避免為測試編寫重複的設定程式碼。假設你使用的是框架X,為了讓你測試它,需要某些方法總是返回一個模擬實現。也許一些靜態初始化器也必須被抑制。而不是在測試之間複製這段程式碼,寫一個可重用的模擬策略是一個好主意。

PowerMock 1.1提供了三種模擬slf4j,java common-logging和log4j的模擬策略。以slf4j為例,假設您有一個如下所示的類:

public class Slf4jUser {
    private static final Logger log = LoggerFactory.getLogger(Slf4jUser.class);

    public final String getMessage() {
        log.debug("getMessage!");
        return "log4j user";
    }
}

這裡我們遇到了一個問題,因為記錄器在Slf4jUser類的靜態初始化器中被例項化。有時這會導致問題,具體取決於日誌配置,因此您想在單元測試中執行的操作是對日誌例項進行存根。這是完全可行的,而不使用模擬政策。一種方法是先從我們的測試中禁用Slf4jUser類的靜態初始化程式。然後,我們可以建立一個存根或Logger類的一個很好的模擬,並將其注入Slf4jUser例項。但是這還不夠,想象一下,我們已經配置了slf4j來使用log4j作為後端日誌記錄,然後在執行測試時我們會在控制檯中顯示以下錯誤:

log4j:ERROR A "org.apache.log4j.RollingFileAppender" object is not assignable to a org.apache.log4j.Appender" variable.
log4j:ERROR The class "org.apache.log4j.Appender" was loaded by
log4j:ERROR [[email protected]] whereas object of  type
log4j:ERROR "org.apache.log4j.RollingFileAppender" was loaded by [[email protected]].
log4j:ERROR Could not instantiate appender named "R".

為了避免這個錯誤資訊,我們需要準備org.apache.log4j.Appender測試。完整的測試設定將如下所示:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("org.myapp.Slf4jUser")
@PrepareForTest( Appender.class)
public class MyTest {

  @Before
  public void setUp() {
      Logger loggerMock = createNiceMock(Logger.class);
      Whitebox.setInternalState(Slf4jUser.class, loggerMock);
      ...
  }
  ...
}

這種設定行為將不得不被複制到處理slf4j的所有測試類。相反,您可以使用Slf4j模擬策略來照顧您為此設定。你的測試看起來像這樣:

@RunWith(PowerMockRunner.class)
@MockPolicy(Slf4jMockPolicy.class)
public class Slf4jUserTest {
     ...
}

請注意,我們沒有做任何設定來模擬slf4j,這Slf4jMockPolicy需要照顧。
模擬策略也可以像這樣連結或巢狀:

@RunWith(PowerMockRunner.class)
@MockPolicy({MockPolicyX.class, MockPolicyY.class})
public class MyTest {
    ...
}

請注意,鏈中的後續模擬策略可以覆蓋上述策略的行為。在這個例子中,這意味著MockPolicyY可能會覆蓋由定義的行為MockPolicyX。如果編寫自定義模擬策略,記住這一點很重要。

還可以建立自定義的模擬策略,這裡不再敘述。

模擬系統類:

PowerMock 1.2.5及以上版本支援Java系統類中的模擬方法,例如位於java.lang和java.net中的模擬方法。這可以在不修改JVM或IDE設定的情況下執行!儘管如此,mock這些類的方式有點不同。通常情況下,你需要準備包含靜態方法的類(我們稱之為X),但是因為PowerMock不可能為測試準備一個系統類,所以必須採取另一種方法。因此,不是準備X,而是準備在X中呼叫靜態方法的類!我們來看一個簡單的例子:

public class SystemClassUser {

    public String performEncode() throws UnsupportedEncodingException {
        return URLEncoder.encode("string", "enc");
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest( { SystemClassUser.class })
public class SystemClassUserTest {

    @Test
    public void assertThatMockingOfNonFinalSystemClassesWorks() throws Exception {
        mockStatic(URLEncoder.class);

        expect(URLEncoder.encode("string", "enc")).andReturn("something");
        replayAll();

        assertEquals("something", new SystemClassUser().performEncode());

        verifyAll();
    }
}

相關推薦

Powermock學習基本用法

本文主要內容: 1、@PrepareForTest註釋 2、訪問私有狀態內容 3、抑制(禁止載入),即不執行,不需要的內容 4、測試聽眾 5、模擬策略 6、模擬系統類 PowerMock是一個Java模擬框架,可用於解決通常認為很難甚至無法測試的測試問題。使用

Theano學習基本用法

1、函式定義,容器定義,原始方程列印 import numpy as np #需要用到的模組 import theano.tensor as T #用來定義張量,容器等 from theano import function#用來定義函式 from theano import pp#可以用

java學習基本數據類型

輸出 com 9.png 浮點型 可能 包裝 cnblogs 如果 高精度 基本數據類型: boolean ,只有兩個值,true或false 。在邏輯中用於判斷。 byte, 字節型,一字節,-128~127,不常用。 short,短整型,兩字節,-2^15~(2^15-

入門VMware Workstation下的Debian學習基本命令(二)

修改文件 移除 usermod 修改用戶密碼 系統 軟件 普通 格式 new   本章記錄如何在Linux終端進行命令操作命令下載路徑,模擬終端、dkpg管理軟件包、用戶組和用戶管理、文件屬性、文件與目錄管理、查看磁盤使用量。   (1)命令下載路徑:     wegt 路

oracle學習基本查詢和條件過濾,分組函數使用

pic sub 排序 acl date 數值 模糊查詢 使用 char oracle是殷墟出土的甲骨文的第一個單詞,所以在中國叫做甲骨文,成立於1977年,總部位於美國加州。 在安裝好後,主要有兩個服務需要開啟: 1,實例服務,OracleServiceORCL,決定是否可

ElasticSearch學習——基本的文檔CURD

文檔 uniq 同時 base64 arch source code 多條 pan 一、文檔的添加 POST http://127.0.0.1:9200/{index}/{type}/{id} { "key":"value", "key2":"value2", "key2

hibernate學習基本用法

初始化 erro 共享 org HR 如果 oca stat exceptio 一 映射文件User.hbm.xml 定義了持久化類實例是如何存儲和加載的,這個文件定義了持久化類和表的映射。 根據映射文件,Hibernate可以生成足夠的信息以產生所有的SQL語句,也就是

JVM基礎學習基本概念、可見性與同步

讀寫 應用 資源 而不是 檢查 依靠 完成 層次 並發 開發高性能並發應用不是一件容易的事情。這類應用的例子包括高性能Web服務器、遊戲服務器和搜索引擎爬蟲等。這樣的應用可能需要同時處理成千上萬個請求。對於這樣的應用,一般采用多線程或事件驅動的 架構 。對於Java來說,在

python學習基本數據類型

pan 運行 pytho 所有 find AC 範圍 檢測 分隔 python的基本數據類型有數字、字符串、列表、字典、元祖、布爾值 一、數字 1.1、字符轉換為數字 實例: a="123" b=int(a) print(b+100) 運行結果:

JAVA集合學習基本介紹

1.JAVA集合框架的體系結構   2.區別   1.List(列表)   特點:元素有序並且可以重複的集合,可以精確的控制每個元素的插入位置,或刪除某個位置的元素   主要實現類:     1.ArrayList(動態陣列):動態增長,適合查詢和更新元素,元素的值可以為null   

ES6 Generator函式基本用法(2)

Generator函式之基本用法(2) 上一篇文章中總結了Generator函式基本概念: yield表示式,與Iterator介面、for…of迴圈的關係,next方法,throw方法,return方法等內容。 這篇文章接著上一篇文章繼續總結Generator函式的基本用法 (1)

ES6 Generator函式基本用法(1)

Generator函式之基本用法 (1)基本概念 Generator函式是ES6 提供的一種非同步程式設計解決方案,語法與傳統函式完全不同。 Generator函式與普通函式在寫法上的不同 1.function命令與函式名之間有一個星號(*)。 2.函式體內部使用yield語

theano學習function用法

heano 當中的 function 就和 python 中的 function 類似, 不過因為要被用在多程序並行運算中,所以他的 function 有他自己的一套使用方式。   import numpy as np import th

rest-framework框架學習基本元件

序列化 建立一個序列化類 簡單使用 開發我們的Web API的第一件事是為我們的Web API提供一種將程式碼片段例項序列化和反序列化為諸如json之類的表示形式的方式。我們可以通過宣告與Django forms非常相似的序列化器(serial

Stoker的資料庫學習基本語句

資料庫學習之基本語句 資料庫 是一個檔案 儲存大量資料 按照一定的規則(SQL語句)進行儲存 資料庫管理系統 可以對資料庫 進行增刪改查(SQL語句) SQL語句 標準SQL語句:所有資料庫通用 SQL語句方言:資料庫廠商會為自己的資料庫提供特殊的語句

MongoDB 學習基本操作(五)

基本操作 MongoDB將資料儲存為一個文件,資料結構由鍵值(key=>value)對組成 MongoDB文件類似於JSON物件,欄位值可以包含其他文件、陣列、文件陣列 安裝管理mongod

RecycleView學習0 基本用法

1RecycleView Adapter 基本使用: 包括多種itemType,繫結事件, import android.content.Context; import android.support.v7.widget.RecyclerView; import

JS學習Date用法

在學習js的慢慢長路中,今天學到了Date類,用它可以獲取到我們當前的系統時間,學到最後做了一個測試,感覺收穫滿滿,也鞏固了一下學習今天學習的東西,為了方便自己以後查閱,特此把它放到部落格上。 <!DOCTYPE HTML> <html &g

Android學習基本元件

TextView  展示文字,不可編輯 Button 按鈕, EditText 文字輸入框 ProgressBar 進度條 一、TextView android:id  設定TextView的id值,是TextView的唯一標識。在JAVA程式碼塊中,也

jQuery學習基本選擇器、層次選擇器和並且選擇器

<!DOCTYPE html> <html><head><meta charset="utf-8" /><title>JQuery學習</title><script type="text/java