本文首發於泊浮目的專欄: https://segmentfault.com/blog...
筆者工做2年有餘,剛開始實習的時候是不知道自動化測試這種神器的,在剛開始工做的時候每每苦於救火滅火再救火,搞的心力憔悴,一度懷疑猿生。實踐自動化測試後感受生產力慢慢的解放了,那個時候搞的仍是偏單機應用,測試的Cover也是止步在單機應用上。在接觸到了ZStack之後,因爲其產品化的特性,對軟件質量要求偏高,然做爲一個典型的分佈式系統,測試的覆蓋率倒是較高的。在這篇文章,筆者想談談對自動化測試的一些想法。java
自動化測試的收益點很明顯,幾乎衆所周知:git
既然收益這麼高,爲何現實中自動化測試實施起來就像勞動人民愛勞動這句話同樣這麼不現實呢?大概有這幾點:github
ZStack的自動化測試是基於Junit使用Grovvy
編寫的集成測試,在運行時會把依賴的Bean按需加載進來並啓動一個JVM進程,同時也會啓動一個基於Jetty的HTTPServer用於Mock Agent的行爲。web
不少人覺得Junit是用於作單元測試的。其實並不是如此,官網上的介紹是:JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
package org.zstack.test.integration.kvm.vm import org.springframework.http.HttpEntity import org.zstack.header.vm.VmCreationStrategy import org.zstack.header.vm.VmInstanceState import org.zstack.header.vm.VmInstanceVO import org.zstack.kvm.KVMAgentCommands import org.zstack.kvm.KVMConstant import org.zstack.sdk.CreateVmInstanceAction import org.zstack.sdk.DiskOfferingInventory import org.zstack.sdk.ImageInventory import org.zstack.sdk.InstanceOfferingInventory import org.zstack.sdk.L3NetworkInventory import org.zstack.sdk.VmInstanceInventory import org.zstack.test.integration.kvm.Env import org.zstack.test.integration.kvm.KvmTest import org.zstack.testlib.EnvSpec import org.zstack.testlib.SubCase import org.zstack.testlib.VmSpec import org.zstack.utils.gson.JSONObjectUtil /** * Created by xing5 on 2017/2/22. */ class OneVmBasicLifeCycleCase extends SubCase { EnvSpec env def DOC = """ test a VM's start/stop/reboot/destroy/recover operations """ @Override void setup() { useSpring(KvmTest.springSpec) } @Override void environment() { env = Env.oneVmBasicEnv() } @Override void test() { env.create { testStopVm() testStartVm() testRebootVm() testDestroyVm() testRecoverVm() testDeleteCreatedVm() } } void testRecoverVm() { VmSpec spec = env.specByName("vm") VmInstanceInventory inv = recoverVmInstance { uuid = spec.inventory.uuid } assert inv.state == VmInstanceState.Stopped.toString() // confirm the vm can start after being recovered testStartVm() } void testDestroyVm() { VmSpec spec = env.specByName("vm") KVMAgentCommands.DestroyVmCmd cmd = null env.afterSimulator(KVMConstant.KVM_DESTROY_VM_PATH) { rsp, HttpEntity<String> e -> cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.DestroyVmCmd.class) return rsp } destroyVmInstance { uuid = spec.inventory.uuid } assert cmd != null assert cmd.uuid == spec.inventory.uuid VmInstanceVO vmvo = dbFindByUuid(cmd.uuid, VmInstanceVO.class) assert vmvo.state == VmInstanceState.Destroyed } void testRebootVm() { // reboot = stop + start VmSpec spec = env.specByName("vm") KVMAgentCommands.StartVmCmd startCmd = null KVMAgentCommands.StopVmCmd stopCmd = null env.afterSimulator(KVMConstant.KVM_STOP_VM_PATH) { rsp, HttpEntity<String> e -> stopCmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StopVmCmd.class) return rsp } env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity<String> e -> startCmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class) return rsp } VmInstanceInventory inv = rebootVmInstance { uuid = spec.inventory.uuid } assert startCmd != null assert startCmd.vmInstanceUuid == spec.inventory.uuid assert stopCmd != null assert stopCmd.uuid == spec.inventory.uuid assert inv.state == VmInstanceState.Running.toString() } void testStartVm() { VmSpec spec = env.specByName("vm") KVMAgentCommands.StartVmCmd cmd = null env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity<String> e -> cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class) return rsp } VmInstanceInventory inv = startVmInstance { uuid = spec.inventory.uuid } assert cmd != null assert cmd.vmInstanceUuid == spec.inventory.uuid assert inv.state == VmInstanceState.Running.toString() VmInstanceVO vmvo = dbFindByUuid(cmd.vmInstanceUuid, VmInstanceVO.class) assert vmvo.state == VmInstanceState.Running assert cmd.vmInternalId == vmvo.internalId assert cmd.vmName == vmvo.name assert cmd.memory == vmvo.memorySize assert cmd.cpuNum == vmvo.cpuNum //TODO: test socketNum, cpuOnSocket assert cmd.rootVolume.installPath == vmvo.rootVolume.installPath assert cmd.useVirtio vmvo.vmNics.each { nic -> KVMAgentCommands.NicTO to = cmd.nics.find { nic.mac == it.mac } assert to != null: "unable to find the nic[mac:${nic.mac}]" assert to.deviceId == nic.deviceId assert to.useVirtio assert to.nicInternalName == nic.internalName } } void testStopVm() { VmSpec spec = env.specByName("vm") KVMAgentCommands.StopVmCmd cmd = null env.afterSimulator(KVMConstant.KVM_STOP_VM_PATH) { rsp, HttpEntity<String> e -> cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StopVmCmd.class) return rsp } VmInstanceInventory inv = stopVmInstance { uuid = spec.inventory.uuid } assert inv.state == VmInstanceState.Stopped.toString() assert cmd != null assert cmd.uuid == spec.inventory.uuid def vmvo = dbFindByUuid(cmd.uuid, VmInstanceVO.class) assert vmvo.state == VmInstanceState.Stopped } void testDeleteCreatedVm() { VmSpec spec = env.specByName("vm") DiskOfferingInventory diskOfferingInventory = env.inventoryByName("diskOffering") InstanceOfferingInventory instanceOfferingInventory = env.inventoryByName("instanceOffering") ImageInventory imageInventory = env.inventoryByName("image1") L3NetworkInventory l3NetworkInventory = env.inventoryByName("l3") CreateVmInstanceAction action = new CreateVmInstanceAction() action.name = "JustCreatedVm" action.rootDiskOfferingUuid = diskOfferingInventory.uuid action.instanceOfferingUuid = instanceOfferingInventory.uuid action.imageUuid = imageInventory.uuid action.l3NetworkUuids = [l3NetworkInventory.uuid] action.strategy = VmCreationStrategy.JustCreate.toString() action.sessionId = adminSession() CreateVmInstanceAction.Result result = action.call() destroyVmInstance { uuid = result.value.inventory.uuid } VmInstanceVO vo = dbFindByUuid(result.value.inventory.uuid, VmInstanceVO.class) assert vo == null } @Override void clean() { env.delete() } }
咱們先從跳轉到extends的SubCase
中:spring
package org.zstack.testlib /** * Created by xing5 on 2017/2/22. */ abstract class SubCase extends Test implements Case { final void run() { try { environment() test() } catch (Throwable t) { logger.warn("a sub case [${this.class}] fails, ${t.message}", t) collectErrorLog() throw t } finally { logger.info("start cleanup for case ${this.class}") try{ clean() }catch (Throwable t){ collectErrorLog() throw t } } } @Override protected void runSubCases() { throw new Exception("runSubCases() cannot be called in a SubCase") } }
從簽名中能夠看到,其繼承於Test
,並實現了Case
接口中的方法,咱們看一下 Case
:數據庫
package org.zstack.testlib /** * Created by xing5 on 2017/3/3. */ interface Case { void environment() void test() void run() void clean() }
這裏定義一個SubCase
的基本行爲:json
在Test
中,咱們也能夠看到定義裏幾個關鍵抽象函數,用於定義一個Case的行爲:segmentfault
abstract void setup() abstract void environment() abstract void test()
因此一個Case
必須實現Test
中的接口以及Case
中的clean方法。session
通常在setup
中,會將依賴的Bean按需加載進來。這在前面提到過;而environment
則會構建出一個環境。Grovvy對DSL支持較好,因此整個環境的構建代碼可讀性極強,本質上每一個DSL都對應了一個Spec,而Sepc對應了一個ZStack的SDK建立調用——即XXXAction。而XXXAction則經過HTTP調用ZStack的API接口。架構
平時在測試中你們可能會爲了Build一個環境直接對數據庫進行操做。例如:
xxxRepo.save(new Object());
但在ZStack中並非一個很好的方案——一個Iaas中的資源依賴及狀態變更的關係是錯綜複雜的,所以調用外部的API來建立資源是一個明智的選擇。同時也能夠測試SDK和API的行爲是不是期待的。
在clean中也是如此。會調用ZStack自己的Cascade邏輯進行資源清理。打開EnvSpec.Grovvy
能夠看到
static List deletionMethods = [ [CreateZoneAction.metaClass, CreateZoneAction.Result.metaClass, DeleteZoneAction.class], [AddCephBackupStorageAction.metaClass, AddCephBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class], [AddCephPrimaryStorageAction.metaClass, AddCephPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class], [AddCephPrimaryStoragePoolAction.metaClass, AddCephPrimaryStoragePoolAction.Result.metaClass, DeleteCephPrimaryStoragePoolAction.class], [CreateEipAction.metaClass, CreateEipAction.Result.metaClass, DeleteEipAction.class], [CreateClusterAction.metaClass, CreateClusterAction.Result.metaClass, DeleteClusterAction.class], [CreateDiskOfferingAction.metaClass, CreateDiskOfferingAction.Result.metaClass, DeleteDiskOfferingAction.class], [CreateInstanceOfferingAction.metaClass, CreateInstanceOfferingAction.Result.metaClass, DeleteInstanceOfferingAction.class], [CreateAccountAction.metaClass, CreateAccountAction.Result.metaClass, DeleteAccountAction.class], [CreatePolicyAction.metaClass, CreatePolicyAction.Result.metaClass, DeletePolicyAction.class], [CreateUserGroupAction.metaClass, CreateUserGroupAction.Result.metaClass, DeleteUserGroupAction.class], [CreateUserAction.metaClass, CreateUserAction.Result.metaClass, DeleteUserAction.class], [AddImageAction.metaClass, AddImageAction.Result.metaClass, DeleteImageAction.class], [CreateDataVolumeTemplateFromVolumeAction.metaClass, CreateDataVolumeTemplateFromVolumeAction.Result.metaClass, DeleteImageAction.class], [CreateRootVolumeTemplateFromRootVolumeAction.metaClass, CreateRootVolumeTemplateFromRootVolumeAction.Result.metaClass, DeleteImageAction.class], [CreateL2NoVlanNetworkAction.metaClass, CreateL2NoVlanNetworkAction.Result.metaClass, DeleteL2NetworkAction.class], [CreateL2VlanNetworkAction.metaClass, CreateL2VlanNetworkAction.Result.metaClass, DeleteL2NetworkAction.class], [AddIpRangeByNetworkCidrAction.metaClass, AddIpRangeByNetworkCidrAction.Result.metaClass, DeleteIpRangeAction.class], [CreateL3NetworkAction.metaClass, CreateL3NetworkAction.Result.metaClass, DeleteL3NetworkAction.class], [CreateSchedulerJobAction.metaClass, CreateSchedulerJobAction.Result.metaClass, DeleteSchedulerJobAction.class], [CreateSchedulerTriggerAction.metaClass, CreateSchedulerTriggerAction.Result.metaClass, DeleteSchedulerTriggerAction.class], [CreateVmInstanceAction.metaClass, CreateVmInstanceAction.Result.metaClass, DestroyVmInstanceAction.class], [CreateDataVolumeFromVolumeSnapshotAction.metaClass, CreateDataVolumeFromVolumeSnapshotAction.Result.metaClass, DeleteDataVolumeAction.class], [CreateDataVolumeFromVolumeTemplateAction.metaClass, CreateDataVolumeFromVolumeTemplateAction.Result.metaClass, DeleteDataVolumeAction.class], [CreateDataVolumeAction.metaClass, CreateDataVolumeAction.Result.metaClass, DeleteDataVolumeAction.class], [CreateVolumeSnapshotAction.metaClass, CreateVolumeSnapshotAction.Result.metaClass, DeleteVolumeSnapshotAction.class], [AddKVMHostAction.metaClass, AddKVMHostAction.Result.metaClass, DeleteHostAction.class], [CreateLoadBalancerAction.metaClass, CreateLoadBalancerAction.Result.metaClass, DeleteLoadBalancerAction.class], [AddLocalPrimaryStorageAction.metaClass, AddLocalPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class], [AddImageStoreBackupStorageAction.metaClass, AddImageStoreBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class], [AddNfsPrimaryStorageAction.metaClass, AddNfsPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class], [CreatePortForwardingRuleAction.metaClass, CreatePortForwardingRuleAction.Result.metaClass, DeletePortForwardingRuleAction.class], [CreateSecurityGroupAction.metaClass, CreateSecurityGroupAction.Result.metaClass, DeleteSecurityGroupAction.class], [AddSftpBackupStorageAction.metaClass, AddSftpBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class], [AddSharedMountPointPrimaryStorageAction.metaClass, AddSharedMountPointPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class], [CreateVipAction.metaClass, CreateVipAction.Result.metaClass, DeleteVipAction.class], [CreateVirtualRouterOfferingAction.metaClass, CreateVirtualRouterOfferingAction.Result.metaClass, DeleteInstanceOfferingAction.class], [CreateWebhookAction.metaClass, CreateWebhookAction.Result.metaClass, DeleteWebhookAction.class], [CreateBaremetalPxeServerAction.metaClass, CreateBaremetalPxeServerAction.Result.metaClass, DeleteBaremetalPxeServerAction.class], [CreateBaremetalChassisAction.metaClass, CreateBaremetalChassisAction.Result.metaClass, DeleteBaremetalChassisAction.class], [CreateBaremetalHostCfgAction.metaClass, CreateBaremetalHostCfgAction.Result.metaClass, DeleteBaremetalHostCfgAction.class], [CreateMonitorTriggerAction.metaClass, CreateMonitorTriggerAction.Result.metaClass, DeleteMonitorTriggerAction.class], [CreateEmailMonitorTriggerActionAction.metaClass, CreateEmailMonitorTriggerActionAction.Result.metaClass, DeleteMonitorTriggerActionAction.class], [CreateEmailMediaAction.metaClass, CreateEmailMediaAction.Result.metaClass, DeleteMediaAction.class], [AddLdapServerAction.metaClass, AddLdapServerAction.Result.metaClass, DeleteLdapServerAction.class], [SubmitLongJobAction.metaClass, SubmitLongJobAction.Result.metaClass, DeleteLongJobAction.class], ]
設置了對應的createAction和deleteAction,用於清理環境時調用。這樣同時也對Cascade邏輯進行了Cover。
若是看過ZStack的Case
,能夠看到不少相似的方法:
這幾個方法用來hook Message和HTTP Request。因爲在ZStack中各個組件的通訊都由Message來完成,對於Agent的請求則是統一經過HTTP來完成。這樣在TestCase就能夠任意模擬任何組件及agent的狀態,讓Case有極強的實用性——也保證了ManagentMent Node的邏輯健壯。
ZStack的SDK本質上是包裝了一層HTTP Path,利用通用的協議便於開發者進行開發或測試。而在傳統的Java WEB應用中,通常會經過MockMvc進行測試。其本質也是經過調用每一個API的Path傳參來進行測試。接下來來看一個demo:
import com.camile.base.Utils.JsonUtils; import com.camile.base.common.CommonResponse; import com.camile.base.common.error.ResponseCode; import com.camile.base.common.utils.MD5Util; import com.camile.base.data.dao.UserRepository; import com.camile.base.data.dto.user.*; import com.camile.base.data.entity.UserEntity; import com.camile.base.data.vo.UserVO; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; import javax.servlet.http.HttpSession; import java.util.Map; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Created by Camile * 1.用戶註冊 * 2.用戶登陸,測試本身是否處於登陸狀態,並執行更新信息、修改密碼操做 * 3.用戶登出,更新信息、在線修改密碼,應所有失敗。 * 4.用戶用新信息登陸,成功 * 5.用戶登出,測試本身是否處於登陸狀態,走忘記密碼流程 * 6.修改後再次登陸,成功 */ @Slf4j @Transactional @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class UserBasicTests { @Autowired private UserRepository userRepository; @Autowired private WebApplicationContext context; private String uuid; private HttpSession session; private MockMvc mvc; private ObjectMapper mapper; private final String email = "487643862@qq.com"; private String password = "newPassword"; private final String question = "are you ok ?"; private final String answer = "im fine"; private final String name = "camile"; private final String phone = "13043769014"; private String updateName = "camile1"; private String updateEmail = "587643862@qq.com"; private String updatePhone = "13834671096"; @Before public void setUp() throws Exception { mvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mapper = new ObjectMapper(); } @Test public void test() throws Exception { testRegisterSuccess(); testIsLoginFailure(); testLoginSuccess(); testIsLoginSuccess(); testUpdateInformationSuccess(); testOnlineRestPwdSuccess(); testLoginOutSuccess(); testUpdateInformationFailure(); testOnlineRestPwdFailure(); testloginWithOldPwdFailure(); testLoginWithNewInfoSuccess(); testLoginOutSuccess(); testForgetPwdAndResetSuccess(); testLoginWithNewInfoSuccess(); } private void testRegisterSuccess() throws Exception { UserAllPropertyDTO dto = new UserAllPropertyDTO(); dto.setEmail(email); dto.setPassword(password); dto.setQuestion(question); dto.setAnswer(answer); dto.setName(name); dto.setPhone(phone); String registerJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/register.do") .contentType(MediaType.APPLICATION_JSON) .content(registerJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode()); UserVO vo = JsonUtils.jsonToObject((Map) response.getData(), UserVO.class); Assert.assertNotNull(userRepository.findByUuid(vo.getUuid())); uuid = vo.getUuid(); session = result.getRequest().getSession(); } private void testIsLoginFailure() throws Exception { // never login MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/isLogin.do?uuid=%s", uuid)) .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getCode(), ResponseCode.NeedLogin.getCode()); session = result.getRequest().getSession(); } private void testLoginSuccess() throws Exception { UserLoginDTO dto = new UserLoginDTO(name, password); String loginJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(loginJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode()); session = result.getRequest().getSession(); } private void testIsLoginSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/isLogin.do?uuid=%s", uuid)) .session((MockHttpSession) session) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode()); session = result.getRequest().getSession(); } private void testUpdateInformationSuccess() throws Exception { UserDTO dto = new UserDTO(); dto.setUuid(uuid); dto.setName(updateName); dto.setEmail(updateEmail); dto.setPhone(updatePhone); String updateJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.put("/user/information.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(updateJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode()); UserEntity entity = userRepository.findByUuid(uuid); UserVO vo = JsonUtils.jsonToObject((Map) response.getData(), UserVO.class); Assert.assertNotNull(entity); Assert.assertEquals(vo.getName(), entity.getName()); Assert.assertEquals(vo.getPhone(), entity.getPhone()); Assert.assertEquals(vo.getEmail(), entity.getEmail()); Assert.assertEquals(vo.getEmail(), updateEmail); Assert.assertEquals(vo.getPhone(), updatePhone); Assert.assertEquals(vo.getName(), updateName); session = result.getRequest().getSession(); } private void testOnlineRestPwdSuccess() throws Exception { UserResetPwdDTO dto = new UserResetPwdDTO(); dto.setUuid(uuid); dto.setOldPassword(password); dto.setNewPassword("12345678"); password = "12345678"; String resetPwdJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/onlineResetPwd.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(resetPwdJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), response.getCode(), ResponseCode.Success.getCode()); session = result.getRequest().getSession(); UserEntity userEntity = userRepository.findByUuid(uuid); Assert.assertEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8(password)); } private void testLoginOutSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.post(String.format("/user/loginOut.do?uuid=%s", uuid)) .session((MockHttpSession) session) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode()); session = result.getRequest().getSession(); } private void testUpdateInformationFailure() throws Exception { String updateName = "camile2"; String updateEmail = "687643862@qq.com"; String updatePhone = "14834671096"; UserDTO dto = new UserDTO(); dto.setUuid(uuid); dto.setName(updateName); dto.setEmail(updateEmail); dto.setPhone(updatePhone); String updateJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.put("/user/information.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(updateJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), ResponseCode.Failure.getCode(), response.getCode()); session = result.getRequest().getSession(); } private void testOnlineRestPwdFailure() throws Exception { UserResetPwdDTO dto = new UserResetPwdDTO(); dto.setUuid(uuid); dto.setOldPassword(password); dto.setNewPassword("123456789"); String resetPwdJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/onlineResetPwd.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(resetPwdJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), response.getCode(), ResponseCode.Failure.getCode()); session = result.getRequest().getSession(); UserEntity userEntity = userRepository.findByUuid(uuid); Assert.assertNotEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8("123456789")); } private void testloginWithOldPwdFailure() throws Exception { UserLoginDTO dto = new UserLoginDTO(name, "newPassword"); String loginJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(loginJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getCode(), ResponseCode.UserInfoError.getCode()); session = result.getRequest().getSession(); } private void testLoginWithNewInfoSuccess() throws Exception { UserLoginDTO dto = new UserLoginDTO(updateName, password); String loginJson = JsonUtils.ObjectToJson(dto); MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(loginJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode()); session = result.getRequest().getSession(); } private void testForgetPwdAndResetSuccess() throws Exception { MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/forget/question?name=%s", updateName)) .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); String content = result.getResponse().getContentAsString(); CommonResponse response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode()); session = result.getRequest().getSession(); String question = (String) response.getData(); Assert.assertEquals(question, this.question); UserQuestionDTO dto = new UserQuestionDTO(); dto.setName(updateName); dto.setQuestion(question); dto.setAnswer(answer); String questionJson = JsonUtils.ObjectToJson(dto); result = mvc.perform(MockMvcRequestBuilders.post("/user/forget/checkAnswer.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(questionJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode()); session = result.getRequest().getSession(); String token = (String) response.getData(); UserForgetResetPwdDTO userForgetResetPwdDTO = new UserForgetResetPwdDTO(); userForgetResetPwdDTO.setForgetToken(token); userForgetResetPwdDTO.setName(updateName); userForgetResetPwdDTO.setNewPassword("superpwd!"); password = "superpwd!"; String resetPwdDTO = JsonUtils.ObjectToJson(userForgetResetPwdDTO); result = mvc.perform(MockMvcRequestBuilders.post("/user/forget/resetPassword.do") .session((MockHttpSession) session) .contentType(MediaType.APPLICATION_JSON) .content(resetPwdDTO) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); content = result.getResponse().getContentAsString(); response = mapper.readValue(content, CommonResponse.class); Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode()); session = result.getRequest().getSession(); UserEntity userEntity = userRepository.findByUuid(uuid); Assert.assertEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8(password)); } }
咱們能夠看到MockMvc
的鏈式調用讓代碼可讀性變得極強:
MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/register.do") .contentType(MediaType.APPLICATION_JSON) .content(registerJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn();
在這裏,咱們對MockMvc的對象設置了相應的URL以及Content類型、數據,而且期待了它的狀態碼。
在這篇文章中,筆者和你們一塊兒分析了ZStack的自動化測試,以及在JavaWeb應用中常見的測試方法。固然,這些測試都屬於集成測試。而單元測試以及如何在本身的應用中編寫一套更強大的自動測試框架這類主題,以後有機會筆者會再與你們分享。
擴展閱讀: ZStack:管理節點基於模擬器的Integration Test框架