日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

powermockito测试私有方法_03 增强测试: 静态、私有方法处理

發布時間:2024/1/8 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 powermockito测试私有方法_03 增强测试: 静态、私有方法处理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

mockito 已經很強大,能幫我們完成大部分 mock 工作,但是對于一些特殊方法來說,還是無能為力。

例如,當我們使用系統獲取當前時間戳的時候,可能會調用 System.currentTimeMillis()。對于這個方法來說,我們無法 mock。往往就會遇到一個有趣的現象,一些測試過了一段時間就通不過了,項目中可能有對時間進行檢查的邏輯。

我們有一個項目是做財務報銷單,當費用產生后的幾個月后報銷就會失敗,我們的模擬數據是一個固定的時間,因此幾個月后重新運行這個項目的單元測試就不通過了。

另外,項目中不可避免的需要 mock 系統靜態方法、私有方法;以及對一些私有方法進行測試,雖然不推薦測試私有方法,但是如果遇到遺留系統,public 方法有時候巨大,測試的成本非常高。

配合 mockito 使用的另外一個框架是 powermock。它采用了字節碼技術,可以增強測試,解決 mock 靜態、私有方法,以及必要時測試靜態、私有方法。

為了便于演示,我給上一部分的例子中的 User 對象增加了 createAt 字段,createAt 字段在 register 方法內被填充,然后進行持久化。

更新后的 User 對象如下:

public class User {

private String email;

private String username;

private String password;

private Instant createAt;

public User(String email, String username, String password, Instant createAt) {

this.email = email;

this.username = username;

this.password = password;

this.createAt = createAt;

}

...

}

并給 user 添加對應的值,Instant.now() 調用的系統方法。

user.setCreateAt(Instant.now());

按照我們前面的測試,這樣會給測試帶來不變,因此需要想辦法 mock 掉 Instant.now 這個方法。

assertEquals("", argument.getValue().getCreateAt());

首先,引入 powermock 的相關依賴。powermock 有兩個模塊,一個是封裝 junit、另外一個是封裝 mockito,間接的依賴了 junit 和 mockito。因此可以先把原來的測試依賴移除,添加下面的兩個依賴即可。

org.powermock

powermock-module-junit4

2.0.2

test

org.powermock

powermock-api-mockito2

2.0.2

test

然后,使用 PowerMockRunner 代替 mockito 的 runner。并使用 @PrepareForTest 對用到該靜態方法的地方進行初始化。

@RunWith(PowerMockRunner.class)

@PrepareForTest(UserService.class)

在測試過程中,我們就可以 mock Instant.class 中的靜態方法,并且會影響 UserService 中使用它的地方。

Instant moment = Instant.ofEpochSecond(1596494464);

PowerMockito.mockStatic(Instant.class);

PowerMockito.when(Instant.now()).thenReturn(moment);

Instant.now() 就會按照我們期望的值返回結果,當然也可以按照時間戳 1596494464 來斷言了,完整的測試如下:

@RunWith(PowerMockRunner.class)

// 比如使用 PrepareForTest 讓 mock 在被測試代碼中生效

@PrepareForTest({UserService.class})

public class UserServiceAnnotationTest {

@Mock

UserRepository mockedUserRepository;

@Mock

EmailService mockedEmailService;

@Spy

EncryptionService mockedEncryptionService = new EncryptionService();

@InjectMocks

UserService userService;

@Test

public void should_register() {

// mock 前生成一個 Instant 實例

Instant moment = Instant.ofEpochSecond(1596494464);

// mock 并設定期望返回值

PowerMockito.mockStatic(Instant.class);

PowerMockito.when(Instant.now()).thenReturn(moment);

// given

User user = new User("admin@test.com", "admin", "xxx", null);

// when

userService.register(user);

// then

verify(mockedEmailService).sendEmail(

eq("admin@test.com"),

eq("Register Notification"),

eq("Register Account successful! your username is admin"));

ArgumentCaptor argument = ArgumentCaptor.forClass(User.class);

verify(mockedUserRepository).saveUser(argument.capture());

assertEquals("admin@test.com", argument.getValue().getEmail());

assertEquals("admin", argument.getValue().getUsername());

assertEquals("cd2eb0837c9b4c962c22d2ff8b5441b7b45805887f051d39bf133b583baf6860", argument.getValue().getPassword());

assertEquals(moment, argument.getValue().getCreateAt());

}

}

除了入門的基本使用外,powermock 還有一些需要特別注意的點,可以避免在實際項目中碰到的問題。

