亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中UnitTest和PowerMock的詳細解析

發布時間:2020-07-18 16:14:08 來源:億速云 閱讀:173 作者:小豬 欄目:編程語言

這篇文章主要講解了Java中UnitTest和PowerMock的詳細解析,內容清晰明了,對此有興趣的小伙伴可以學習一下,相信大家閱讀完之后會有幫助。

學習一門計算機語言,我覺得除了學習它的語法外,最重要的就是要學習怎么在這個語言環境下進行單元測試,因為單元測試能幫你提早發現錯誤;同時給你的程序加一道防護網,防止你的修改破壞了原有的功能;單元測試還能指引你寫出更好的代碼,畢竟不能被測試的代碼一定不是好代碼;除此之外,它還能增加你的自信,能勇敢的說出「我的程序沒有bug」。

每個語言都有其常用的單元測試框架,本文主要介紹在 Java 中,我們如何使用 PowerMock,來解決我們在寫單元測試時遇到的問題,從 Mock 這個詞可以看出,這類問題主要是解依賴問題。

在寫單元測試時,為了讓測試工作更簡單、減少外部的不確定性,我們一般都會把被測類和其他依賴類進行隔離,不然你的類依賴得越多,你需要做的準備工作就越復雜,尤其是當它依賴網絡或外部數據庫時,會給測試帶來極大的不確定性,而我們的單測一定要滿足快速、可重復執行的要求,所以隔離或解依賴是必不可少的步驟。

而 Java 中的 PowerMock 庫是一個非常強大的解依賴庫,下面談到的 3 個特性,可以幫你解決絕大多數問題:

1 通過 PowerMock 注入依賴對象

2 利用 PowerMock 來 mock static 函數

3 輸出參數(output parameter)怎么 mock

通過 PowerMock 注入依賴對象

假設你有兩個類,MyService MyDaoMyService 依賴于 MyDao,且它們的定義如下

// MyDao.java
@Mapper
public interface MyDao {
  /**
   * 根據用戶 id 查看他最近一次操作的時間
   */
  Date getLastOperationTime(long userId);
}

// MyService.java
@Service
public class MyService {
	@Autowired
	private MyDao myDao;
	
  public boolean operate(long userId, String operation) {
    Date lastTime = myDao.getLastOperationTime(userId);
    // ...
  }
}

這個服務提供一個 operate 接口,用戶在調用該接口時,會被限制一個操作頻次,所以系統會記錄每個用戶上次操作的時間,通過 MyDao.getLastOperationTime(long userId) 接口獲取,現在我們要對 MyService 類的 operate 做單元測試,該怎么做?

你可能會想到使用 SpringBoot,它能自動幫我們初始化 myDao 對象,但這樣做卻存在一些問題:

1 SpringBoot 的啟動速度很慢,這會延長單元測試的時間

2 因為時間是一個不斷變化的量,也許這一次你構造的時間滿足測試條件,但下一次運行測試時,可能就不滿足了。

由于以上原因,我們一般在做單元測試時,不啟動 SpringBoot 上下文,而是采用 PowerMock 幫我們注入依賴,對于上面的 case,我們的測試用例可以這樣寫:

// MyServiceTest.java
@RunWith(PowerMockRunner.class)
@PrepareForTest({MyService.class, MyDao.class})
public class MyServiceTest {
  @Test
  public void testOperate() throws IllegalAccessException {
    // 構造一個和當前調用時間永遠只差 4 秒的返回值
  	Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.SECOND, -4);
    Date retTime = calendar.getTime();
    
    // spy 是對象的“部分 mock”
    MyService myService = PowerMockito.spy(new MyService());
    MyDao md = PowerMockito.mock(MyDao.class);
    PowerMockito
        .when(md.getLastOperationTime(Mockito.any(long.class)))
        .thenReturn(retTime);
    // 替換 myDao 成員
    MemberModifier.field(MyService.class, "myDao").set(myService, md);
    // 假設最小操作的間隔是 5 秒,否則返回 false
    Assert.assertFalse(myService.operate(1, "test operation"));
  }
}

從上面代碼中,我們首先構造了一個返回時間 retTime,模擬操作間隔的時間為 4 秒,保證了每次運行測試時該條件不會變化;然后我們用 spy 構造一個待測試的 MyService 對象,spymock 的區別是,spy 只會部分模擬對象,即這里只修改掉 myService.myDao 成員,其他的保持不變。

然后我們定義了被 mock 的對象 MyDao md 的調用行為,當 md.getLastOperationTime 函數被調用時,返回我們構造的時間 retTime,此時測試環境就設置完畢了,這樣做之后,你就可以很容易的測試 operate 函數了。

利用 PowerMock 來 mock static 函數

上文所說的使用 PowerMock 進行依賴注入,可以覆蓋測試中絕大多數的解依賴場景,而另一種常見的依賴是 static 函數,例如我們自己寫的一些 CommonUtil 工具類中的函數。

