和任何做產品的公司一樣,軟件公司對其軟件產品的質量也是十分看重。
雖然任何公司都會有測試部門,但軟件產品的質量不能完全靠QA同學們的加班來保障。軟件質量從程序開發階段就應該引起重視。
單元測試便是在開發階段中保證質量的一種重要方式。
什么是單元測試
- 一個單元指的是應用程序中可測試的最小的一組源代碼。
- 源代碼中包含明確的輸入和輸出的每一個方法被認為是一個可測試的單元。
- 單元測試也就是在完成每個模塊后都進行的測試。從確保每個模塊沒有問題,從而提高整體的程序質量。
單元測試的目的
- 是將應用程序的所有源代碼,隔離成最小的可測試的單元,保證每個單元的正確性。
- 理想情況下,如果每個單元都能保證正確,就能保證應用程序整體相當程度的正確性。
- 單元測試也是一種特殊類型的文檔,相對于書面的文檔,測試腳本本身往往就是對被測試代碼的實際的使用代碼,對于幫助開發人員理解被測試單元的使用是相當有幫助的。
Spring Restful Api 服務單元測試
就以現在流行的Restful
服務來舉一個單元測試例子,主要包含Controller
與Service
層。
測試工具,使用junit
+mockito
+powermock
測試框架
- 項目新增測試框架的
pom
依賴:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.8.4</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>1.5.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<version>1.5.6</version>
<scope>test</scope>
</dependency>
創建單元測試基類
@ContextConfiguration(classes = {WebConfig.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class BaseCtlCfg extends AbstractContextControllerTests {
}
Controller層單元測試
- 測試類繼承
BaseCtlCfg
,加載、初始化Spring
相關的配置。 - 在
@Before
中初始化Mock
。 - 從
Spring
容器中獲取Controller
實例,通過反射機制用模擬的service
類替換原有的service
類。 - 根據測試的
Case
構建模擬數據。 - 通過
Mock
的http method
請求方法調用接口,斷言返回的http status
、body
是否符合期望。
public class DraftControllerTest extends BaseCtlCfg{
@Mock
private DraftService draftService;
@Autowired
protected WebApplicationContext wac;
@Before
public void before(){
//初始化Mock
MockitoAnnotations.initMocks(this);
//獲取上下文容器中的controller
DraftController draftController = (DraftController) wac.getBean("draftController");
//用mock的Service替換原有的service
ReflectionTestUtils.setField(draftController, "draftService", draftService);
}
@Test
public void publishTest() throws Exception {
DraftPublishReq request = new DraftPublishReq();
request.setPublishMode(1);
request.setClassIds("495331469627");
request.setPublishTime(System.currentTimeMillis());
request.setPublishTime(System.currentTimeMillis() + 2*24*60*60*1000);
String json = JSON.toJSONString(request);
String uri = "/v0.3/drafts/00691d21-71f4-4115-89d0-a7168c77ef8b/actions/publish";
//構建模擬數據。
DraftInfo draftInfo = new DraftInfo();
draftInfo.setIsdel(false);
//模擬方法調用,當draftId=00691d21-71f4-4115-89d0-a7168c77ef8b時,return draftInfo對象
when(draftService.get("00691d21-71f4-4115-89d0-a7168c77ef8b")).thenReturn(draftInfo);
String resStr = mockMvc.perform(post(uri, "json")
.characterEncoding("UTF-8")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.valueOf(MockUtil.APPLICATION_JSON))
.content(json.getBytes()))
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString();
JSONArray jsonArray = JSON.parseArray(resStr);
Assert.assertEquals(1, jsonArray.size());
}
}
Service層測試
-
Mock
類的靜態方法需要引入注解@RunWith(PowerMockRunner.class)
、@PrepareForTest(LifecycleServiceV06.class)
(LifecycleServiceV06.class
為Mock的靜態方法所在的類)。 - 通過
@Spy
注解Mock
測試類的真實對象。 - 在
@Before
中初始化Mock
,并替換類中要Mock
的屬性。 - 構建模擬數據
- 方法如果有返回值,斷言返回值是否符合期望。
@RunWith(PowerMockRunner.class)
@PrepareForTest(LifecycleServiceV06.class)
public class HomeworkBizServiceTest{
@Spy //mock真實對象
private HomeworkBizService homeworkBizService = new HomeworkBizService();
@Mock
private HomeworkExtendRepository homeworkExtendRepository;
@Before
public void before() {
//初始化Mock
MockitoAnnotations.initMocks(this);
//用mock的repository替換原有的repository
ReflectionTestUtils.setField(homeworkBizService, "homeworkExtendRepository", homeworkExtendRepository);
}
@Test
public void createHomeworkPackage() throws Exception {
Long userId = 2107222934l;
UserInfo userInfo = new UserInfo();
userInfo.setUserId(String.valueOf(userId));
String homeworkId="8b310d5d-ebd2-4798-bff3-2863fe1e1cd7";
//構建模擬數據
HomeworkExtend extend = new HomeworkExtend();
extend.setLcmsHomeworkId(homeworkId);
LifecycleSession session = new LifecycleSession();
//mock類靜態方法
PowerMockito.mockStatic(LifecycleServiceV06.class);
//模擬方法調用返回
PowerMockito.when(LifecycleServiceV06.uploadHomework(homeworkId, userId)).thenReturn(session);
//模擬方法調用返回
when(homeworkExtendRepository.get(homeworkId)).thenReturn(extend);
//void方法do nothing
doNothing().when(homeworkBizService).createHomeworkPackage(homeworkId,session);
homeworkBizService.createHomeworkPackage(homeworkId, userInfo);
}
}