Mock和Java单元测试中的Mock框架Mockito介绍
什么是Mock?
????在面向對象程序設計中,模擬對象(英語:mock object,也譯作模仿對象)是以可控的方式模擬真實對象行為的假的對象。程序員通常創造模擬對象(mock object)來測試其他對象的行為,很類似汽車設計者使用碰撞測試假人來模擬車輛碰撞中人的動態行為。
為什么要使用Mock?
????在單元測試中,模擬對象可以模擬復雜的、真實的(非模擬)對象的行為, 如果真實的對象無法放入單元測試中,使用模擬對象就很有幫助。
在下面的情形,可能需要使用模擬對象來代替真實對象:
真實對象的行為是不確定的(例如,當前的時間或當前的溫度);
真實對象很難搭建起來;
真實對象的行為很難觸發(例如,網絡錯誤);
真實對象速度很慢(例如,一個完整的數據庫,在測試之前可能需要初始化);
真實的對象是用戶界面,或包括用戶界面在內;
真實的對象使用了回調機制;
真實對象可能還不存在;
真實對象可能包含不能用作測試(而不是為實際工作)的信息和方法。
????例如,一個可能會在特定的時間響鈴的鬧鐘程序可能需要外部世界的當前時間。要測試這一點,測試一直要等到鬧鈴時間才知道鬧鐘程序是否正確地響鈴。如果使用一個模擬對象替代真實的對象,可以變成提供一個鬧鈴時間(不管是否實際時間),這樣就可以隔離地測試鬧鐘程序。
Mockito的簡單使用
????Mockito是GitHub上使用最廣泛的Mock框架,并與JUnit(java單元測試框架)結合使用。Mockito框架可以創建和配置mock對象.使用Mockito簡化了具有外部依賴的類的測試開發!
一般使用Mockito的步驟:
1、模擬任何外部依賴并將這些模擬對象插入測試代碼中
2、執行測試中的代碼
3、驗證代碼是否按照預期執行
????單元測試是每個程序員的一項基本技能,甚至于還出現一種 TDD 的敏捷軟件設計開發方法。在我們劃分好模塊進行詳細設計編碼之前,可能只是粗略的定義了一些接口,在我們進行的前后端分離開發方式實踐中,以及微服務架構的系統設計中,經常會遇到這種情況。
? ? 在我們需要測試的代碼所依賴的服務還未實現,或者說要構建依賴的對象比較困難時,使用Mock的方式進行單元測試是一種比較好的選擇。例如我們在使用 Spring 框架開發和測試 Service 層的代碼時,并不需要等到 Dao 層的相關代碼開發完成才進行單元測試。
? ? 本文主要介紹Java編程領域一個非常好用的Mock框架的應用。
? ? 1、Mockito的引入
? ? Mockito 目前發布的是 2.x 版本( Mockito 3.x 版本目前還在開發中,會考慮 Java 8 的一些新特性)。我們以 Maven 為例(當然根據自己項目的情況也可以使用 Ivy、Gradle、SBT 等等,甚至直接把 jar 包下載下來放到項目中使用),只需要在 Maven 項目的 pom 文件中增加 Mockito 依賴即可。
<dependency>
????<groupId>org.mockito</groupId>
????<artifactId>mockito-core</artifactId>????
????<version>2.23.0</version>
</dependency>
? ? ? ?2、第一段Mock測試代碼。
? ? ? ?考慮一個簡單的用戶注冊功能,我們需要先判斷注冊用戶的用戶名是否被其他用戶注冊過。如果已被注冊過,則注冊失敗,如果未被注冊過,則保存注冊信息,注冊成功。下面是代碼設計(示例代碼使用 spring 框架,并使用了 lombok 以減少 POJO 類的 getter,setter 定義):
@Data
public class User {
? ? private String idUser;? ? ? // 用戶ID
? ? private String username;? ? // 用戶名
? ? private String password;? ? // 用戶密碼
}
public interface UserService {
? ? /**
? ? * 新用戶注冊。注冊成功返回true,注冊失敗返回false
? ? * @param user 新注冊用戶
? ? * @return
? ? */
? ? boolean regist(User user);
}
@Service
public class UserServiceImpl implements UserService {
? ? @Autowired
? ? private UserDao userDao;
? ? @Override
? ? public boolean regist(User user) {
? ? ? ? User existUser = userDao.queryByUsername(user.getUsername());
? ? ? ? if (existUser == null) {
? ? ? ? ? ? userDao.insertUser(user);
? ? ? ? ? ? return true;
? ? ? ? }
? ? ? ? return false;
? ? }
}
public interface UserDao {
? ? /**
? ? * 根據用戶名查詢用戶
? ? * @param username 用戶名
? ? * @return
? ? */
? ? User queryByUsername(String username);
? ? /**
? ? * 持久化新用戶
? ? * @param user 新用戶
? ? * @return
? ? */
? ? void insertUser(User user);
}
????這個時候 UserDao 的實現類還沒有開發,我們要測試 UserService 的 regist 方法時就可以使用 Mock 了。下面就是第一段使用 JUnit 結合 Mockito 編寫的單元測試代碼。
@RunWith(MockitoJUnitRunner.class)
public class UserServiceImplTest {
? ? String existUsername? ? = "spiderman";
? ? String notExistUsername = "ironman";
? ? @Mock
? ? private UserDao? ? ? ? userDao;
? ? @InjectMocks
? ? private UserServiceImpl userService;
? ? @Before
? ? public void setUp() throws Exception {
? ? ? ? // 效果同@RunWith(MockitoJUnitRunner.class)
? ? ? ? // MockitoAnnotations.initMocks(this);
? ? ? ? User existUser = new User();
? ? ? ? existUser.setUsername(existUsername);
? ? ? ? existUser.setPassword("aaaaa");
? ? ? ? // 當調用userDao.queryByUsername入參為"spiderman"時會返回existUser對象,表示該用戶已存在
? ? ? ? Mockito.when(userDao.queryByUsername(existUsername)).thenReturn(existUser);
? ? ? ? // 當調用userDao.queryByUsername入參為"ironman"時會返回null值,表示不存在該用戶
? ? ? ? Mockito.when(userDao.queryByUsername(notExistUsername)).thenReturn(null);
? ? }
? ? @Test
? ? public void testExists() throws Exception {
? ? ? ? User testUser = new User();
? ? ? ? testUser.setUsername(existUsername);
? ? ? ? Assert.assertFalse(userService.regist(testUser));
? ? }
? ? @Test
? ? public void testNotExists() throws Exception {
? ? ? ? User testUser = new User();
? ? ? ? testUser.setUsername(notExistUsername);
? ? ? ? Assert.assertTrue(userService.regist(testUser));
? ? }
}
以上單元測試代碼除了幾行帶有 Mock 字樣的代碼,其他內容和我們之前寫的單元測試沒有區別。從面的代碼我們可以看到 userService 依賴了 userDao ,但是我們的代碼中并沒有實例化這兩個對象,并且源代碼中也沒有 UserDao 的具體實現,但是我們的測試代碼卻可以像是已經實例化了這兩個對象一樣進行操作。下面我們就來看看這幾行新增代碼的作用。
? ? ? ?3、Mock注解介紹。
? ? ? ?@RunWith(MockitoJUnitRunner.class) 該注解會在test方法執行之前初始化使用 @Spy & @Mock & @InjectMocks 注解的對象;該注解還會自動驗證我們單元測試用Mockito框架的使用情況。如果我們在調用Mockito的靜態方法when之后繼續鏈式調用相應的 stub 方法(如上面示例代碼中去掉thenReturn方法調用),單元測試代碼可以編譯通過,是運行時會報錯。
我們在setup方法中第一行使用MockitoAnnotations.initMocks(this)可以達到該注解同樣的效果。
? ? ? ?當然我們也可以使用 Mockito.mock 方法手動創建 mock 對象,但是并不推薦這樣做。
? ? ? ?@Mock 該注解表示會創建一個mock對象。我們在該 mock 對象上的方法調用并不會實際調用具體的實現代碼(也可能其實本來就還沒有實現)
? ? ? ?@Spy 該注解上面示例代碼并未使用,功能與 @Mock 類似,也是創建一個mock對象,區別在于調用 @Spy 對應的mock對象上的方法時,會實際調用事實實現好的代碼(如果已有實現方法的前提下),但是不會影響我們when-then語句中的定義。但是使用該注解還是會為我們省去對象創建的過程。例如上面示例中如果我們實現了 UserDao 接口:
@Repository
public class UserDaoImpl implements UserDao {
? ? @Override
? ? public User queryByUsername(String username) {
? ? ? ? System.out.println("call UserDaoImpl.queryByUsername");
? ? ? ? return null;
? ? }
? ? @Override
? ? public void insertUser(User user) {
? ? }
}
然后更換單元測試類型的 Mock定義:
@Mock
private UserDao userDao;
更換為
@Spy
private UserDaoImpl userDao;
執行單元測試后我們可以在控制臺看到 UserDaoImpl 實現方法中的打印語句:
如果我們再把 @Spy 注解切換回? @Mock 注解,可以發現控制臺不會打印 UserDaoImpl 實現方法中的打印語句。
@Spy
privateUserDaoImpl userDao;
更換為
@Mock
privateUserDaoImpl userDao;
@InjectMocks 該注解表示會創建一個測試類的實例,并注入依賴的mock對象(@Mock 注解或 @Spy 注解)。
? ? ? ?4、Mock的應用介紹
? ? ? ?除了Mock的注解,下面我們再來看看使用Mock的表達式。上面示例代碼我們展示了when-then表達式的使用。我們使用該語句定義對象方法調用的一些預先約定。
? ? ? ?when方法定義方法場景,指定了具體的mock對象,指定了mock對象的某個具體調用方法,指定了該方法的調用參數值(如果不關心具體的參數值內容,可以用Any代替)。
? ? ? ?then方法定義了我們約定的方法調用之后需要具體執行的操作,比如返回一個值或者拋出一個異常。
????另外我們還可以使用Mock做一些驗證。例如我們在Assert的斷言方法后面增加一些判斷,如果測試用例是注冊不存在的用戶,我們的業務邏輯中會調用userDao的insertUser方法,這個時候我們可以增加一行Mockito.verify(userDao).insertUser(testUser)。如果我們的 UserServiceImpl 實現中去掉userDao.insertUser(user)調用,測試不會通過,也就提示我們說新注冊的用戶沒有持久化操作需要修復 UserServiceImpl 里的實現邏輯。
? ? ? ?Mockito在 stackoverflow 是程序員投票最廣泛使用評價最高的一個 Java 編程 Mock 框架。使用該框架編寫的單元測試代碼美觀、清潔、易于理解,并且功能強大。本文只是簡單介紹 Mockito 的一些簡單的基礎知識,一些復雜的高級的功能另文介紹。
作者:金融測試民工
鏈接:https://www.jianshu.com/p/b8a52260dc22
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
總結
以上是生活随笔為你收集整理的Mock和Java单元测试中的Mock框架Mockito介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小人舞视频(小人舞歌曲串烧下载mp3)
- 下一篇: 浅谈Java中equals()和==的区