In this paper, starting from purpose of moored floating column: segmentfault.com/blog/camile

This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details

version The date of note
1.0 2018.2.1 The article first
1.1 2021.5.21 Modify the title:Test automation: Start with the ZStack Integration Test-> Tip: How does ZStack do Integration Test

preface

The author has worked for more than 2 years. At the beginning of my internship, I did not know about automatic testing. At the beginning of my work, I was often suffering from fire fighting and fire fighting, and I was haggard and once suspected that apes were born. After the practice of automated testing, I felt that productivity was gradually liberated. At that time, I was still engaged in single-machine application, and the Cover of the test was restricted to single-machine application. After contacting ZStack, due to its productized characteristics, the software quality requirements are relatively high. However, as a typical distributed system, the test coverage is relatively high. In this article, I’d like to share some thoughts on automated testing.

earnings

The benefits of automated testing are obvious and almost well-known:

  • Ensure the quality of software, repetitive work to the machine to do, avoid tedious repeated manual testing, save manpower;
  • Lay a good foundation for refactoring: no matter how refactoring happens inside the software, the results returned by external requests should not change;
  • Ensure that the logic of the core class library is not broken, and can also be used as a “sample”, because there is no coupling of business logic, the code is clearer and easier to read;
  • .

The difficulties in

Since the benefits are so high, why is the implementation of automated testing as unrealistic as the saying that working people love labor? Here are some things:

  • High requirements for code architecture: the code that can be flexibly tested (cluster test, single test) is often loosely-coupled, but controlling the degree of loosely-coupled is not a simple problem;
  • Developers need to be “lazy” : developers need to be willing to do something once and for all, rather than test it every time;
  • The project leader does not care about automated testing and only cares about delivery.
  • .

ZStack’s automated testing practices

ZStack’s automated tests are based on Junit’s integration tests written in Grovvy, which at run time load dependent beans on demand and start a JVM process, as well as a Jetty based HTTPServer for Mock Agent behavior.

Many people think Junit is for unit testing. Not really. The website says: JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.

Start with the code

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()
    }
}
Copy the code

Let’s jump to extends’s SubCase first:

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") } }Copy the code

As you can see from the signature, it inherits from Test and implements methods in the Case interface. Let’s look at Case:

package org.zstack.testlib

/**
 * Created by xing5 on 2017/3/3.
 */
interface Case {
    void environment()
    void test()
    void run()
    void clean()
}
Copy the code

Here defines the basic behavior of a SubCase:

  • Environment: Builds an environment
  • Test: used to run the Case itself
  • Run: Run SubCase
  • Clean: Cleans the environment. This is something SubCase must be concerned about, otherwise the environment will contain dirty data

In Test, we can also see several key abstract functions in the definition that define the behavior of a Case:

    abstract void setup()
    abstract void environment()
    abstract void test()
Copy the code

So a Case must implement the interface in Test and the clean method in Case.

Typically in Setup, dependent beans are loaded on demand. This was mentioned earlier; The environment builds an environment. Grovvy has good DSL support, so the build code for the entire environment is extremely readable, with each DSL essentially corresponding to a Spec, and Sepc to a ZStack SDK creation call — namely, XXXAction. XXXAction calls the ZStack API through HTTP.

In normal tests, you may want to operate directly on the database in order to Build an environment. Such as:

xxxRepo.save(new Object());
Copy the code

However, this is not a good solution in ZStack — the relationship between resource dependencies and state changes in an Iaas is complex, so calling an external API to create resources is a wise choice. You can also test whether the SDK and API behave as expected.

The same is true in Clean. ZStack’s own Cascade logic is called for resource cleanup. Open envspec.grovvy to see

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],
    ]
Copy the code

CreateAction and deleteAction are set to be called when cleaning up the environment. This also covers the Cascade logic.

Use loose coupling for flexible testing

If you look at the ZStack Case, you can see many similar methods:

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

These methods are used to hook messages and HTTP requests. Since the communication of each component in ZStack is completed by Message, the request of Agent is uniformly completed through HTTP. In this way, the state of any component and agent can be simulated at will in TestCase, making the Case extremely practical — and ensuring that the logic of Managenfree-to-play is robust.

The MockMvc practice automates testing in Java Web applications

ZStack’s SDK essentially wraps a layer of HTTP Path, using a common protocol for developers to develop or test. In a traditional Java WEB application, you typically test with MockMvc. The essence of this is to test by calling each API’s Path pass parameter. Here’s a 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 = "[email protected]";
    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 = "[email protected]";
    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 = "[email protected]";
        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));
    }
}
Copy the code

We can see that the MockMvc chain-call makes the code very readable:

MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/register.do") .contentType(MediaType.APPLICATION_JSON)  .content(registerJson) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn();Copy the code

Here, we set the corresponding URL to the MockMvc object along with the Content type, data, and expect its status code.

summary

In this article, the author and everyone analyzed ZStack automated testing, as well as common testing methods in JavaWeb applications. Of course, these tests are integration tests. Unit testing and how to write a more powerful automated testing framework for your own applications are topics THAT I will share with you at a later time.

ZStack: Integration Test framework for management nodes based on emulators