小談自動化測試:從ZStack Integration Test談起

本文首發於泊浮目的專欄: https://segmentfault.com/blog...

前言

筆者工做2年有餘,剛開始實習的時候是不知道自動化測試這種神器的,在剛開始工做的時候每每苦於救火滅火再救火,搞的心力憔悴,一度懷疑猿生。實踐自動化測試後感受生產力慢慢的解放了,那個時候搞的仍是偏單機應用,測試的Cover也是止步在單機應用上。在接觸到了ZStack之後,因爲其產品化的特性,對軟件質量要求偏高,然做爲一個典型的分佈式系統,測試的覆蓋率倒是較高的。在這篇文章,筆者想談談對自動化測試的一些想法。java

收益

自動化測試的收益點很明顯,幾乎衆所周知:git

  • 保證軟件質量,重複的活交給機器來作,避免繁瑣重複的手動測試,節省人力;
  • 爲重構打下良好的基礎:軟件內部不管如何重構,對外部請求所返回的結果不該該有所變化;
  • 保證核心類庫的邏輯不遭受破壞,同時也能夠做爲使用的「樣本」,因爲沒有業務邏輯的耦合,代碼顯得更加清楚,便於閱讀;
  • .....

難點

既然收益這麼高,爲何現實中自動化測試實施起來就像勞動人民愛勞動這句話同樣這麼不現實呢?大概有這幾點:github

  • 對代碼架構要求較高:能靈活測試(集測、單測)的代碼每每是鬆耦合的,可是鬆耦合程度的控制可不是一個簡單的問題;
  • 開發者得夠「懶」:開發者得願意作一勞永逸的事,而不是每次都手測一下;
  • 項目負責人對自動化測試不重視,眼裏只有交付;
  • .....

ZStack的自動化測試實踐

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

  • environment:構建一個環境
  • test:用於跑Case自己
  • run:用於跑SubCase
  • clean:清理環境。這是SubCase必須關注的,否則會致使環境中含有髒數據

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,能夠看到不少相似的方法:

  • env.afterSimulator
  • env.simulator
  • env.message

這幾個方法用來hook Message和HTTP Request。因爲在ZStack中各個組件的通訊都由Message來完成,對於Agent的請求則是統一經過HTTP來完成。這樣在TestCase就能夠任意模擬任何組件及agent的狀態,讓Case有極強的實用性——也保證了ManagentMent Node的邏輯健壯。

在Java Web應用中的MockMvc實踐自動化測試

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框架
相關文章
相關標籤/搜索