還是使用上面的例子,假設我們要計算當前時間和用戶上一次操作時間之間的間隔,并使用 public static long getTimeInterval(Date lastTime) 實現該功能,如下:

// CommonUtil.java
class CommonUtil {
  public static long getTimeInterval(Date lastTime) {
    long duration = Duration.between(lastTime.toInstant(),
        new Date().toInstant()).getSeconds();
    return duration; 
  }
}

我們的 operator 函數修改如下

// MyService.java
// ...
  public boolean operate(long userId, String operation) {
    Date lastTime = myDao.getLastOperationTime(userId);
    long duration = CommonUtil.getTimeInterval(lastTime);
    if (duration >= 5) {
      System.out.println("user: " + userId + " " + operation);
      return true;
    } else {
      return false;
    }
  }
// ...

這里先從 myDao 獲取上次操作的時間,再調用 CommonUtil.getTimeInterval 計算操作間隔,如果小于 5 秒,就返回 false,否則執行操作,并返回 true。那么我的問題是,如何解掉這里 static 函數的依賴呢?我們直接看測試代碼吧

// MyServiceTest.java
@PrepareForTest({MyService.class, MyDao.class, CommonUtil.class})
public class MyServiceTest {
// ...
  @Test
  public void testOperateWithStatic() throws IllegalAccessException {
    // ...
    PowerMockito.spy(CommonUtil.class);
    PowerMockito.doReturn(5L).when(CommonUtil.class);
    CommonUtil.getTimeInterval(Mockito.anyObject());
    // ...
  }
}

首先在注解 @PrepareForTest 中增加 CommonUtil.class,依然使用 spy 對類 CommonUtil 進行 mock,如果不這么做,這個類中所有靜態函數的行為都會發生變化,這會給你的測試帶來麻煩。spy 下面的兩行代碼你應該放在一起解讀,意為當調用 CommonUtil.getTimeInterval 時,返回 5;這種寫法比較奇怪,但卻是 PowerMock 要求的。至此,你已經掌握了 mock static 函數的技巧。

輸出參數(output parameter)怎么 mock

有些函數會通過修改參數所引用的對象作為輸出,例如下面的這個場景,假設我們的 operation 是一個長時間執行的任務,我們需要不斷輪訓該任務的狀態,更新到內存,并對外提供查詢接口,如下代碼:到內存,并對外提供查詢接口,如下代碼:

// MyTask.java
// ...
  public boolean run() throws InterruptedException {
    while (true) {
      updateStatus(operation);

      if (operation.getStatus().equals("success")) {
        return true;
      } else {
        Thread.sleep(1000);
      }
    }
  }

  public void updateStatus(Operation operation) {
    String status = myDao.getStatus(operation.getOperationId());
    operation.setStatus(status);
  }
// ...

上面的代碼中,run() 是一個輪詢任務,它會不斷更新 operation 的狀態,并在狀態達到 "success" 時停止,可以看到,updateStatus 就是我們所說的函數,雖然它沒有返回值,但它會修改參數所引用的對象,所以這種參數也被稱作輸出參數。

現在我們要測試 run() 函數的行為,看它是否會在 "success" 狀態下退出,那么我們就需要 mock updateStatus 函數,該怎么做?下面是它的測試代碼:

  @Test
  public void testUpdateStatus() throws InterruptedException {
    // 初始化被測對象
    MyTask myTask = PowerMockito.spy(new MyTask());
    myTask.setOperation(new MyTask.Operation());
    // 使用 doAnswer 來 mock updateStatus 函數的行為
    PowerMockito.doAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        Object[] args = invocation.getArguments();
        MyTask.Operation operation = (MyTask.Operation)args[0];
        operation.setStatus("success");
        return null;
      }
    }).when(myTask).updateStatus(Mockito.any(MyTask.Operation.class));

    Assert.assertEquals(true, myTask.run());
  }

上面的代碼中,我們使用 doAnswermock updateStatus 的行為,相當于使用 answer 函數來替換原來的 updateStatus 函數,在這里,我們將 operation 的狀態設置為了 "success",以期待 myTask.run() 函數返回 true。于是,我們又學會了如何 mock 具有輸出參數的函數了。

看完上述內容,是不是對Java中UnitTest和PowerMock的詳細解析有進一步的了解,如果還想學習更多內容,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

石狮市| 屏边| 永寿县| 阿尔山市| 宁城县| 屯门区| 德昌县| 长兴县| 承德市| 凌云县| 澎湖县| 钟祥市| 铜川市| 达拉特旗| 炎陵县| 巫山县| 历史| 广平县| 新河县| 霍林郭勒市| 灵璧县| 中牟县| 临安市| 专栏| 合阳县| 垣曲县| 正定县| 大城县| 普格县| 库伦旗| 个旧市| 都昌县| 苏尼特右旗| 湟源县| 茶陵县| 乐山市| 岳普湖县| 嘉鱼县| 丘北县| 阜新| 托里县|