@PrepareForTest 中的參數,也就是一個需要被準備的類。這個類不是被 Mock 的類本身。例如上面一個例子中,我們需要 Mock 的是 Instant.now(),這個方法在 UserService 中被使用,我們需要 prepare UserService 而不是 Instant。這是 powermock 在使用中最常見的一個錯誤,就其原因是 靜態方法是類級別的方法,需要在類被加載前準備完畢。

具體的實現是在 PowerMockRunner 中完成的,其中用了很多字節碼級別的技術,可以不用關心具體實現。

上面的例子中,我們不需要驗證 Instant.now 的調用情況。在有些情況下,對靜態方法也需要驗證。

可以使用 PowerMockito 的 verifyStatic 方法,然后直接調用被 verify 的靜態方法即可。

PowerMockito.verifyStatic(Static.class);

Static.thirdStaticMethod(Mockito.anyInt());

需要注意的是 verifyStatic 需要每次驗證都調用,這兩句代碼為成對出現的。

有時候被測試的代碼中可能會直接使用 new 關鍵字創建一個對象,這種情況下就不太好隔離被創建的對象。如果不使用 powermock 甚至這段代碼不能被測試,有兩個途徑來解決。一個是使用使用工廠方法或者依賴注入代替直接使用 new 關鍵字,進行解耦;另一種方式是,使用 powermock 對構造方法進行 mock。

在 powermock 中使用 whenNew 這個方法,可以攔截構造方法的調用,而直接返回其他對象或者異常。構造方法的 mock 是 powermock 中最常用的特性之一。

@RunWith(PowerMockRunner.class)

@PrepareForTest(X.class)

public class XTest {

@Test

public void test() {

whenNew(MyClass.class).withNoArguments().thenThrow(new IOException("error message"));

X x = new X();

x.y(); // y is the method doing "new MyClass()"

..

}

}

和前面的問題類似,在做一些重構時,我們發現類中有一些特別長的私有方法,這些私有方法比較復雜帶來的測試成本很高。

一種方式是,重構這些私有方法到另外一個類中,保持類的私有方法處于較少的狀態。另外一種是通過 powermock 對私有方法進行 mock。使用 powermock mock 私有方法非常簡單,只需要 PowerMockito.when() 方法即可,因為私有方法直接調用會有 java 語法報錯,因此when 方法提供了通過方法名 mock 的重載。

假如 doSendEmockedEmailService 對象中有一個私有方法 doSendEmail,下面的示例代碼演示了 powermock mock 私有方法的例子。

PowerMockito.when(mockedEmailService, "doSendEmail").thenReturn(true));

在使用 Spring 等一些框架時,會使用 @Value 為對象屬性注入值,但是往往這個屬性是私有的。為了測試方便,mockito 提供了響應的工具。

早期的工具使用了 whitebox 類,提供統一的反射操作方法,因為 whitebox 過于開放,在后期的版本中不推薦使用了。

一般我們在項目中用的比較多的是 FieldSetter.setField 這個 API,可以比較優雅的解決這個問題。

@Value

private String template;

FieldSetter.setField(

mockedEmailService,EmailService.class.getDeclaredField("template"),

"test template"

);

一般來說,單元測試會測試 public 方法,如果被測試代碼不合理時,通常的做法是修改被測試代碼。讓代碼具備可測試性非常重要,也能讓代碼變得更加整潔。

如果我們遇到私有方法,但是想要測試有兩個方法。

一種比較好的方法是將私有方法修改為包級別私有,然后將測試代碼放到同一個包下,但是處于 test 目錄下 (src/main/java 和 src/test/java 的關系),這樣測試代碼就能訪問到該方法。

另外的方法是,使用一些輔助工具,例如 mockito 的 Whitebox 類,可以提供對私有方法、屬性的訪問。使用 Whitebox 可以在必要時增強測試能力。

Whitebox.invokeMethod(testObj, "method1", new Long(10L));

如果使用了 SpringTest 還可以使用 ReflectionTestUtils 類來完成:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

實際工作中,被測試代碼不一定能非常容易的被 mock 和測試。讓代碼具有很好的測試性,實際開發過程中非常重要的一件事。

當我們確實需要對私有方法做 mock 和 測試時,可以借助其他方法和工具:使用 powermock 對私有方法、屬性進行 mock 和驗證

使用包級別可訪問的策略對私有方法進行改造,使其可被測試

使用反射工具,例如 Whitebox、ReflectionTestUtils、FieldSetter 訪問私有屬性和方法

總結

以上是生活随笔為你收集整理的powermockito测试私有方法_03 增强测试: 静态、私有方法处理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。