技巧:ZStack如何做Integration Test

本文首發(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應用中常見的測試方法抡诞。當然穷蛹,這些測試都屬于集成測試。而單元測試以及如何在自己的應用中編寫一套更強大的自動測試框架這類主題昼汗,之后有機會筆者會再與大家分享肴熏。

擴展閱讀:ZStack:管理節(jié)點基于模擬器的Integration Test框架

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市顷窒,隨后出現的幾起案子蛙吏,更是在濱河造成了極大的恐慌,老刑警劉巖鞋吉,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸦做,死亡現場離奇詭異,居然都是意外死亡谓着,警方通過查閱死者的電腦和手機泼诱,發(fā)現死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赊锚,“玉大人治筒,你說我怎么就攤上這事∠掀眩” “怎么了耸袜?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長牲平。 經常有香客問我句灌,道長,這世上最難降的妖魔是什么欠拾? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任胰锌,我火速辦了婚禮,結果婚禮上藐窄,老公的妹妹穿的比我還像新娘资昧。我一直安慰自己,他們只是感情好荆忍,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布格带。 她就那樣靜靜地躺著,像睡著了一般刹枉。 火紅的嫁衣襯著肌膚如雪叽唱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天微宝,我揣著相機與錄音棺亭,去河邊找鬼。 笑死蟋软,一個胖子當著我的面吹牛镶摘,可吹牛的內容都是我干的嗽桩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凄敢,長吁一口氣:“原來是場噩夢啊……” “哼碌冶!你這毒婦竟也來了?” 一聲冷哼從身側響起涝缝,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤扑庞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拒逮,有當地人在樹林里發(fā)現了一具尸體嫩挤,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年消恍,在試婚紗的時候發(fā)現自己被綠了岂昭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡狠怨,死狀恐怖约啊,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情佣赖,我是刑警寧澤恰矩,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站憎蛤,受9級特大地震影響外傅,放射性物質發(fā)生泄漏。R本人自食惡果不足惜俩檬,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一萎胰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棚辽,春花似錦技竟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至联逻,卻和暖如春搓扯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背包归。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工锨推, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓爱态,卻偏偏與公主長得像,于是被迫代替她去往敵國和親境钟。 傳聞我的和親對象是個殘疾皇子锦担,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容