和任何做產(chǎn)品的公司一樣陕贮,軟件公司對其軟件產(chǎn)品的質(zhì)量也是十分看重。
雖然任何公司都會有測試部門昌执,但軟件產(chǎn)品的質(zhì)量不能完全靠QA同學(xué)們的加班來保障萄窜。軟件質(zhì)量從程序開發(fā)階段就應(yīng)該引起重視。
單元測試便是在開發(fā)階段中保證質(zhì)量的一種重要方式循榆。
什么是單元測試
- 一個單元指的是應(yīng)用程序中可測試的最小的一組源代碼析恢。
- 源代碼中包含明確的輸入和輸出的每一個方法被認為是一個可測試的單元。
- 單元測試也就是在完成每個模塊后都進行的測試秧饮。從確保每個模塊沒有問題映挂,從而提高整體的程序質(zhì)量。
單元測試的目的
- 是將應(yīng)用程序的所有源代碼盗尸,隔離成最小的可測試的單元柑船,保證每個單元的正確性。
- 理想情況下泼各,如果每個單元都能保證正確鞍时,就能保證應(yīng)用程序整體相當(dāng)程度的正確性。
- 單元測試也是一種特殊類型的文檔扣蜻,相對于書面的文檔逆巍,測試腳本本身往往就是對被測試代碼的實際的使用代碼,對于幫助開發(fā)人員理解被測試單元的使用是相當(dāng)有幫助的莽使。
Spring Restful Api 服務(wù)單元測試
就以現(xiàn)在流行的Restful
服務(wù)來舉一個單元測試例子锐极,主要包含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>
創(chuàng)建單元測試基類
@ContextConfiguration(classes = {WebConfig.class })
@RunWith(SpringJUnit4ClassRunner.class)
public class BaseCtlCfg extends AbstractContextControllerTests {
}
Controller層單元測試
- 測試類繼承
BaseCtlCfg
灵再,加載、初始化Spring
相關(guān)的配置亿笤。 - 在
@Before
中初始化Mock
檬嘀。 - 從
Spring
容器中獲取Controller
實例,通過反射機制用模擬的service
類替換原有的service
類责嚷。 - 根據(jù)測試的
Case
構(gòu)建模擬數(shù)據(jù)。 - 通過
Mock
的http method
請求方法調(diào)用接口掂铐,斷言返回的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";
//構(gòu)建模擬數(shù)據(jù)。
DraftInfo draftInfo = new DraftInfo();
draftInfo.setIsdel(false);
//模擬方法調(diào)用爆班,當(dāng)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
類的靜態(tài)方法需要引入注解@RunWith(PowerMockRunner.class)
、@PrepareForTest(LifecycleServiceV06.class)
(LifecycleServiceV06.class
為Mock的靜態(tài)方法所在的類)柿菩。 - 通過
@Spy
注解Mock
測試類的真實對象戚嗅。 - 在
@Before
中初始化Mock
,并替換類中要Mock
的屬性枢舶。 - 構(gòu)建模擬數(shù)據(jù)
- 方法如果有返回值懦胞,斷言返回值是否符合期望。
@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";
//構(gòu)建模擬數(shù)據(jù)
HomeworkExtend extend = new HomeworkExtend();
extend.setLcmsHomeworkId(homeworkId);
LifecycleSession session = new LifecycleSession();
//mock類靜態(tài)方法
PowerMockito.mockStatic(LifecycleServiceV06.class);
//模擬方法調(diào)用返回
PowerMockito.when(LifecycleServiceV06.uploadHomework(homeworkId, userId)).thenReturn(session);
//模擬方法調(diào)用返回
when(homeworkExtendRepository.get(homeworkId)).thenReturn(extend);
//void方法do nothing
doNothing().when(homeworkBizService).createHomeworkPackage(homeworkId,session);
homeworkBizService.createHomeworkPackage(homeworkId, userInfo);
}
}