本文首發(fā)于泊浮目的專欄:https://segmentfault.com/blog/camile
版本 | 日期 | 備注 |
---|---|---|
1.0 | 2018.2.1 | 文章首發(fā) |
1.1 | 2021.5.21 | 修改標題:小談自動化測試:從ZStack Integration Test談起 -> 技巧:ZStack如何做Integration Test
|
前言
筆者工作2年有余犬耻,剛開始實習的時候是不知道自動化測試這種神器的渡蜻,在剛開始工作的時候往往苦于救火滅火再救火,搞的心力憔悴,一度懷疑猿生腻暮。實踐自動化測試后感覺生產力慢慢的解放了,那個時候搞的還是偏單機應用侈离,測試的Cover也是止步在單機應用上起宽。在接觸到了ZStack以后,由于其產品化的特性,對軟件質量要求偏高橘忱,然作為一個典型的分布式系統,測試的覆蓋率卻是較高的榄棵。在這篇文章凝颇,筆者想談談對自動化測試的一些想法。
收益
自動化測試的收益點很明顯疹鳄,幾乎眾所周知:
- 保證軟件質量拧略,重復的活交給機器來做,避免繁瑣重復的手動測試瘪弓,節(jié)省人力垫蛆;
- 為重構打下良好的基礎:軟件內部無論如何重構,對外部請求所返回的結果不應該有所變化;
- 保證核心類庫的邏輯不遭受破壞走贪,同時也可以作為使用的“樣本”婴渡,由于沒有業(yè)務邏輯的耦合柠并,代碼顯得更加清楚窄锅,便于閱讀;
- .....
難點
既然收益這么高,為什么現實中自動化測試實施起來就像勞動人民愛勞動這句話一樣這么不現實呢年栓?大概有這幾點:
- 對代碼架構要求較高:能靈活測試(集測否副、單測)的代碼往往是松耦合的赋续,但是松耦合程度的控制可不是一個簡單的問題鹏倘;
- 開發(fā)者得夠“懶”:開發(fā)者得愿意做一勞永逸的事,而不是每次都手測一下稚矿;
- 項目負責人對自動化測試不重視,眼里只有交付侣灶;
- .....
ZStack的自動化測試實踐
ZStack的自動化測試是基于Junit使用Grovvy
編寫的集成測試处嫌,在運行時會把依賴的Bean按需加載進來并啟動一個JVM進程捆昏,同時也會啟動一個基于Jetty的HTTPServer用于Mock Agent的行為烤宙。
很多人以為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
中:
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
的基本行為:
- environment:構建一個環(huán)境
- test:用于跑Case本身
- run:用于跑SubCase
- clean:清理環(huán)境化撕。這是SubCase必須關注的,不然會導致環(huán)境中含有臟數據
在Test
中纯蛾,我們也可以看到定義里幾個關鍵抽象函數,用于定義一個Case的行為:
abstract void setup()
abstract void environment()
abstract void test()
所以一個Case
必須實現Test
中的接口以及Case
中的clean方法。
一般在setup
中,會將依賴的Bean按需加載進來终佛。這在前面提到過;而environment
則會構建出一個環(huán)境妹田。Grovvy對DSL支持較好,所以整個環(huán)境的構建代碼可讀性極強,本質上每個DSL都對應了一個Spec毕泌,而Sepc對應了一個ZStack的SDK創(chuàng)建調用——即XXXAction。而XXXAction則通過HTTP調用ZStack的API接口拧咳。
平時在測試中大家可能會為了Build一個環(huán)境直接對數據庫進行操作养交。例如:
xxxRepo.save(new Object());
但在ZStack中并不是一個很好的方案——一個Iaas中的資源依賴及狀態(tài)變動的關系是錯綜復雜的倒戏,因此調用外部的API來創(chuàng)建資源是一個明智的選擇前鹅。同時也可以測試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,用于清理環(huán)境時調用葱椭。這樣同時也對Cascade邏輯進行了Cover捂寿。
利用松耦合進行靈活的測試
如果看過ZStack的Case
,可以看到很多類似的方法:
- env.afterSimulator
- env.simulator
- env.message
這幾個方法用來hook Message和HTTP Request孵运。由于在ZStack中各個組件的通信都由Message來完成秦陋,對于Agent的請求則是統一通過HTTP來完成。這樣在TestCase就可以任意模擬任何組件及agent的狀態(tài)治笨,讓Case有極強的實用性——也保證了ManagentMent Node的邏輯健壯驳概。
在Java Web應用中的MockMvc實踐自動化測試
ZStack的SDK本質上是包裝了一層HTTP Path,利用通用的協議便于開發(fā)者進行開發(fā)或測試旷赖。而在傳統的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.用戶登錄稚照,測試自己是否處于登錄狀態(tài),并執(zhí)行更新信息俯萌、修改密碼操作
* 3.用戶登出果录,更新信息、在線修改密碼咐熙,應全部失敗弱恒。
* 4.用戶用新信息登錄,成功
* 5.用戶登出棋恼,測試自己是否處于登錄狀態(tài)返弹,走忘記密碼流程
* 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類型琉苇、數據嘲玫,并且期待了它的狀態(tài)碼悦施。
小結
在這篇文章中,筆者和大家一起分析了ZStack的自動化測試去团,以及在JavaWeb應用中常見的測試方法抡诞。當然穷蛹,這些測試都屬于集成測試。而單元測試以及如何在自己的應用中編寫一套更強大的自動測試框架這類主題昼汗,之后有機會筆者會再與大家分享肴熏。