本文首发于泊浮目的专栏:https://segmentfault.com/blog/camile

版本 日期 备注
1.0 2018.2.1 文章首发
1.1 2021.5.21 修改标题:小谈自动化测试:从ZStack Integration Test谈起
-> 技巧:ZStack如何做Integration Test

前言

笔者工作2年有余,刚开始实习的时候是不知道自动化测试这种神器的,在刚开始工作的时候往往苦于救火灭火再救火,搞的心力憔悴,一度怀疑猿生。实践自动化测试后感觉生产力慢慢的解放了,那个时候搞的还是偏单机应用,测试的Cover也是止步在单机应用上。在接触到了ZStack以后,由于其产品化的特性,对软件质量要求偏高,然作为一个典型的分布式系统,测试的覆盖率却是较高的。在这篇文章,笔者想谈谈对自动化测试的一些想法。

收益

自动化测试的收益点很明显,几乎众所周知:

  • 保证软件质量,重复的活交给机器来做,避免繁琐重复的手动测试,节省人力;
  • 为重构打下良好的基础:软件内部无论如何重构,对外部请求所返回的结果不应该有所变化;
  • 保证核心类库的逻辑不遭受破坏,同时也可以作为使用的“样本”,由于没有业务逻辑的耦合,代码显得更加清楚,便于阅读;
  • …..

难点

既然收益这么高,为什么现实中自动化测试实施起来就像劳动人民爱劳动这句话一样这么不现实呢?大概有这几点:

  • 对代码架构要求较高:能灵活测试(集测、单测)的代码往往是松耦合的,但是松耦合程度的控制可不是一个简单的问题;
  • 开发者得够“懒”:开发者得愿意做一劳永逸的事,而不是每次都手测一下;
  • 项目负责人对自动化测试不重视,眼里只有交付;
  • …..

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.

从代码说起

  1. package org.zstack.test.integration.kvm.vm
  2. import org.springframework.http.HttpEntity
  3. import org.zstack.header.vm.VmCreationStrategy
  4. import org.zstack.header.vm.VmInstanceState
  5. import org.zstack.header.vm.VmInstanceVO
  6. import org.zstack.kvm.KVMAgentCommands
  7. import org.zstack.kvm.KVMConstant
  8. import org.zstack.sdk.CreateVmInstanceAction
  9. import org.zstack.sdk.DiskOfferingInventory
  10. import org.zstack.sdk.ImageInventory
  11. import org.zstack.sdk.InstanceOfferingInventory
  12. import org.zstack.sdk.L3NetworkInventory
  13. import org.zstack.sdk.VmInstanceInventory
  14. import org.zstack.test.integration.kvm.Env
  15. import org.zstack.test.integration.kvm.KvmTest
  16. import org.zstack.testlib.EnvSpec
  17. import org.zstack.testlib.SubCase
  18. import org.zstack.testlib.VmSpec
  19. import org.zstack.utils.gson.JSONObjectUtil
  20. /**
  21. * Created by xing5 on 2017/2/22.
  22. */
  23. class OneVmBasicLifeCycleCase extends SubCase {
  24. EnvSpec env
  25. def DOC = """
  26. test a VM's start/stop/reboot/destroy/recover operations
  27. """
  28. @Override
  29. void setup() {
  30. useSpring(KvmTest.springSpec)
  31. }
  32. @Override
  33. void environment() {
  34. env = Env.oneVmBasicEnv()
  35. }
  36. @Override
  37. void test() {
  38. env.create {
  39. testStopVm()
  40. testStartVm()
  41. testRebootVm()
  42. testDestroyVm()
  43. testRecoverVm()
  44. testDeleteCreatedVm()
  45. }
  46. }
  47. void testRecoverVm() {
  48. VmSpec spec = env.specByName("vm")
  49. VmInstanceInventory inv = recoverVmInstance {
  50. uuid = spec.inventory.uuid
  51. }
  52. assert inv.state == VmInstanceState.Stopped.toString()
  53. // confirm the vm can start after being recovered
  54. testStartVm()
  55. }
  56. void testDestroyVm() {
  57. VmSpec spec = env.specByName("vm")
  58. KVMAgentCommands.DestroyVmCmd cmd = null
  59. env.afterSimulator(KVMConstant.KVM_DESTROY_VM_PATH) { rsp, HttpEntity<String> e ->
  60. cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.DestroyVmCmd.class)
  61. return rsp
  62. }
  63. destroyVmInstance {
  64. uuid = spec.inventory.uuid
  65. }
  66. assert cmd != null
  67. assert cmd.uuid == spec.inventory.uuid
  68. VmInstanceVO vmvo = dbFindByUuid(cmd.uuid, VmInstanceVO.class)
  69. assert vmvo.state == VmInstanceState.Destroyed
  70. }
  71. void testRebootVm() {
  72. // reboot = stop + start
  73. VmSpec spec = env.specByName("vm")
  74. KVMAgentCommands.StartVmCmd startCmd = null
  75. KVMAgentCommands.StopVmCmd stopCmd = null
  76. env.afterSimulator(KVMConstant.KVM_STOP_VM_PATH) { rsp, HttpEntity<String> e ->
  77. stopCmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StopVmCmd.class)
  78. return rsp
  79. }
  80. env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity<String> e ->
  81. startCmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class)
  82. return rsp
  83. }
  84. VmInstanceInventory inv = rebootVmInstance {
  85. uuid = spec.inventory.uuid
  86. }
  87. assert startCmd != null
  88. assert startCmd.vmInstanceUuid == spec.inventory.uuid
  89. assert stopCmd != null
  90. assert stopCmd.uuid == spec.inventory.uuid
  91. assert inv.state == VmInstanceState.Running.toString()
  92. }
  93. void testStartVm() {
  94. VmSpec spec = env.specByName("vm")
  95. KVMAgentCommands.StartVmCmd cmd = null
  96. env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity<String> e ->
  97. cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class)
  98. return rsp
  99. }
  100. VmInstanceInventory inv = startVmInstance {
  101. uuid = spec.inventory.uuid
  102. }
  103. assert cmd != null
  104. assert cmd.vmInstanceUuid == spec.inventory.uuid
  105. assert inv.state == VmInstanceState.Running.toString()
  106. VmInstanceVO vmvo = dbFindByUuid(cmd.vmInstanceUuid, VmInstanceVO.class)
  107. assert vmvo.state == VmInstanceState.Running
  108. assert cmd.vmInternalId == vmvo.internalId
  109. assert cmd.vmName == vmvo.name
  110. assert cmd.memory == vmvo.memorySize
  111. assert cmd.cpuNum == vmvo.cpuNum
  112. //TODO: test socketNum, cpuOnSocket
  113. assert cmd.rootVolume.installPath == vmvo.rootVolume.installPath
  114. assert cmd.useVirtio
  115. vmvo.vmNics.each { nic ->
  116. KVMAgentCommands.NicTO to = cmd.nics.find { nic.mac == it.mac }
  117. assert to != null: "unable to find the nic[mac:${nic.mac}]"
  118. assert to.deviceId == nic.deviceId
  119. assert to.useVirtio
  120. assert to.nicInternalName == nic.internalName
  121. }
  122. }
  123. void testStopVm() {
  124. VmSpec spec = env.specByName("vm")
  125. KVMAgentCommands.StopVmCmd cmd = null
  126. env.afterSimulator(KVMConstant.KVM_STOP_VM_PATH) { rsp, HttpEntity<String> e ->
  127. cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StopVmCmd.class)
  128. return rsp
  129. }
  130. VmInstanceInventory inv = stopVmInstance {
  131. uuid = spec.inventory.uuid
  132. }
  133. assert inv.state == VmInstanceState.Stopped.toString()
  134. assert cmd != null
  135. assert cmd.uuid == spec.inventory.uuid
  136. def vmvo = dbFindByUuid(cmd.uuid, VmInstanceVO.class)
  137. assert vmvo.state == VmInstanceState.Stopped
  138. }
  139. void testDeleteCreatedVm() {
  140. VmSpec spec = env.specByName("vm")
  141. DiskOfferingInventory diskOfferingInventory = env.inventoryByName("diskOffering")
  142. InstanceOfferingInventory instanceOfferingInventory = env.inventoryByName("instanceOffering")
  143. ImageInventory imageInventory = env.inventoryByName("image1")
  144. L3NetworkInventory l3NetworkInventory = env.inventoryByName("l3")
  145. CreateVmInstanceAction action = new CreateVmInstanceAction()
  146. action.name = "JustCreatedVm"
  147. action.rootDiskOfferingUuid = diskOfferingInventory.uuid
  148. action.instanceOfferingUuid = instanceOfferingInventory.uuid
  149. action.imageUuid = imageInventory.uuid
  150. action.l3NetworkUuids = [l3NetworkInventory.uuid]
  151. action.strategy = VmCreationStrategy.JustCreate.toString()
  152. action.sessionId = adminSession()
  153. CreateVmInstanceAction.Result result = action.call()
  154. destroyVmInstance {
  155. uuid = result.value.inventory.uuid
  156. }
  157. VmInstanceVO vo = dbFindByUuid(result.value.inventory.uuid, VmInstanceVO.class)
  158. assert vo == null
  159. }
  160. @Override
  161. void clean() {
  162. env.delete()
  163. }
  164. }

我们先从跳转到extends的SubCase中:

  1. package org.zstack.testlib
  2. /**
  3. * Created by xing5 on 2017/2/22.
  4. */
  5. abstract class SubCase extends Test implements Case {
  6. final void run() {
  7. try {
  8. environment()
  9. test()
  10. } catch (Throwable t) {
  11. logger.warn("a sub case [${this.class}] fails, ${t.message}", t)
  12. collectErrorLog()
  13. throw t
  14. } finally {
  15. logger.info("start cleanup for case ${this.class}")
  16. try{
  17. clean()
  18. }catch (Throwable t){
  19. collectErrorLog()
  20. throw t
  21. }
  22. }
  23. }
  24. @Override
  25. protected void runSubCases() {
  26. throw new Exception("runSubCases() cannot be called in a SubCase")
  27. }
  28. }

从签名中可以看到,其继承于Test,并实现了Case接口中的方法,我们看一下 Case

  1. package org.zstack.testlib
  2. /**
  3. * Created by xing5 on 2017/3/3.
  4. */
  5. interface Case {
  6. void environment()
  7. void test()
  8. void run()
  9. void clean()
  10. }

这里定义一个SubCase的基本行为:

  • environment:构建一个环境
  • test:用于跑Case本身
  • run:用于跑SubCase
  • clean:清理环境。这是SubCase必须关注的,不然会导致环境中含有脏数据

Test中,我们也可以看到定义里几个关键抽象函数,用于定义一个Case的行为:

  1. abstract void setup()
  2. abstract void environment()
  3. abstract void test()

所以一个Case必须实现Test中的接口以及Case中的clean方法。

一般在setup中,会将依赖的Bean按需加载进来。这在前面提到过;而environment则会构建出一个环境。Grovvy对DSL支持较好,所以整个环境的构建代码可读性极强,本质上每个DSL都对应了一个Spec,而Sepc对应了一个ZStack的SDK创建调用——即XXXAction。而XXXAction则通过HTTP调用ZStack的API接口。

平时在测试中大家可能会为了Build一个环境直接对数据库进行操作。例如:

  1. xxxRepo.save(new Object());

但在ZStack中并不是一个很好的方案——一个Iaas中的资源依赖及状态变动的关系是错综复杂的,因此调用外部的API来创建资源是一个明智的选择。同时也可以测试SDK和API的行为是否是期待的。

在clean中也是如此。会调用ZStack本身的Cascade逻辑进行资源清理。打开EnvSpec.Grovvy可以看到

  1. static List deletionMethods = [
  2. [CreateZoneAction.metaClass, CreateZoneAction.Result.metaClass, DeleteZoneAction.class],
  3. [AddCephBackupStorageAction.metaClass, AddCephBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class],
  4. [AddCephPrimaryStorageAction.metaClass, AddCephPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class],
  5. [AddCephPrimaryStoragePoolAction.metaClass, AddCephPrimaryStoragePoolAction.Result.metaClass, DeleteCephPrimaryStoragePoolAction.class],
  6. [CreateEipAction.metaClass, CreateEipAction.Result.metaClass, DeleteEipAction.class],
  7. [CreateClusterAction.metaClass, CreateClusterAction.Result.metaClass, DeleteClusterAction.class],
  8. [CreateDiskOfferingAction.metaClass, CreateDiskOfferingAction.Result.metaClass, DeleteDiskOfferingAction.class],
  9. [CreateInstanceOfferingAction.metaClass, CreateInstanceOfferingAction.Result.metaClass, DeleteInstanceOfferingAction.class],
  10. [CreateAccountAction.metaClass, CreateAccountAction.Result.metaClass, DeleteAccountAction.class],
  11. [CreatePolicyAction.metaClass, CreatePolicyAction.Result.metaClass, DeletePolicyAction.class],
  12. [CreateUserGroupAction.metaClass, CreateUserGroupAction.Result.metaClass, DeleteUserGroupAction.class],
  13. [CreateUserAction.metaClass, CreateUserAction.Result.metaClass, DeleteUserAction.class],
  14. [AddImageAction.metaClass, AddImageAction.Result.metaClass, DeleteImageAction.class],
  15. [CreateDataVolumeTemplateFromVolumeAction.metaClass, CreateDataVolumeTemplateFromVolumeAction.Result.metaClass, DeleteImageAction.class],
  16. [CreateRootVolumeTemplateFromRootVolumeAction.metaClass, CreateRootVolumeTemplateFromRootVolumeAction.Result.metaClass, DeleteImageAction.class],
  17. [CreateL2NoVlanNetworkAction.metaClass, CreateL2NoVlanNetworkAction.Result.metaClass, DeleteL2NetworkAction.class],
  18. [CreateL2VlanNetworkAction.metaClass, CreateL2VlanNetworkAction.Result.metaClass, DeleteL2NetworkAction.class],
  19. [AddIpRangeByNetworkCidrAction.metaClass, AddIpRangeByNetworkCidrAction.Result.metaClass, DeleteIpRangeAction.class],
  20. [CreateL3NetworkAction.metaClass, CreateL3NetworkAction.Result.metaClass, DeleteL3NetworkAction.class],
  21. [CreateSchedulerJobAction.metaClass, CreateSchedulerJobAction.Result.metaClass, DeleteSchedulerJobAction.class],
  22. [CreateSchedulerTriggerAction.metaClass, CreateSchedulerTriggerAction.Result.metaClass, DeleteSchedulerTriggerAction.class],
  23. [CreateVmInstanceAction.metaClass, CreateVmInstanceAction.Result.metaClass, DestroyVmInstanceAction.class],
  24. [CreateDataVolumeFromVolumeSnapshotAction.metaClass, CreateDataVolumeFromVolumeSnapshotAction.Result.metaClass, DeleteDataVolumeAction.class],
  25. [CreateDataVolumeFromVolumeTemplateAction.metaClass, CreateDataVolumeFromVolumeTemplateAction.Result.metaClass, DeleteDataVolumeAction.class],
  26. [CreateDataVolumeAction.metaClass, CreateDataVolumeAction.Result.metaClass, DeleteDataVolumeAction.class],
  27. [CreateVolumeSnapshotAction.metaClass, CreateVolumeSnapshotAction.Result.metaClass, DeleteVolumeSnapshotAction.class],
  28. [AddKVMHostAction.metaClass, AddKVMHostAction.Result.metaClass, DeleteHostAction.class],
  29. [CreateLoadBalancerAction.metaClass, CreateLoadBalancerAction.Result.metaClass, DeleteLoadBalancerAction.class],
  30. [AddLocalPrimaryStorageAction.metaClass, AddLocalPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class],
  31. [AddImageStoreBackupStorageAction.metaClass, AddImageStoreBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class],
  32. [AddNfsPrimaryStorageAction.metaClass, AddNfsPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class],
  33. [CreatePortForwardingRuleAction.metaClass, CreatePortForwardingRuleAction.Result.metaClass, DeletePortForwardingRuleAction.class],
  34. [CreateSecurityGroupAction.metaClass, CreateSecurityGroupAction.Result.metaClass, DeleteSecurityGroupAction.class],
  35. [AddSftpBackupStorageAction.metaClass, AddSftpBackupStorageAction.Result.metaClass, DeleteBackupStorageAction.class],
  36. [AddSharedMountPointPrimaryStorageAction.metaClass, AddSharedMountPointPrimaryStorageAction.Result.metaClass, DeletePrimaryStorageAction.class],
  37. [CreateVipAction.metaClass, CreateVipAction.Result.metaClass, DeleteVipAction.class],
  38. [CreateVirtualRouterOfferingAction.metaClass, CreateVirtualRouterOfferingAction.Result.metaClass, DeleteInstanceOfferingAction.class],
  39. [CreateWebhookAction.metaClass, CreateWebhookAction.Result.metaClass, DeleteWebhookAction.class],
  40. [CreateBaremetalPxeServerAction.metaClass, CreateBaremetalPxeServerAction.Result.metaClass, DeleteBaremetalPxeServerAction.class],
  41. [CreateBaremetalChassisAction.metaClass, CreateBaremetalChassisAction.Result.metaClass, DeleteBaremetalChassisAction.class],
  42. [CreateBaremetalHostCfgAction.metaClass, CreateBaremetalHostCfgAction.Result.metaClass, DeleteBaremetalHostCfgAction.class],
  43. [CreateMonitorTriggerAction.metaClass, CreateMonitorTriggerAction.Result.metaClass, DeleteMonitorTriggerAction.class],
  44. [CreateEmailMonitorTriggerActionAction.metaClass, CreateEmailMonitorTriggerActionAction.Result.metaClass, DeleteMonitorTriggerActionAction.class],
  45. [CreateEmailMediaAction.metaClass, CreateEmailMediaAction.Result.metaClass, DeleteMediaAction.class],
  46. [AddLdapServerAction.metaClass, AddLdapServerAction.Result.metaClass, DeleteLdapServerAction.class],
  47. [SubmitLongJobAction.metaClass, SubmitLongJobAction.Result.metaClass, DeleteLongJobAction.class],
  48. ]

设置了对应的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:

  1. import com.camile.base.Utils.JsonUtils;
  2. import com.camile.base.common.CommonResponse;
  3. import com.camile.base.common.error.ResponseCode;
  4. import com.camile.base.common.utils.MD5Util;
  5. import com.camile.base.data.dao.UserRepository;
  6. import com.camile.base.data.dto.user.*;
  7. import com.camile.base.data.entity.UserEntity;
  8. import com.camile.base.data.vo.UserVO;
  9. import com.fasterxml.jackson.databind.ObjectMapper;
  10. import lombok.extern.slf4j.Slf4j;
  11. import org.junit.Assert;
  12. import org.junit.Before;
  13. import org.junit.Test;
  14. import org.junit.runner.RunWith;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.boot.test.context.SpringBootTest;
  17. import org.springframework.http.MediaType;
  18. import org.springframework.mock.web.MockHttpSession;
  19. import org.springframework.test.context.junit4.SpringRunner;
  20. import org.springframework.test.web.servlet.MockMvc;
  21. import org.springframework.test.web.servlet.MvcResult;
  22. import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
  23. import org.springframework.test.web.servlet.setup.MockMvcBuilders;
  24. import org.springframework.transaction.annotation.Transactional;
  25. import org.springframework.web.context.WebApplicationContext;
  26. import javax.servlet.http.HttpSession;
  27. import java.util.Map;
  28. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  29. /**
  30. * Created by Camile
  31. * 1.用户注册
  32. * 2.用户登录,测试自己是否处于登录状态,并执行更新信息、修改密码操作
  33. * 3.用户登出,更新信息、在线修改密码,应全部失败。
  34. * 4.用户用新信息登录,成功
  35. * 5.用户登出,测试自己是否处于登录状态,走忘记密码流程
  36. * 6.修改后再次登录,成功
  37. */
  38. @Slf4j
  39. @Transactional
  40. @RunWith(SpringRunner.class)
  41. @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
  42. public class UserBasicTests {
  43. @Autowired
  44. private UserRepository userRepository;
  45. @Autowired
  46. private WebApplicationContext context;
  47. private String uuid;
  48. private HttpSession session;
  49. private MockMvc mvc;
  50. private ObjectMapper mapper;
  51. private final String email = "487643862@qq.com";
  52. private String password = "newPassword";
  53. private final String question = "are you ok ?";
  54. private final String answer = "im fine";
  55. private final String name = "camile";
  56. private final String phone = "13043769014";
  57. private String updateName = "camile1";
  58. private String updateEmail = "587643862@qq.com";
  59. private String updatePhone = "13834671096";
  60. @Before
  61. public void setUp() throws Exception {
  62. mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
  63. mapper = new ObjectMapper();
  64. }
  65. @Test
  66. public void test() throws Exception {
  67. testRegisterSuccess();
  68. testIsLoginFailure();
  69. testLoginSuccess();
  70. testIsLoginSuccess();
  71. testUpdateInformationSuccess();
  72. testOnlineRestPwdSuccess();
  73. testLoginOutSuccess();
  74. testUpdateInformationFailure();
  75. testOnlineRestPwdFailure();
  76. testloginWithOldPwdFailure();
  77. testLoginWithNewInfoSuccess();
  78. testLoginOutSuccess();
  79. testForgetPwdAndResetSuccess();
  80. testLoginWithNewInfoSuccess();
  81. }
  82. private void testRegisterSuccess() throws Exception {
  83. UserAllPropertyDTO dto = new UserAllPropertyDTO();
  84. dto.setEmail(email);
  85. dto.setPassword(password);
  86. dto.setQuestion(question);
  87. dto.setAnswer(answer);
  88. dto.setName(name);
  89. dto.setPhone(phone);
  90. String registerJson = JsonUtils.ObjectToJson(dto);
  91. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/register.do")
  92. .contentType(MediaType.APPLICATION_JSON)
  93. .content(registerJson)
  94. .accept(MediaType.APPLICATION_JSON))
  95. .andExpect(status().isOk())
  96. .andReturn();
  97. String content = result.getResponse().getContentAsString();
  98. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  99. Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode());
  100. UserVO vo = JsonUtils.jsonToObject((Map) response.getData(), UserVO.class);
  101. Assert.assertNotNull(userRepository.findByUuid(vo.getUuid()));
  102. uuid = vo.getUuid();
  103. session = result.getRequest().getSession();
  104. }
  105. private void testIsLoginFailure() throws Exception { // never login
  106. MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/isLogin.do?uuid=%s", uuid))
  107. .session((MockHttpSession) session)
  108. .contentType(MediaType.APPLICATION_JSON)
  109. .accept(MediaType.APPLICATION_JSON))
  110. .andExpect(status().isOk())
  111. .andReturn();
  112. String content = result.getResponse().getContentAsString();
  113. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  114. Assert.assertEquals(response.getCode(), ResponseCode.NeedLogin.getCode());
  115. session = result.getRequest().getSession();
  116. }
  117. private void testLoginSuccess() throws Exception {
  118. UserLoginDTO dto = new UserLoginDTO(name, password);
  119. String loginJson = JsonUtils.ObjectToJson(dto);
  120. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do")
  121. .session((MockHttpSession) session)
  122. .contentType(MediaType.APPLICATION_JSON)
  123. .content(loginJson)
  124. .accept(MediaType.APPLICATION_JSON))
  125. .andExpect(status().isOk())
  126. .andReturn();
  127. String content = result.getResponse().getContentAsString();
  128. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  129. Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode());
  130. session = result.getRequest().getSession();
  131. }
  132. private void testIsLoginSuccess() throws Exception {
  133. MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/isLogin.do?uuid=%s", uuid))
  134. .session((MockHttpSession) session)
  135. .accept(MediaType.APPLICATION_JSON))
  136. .andExpect(status().isOk())
  137. .andReturn();
  138. String content = result.getResponse().getContentAsString();
  139. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  140. Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode());
  141. session = result.getRequest().getSession();
  142. }
  143. private void testUpdateInformationSuccess() throws Exception {
  144. UserDTO dto = new UserDTO();
  145. dto.setUuid(uuid);
  146. dto.setName(updateName);
  147. dto.setEmail(updateEmail);
  148. dto.setPhone(updatePhone);
  149. String updateJson = JsonUtils.ObjectToJson(dto);
  150. MvcResult result = mvc.perform(MockMvcRequestBuilders.put("/user/information.do")
  151. .session((MockHttpSession) session)
  152. .contentType(MediaType.APPLICATION_JSON)
  153. .content(updateJson)
  154. .accept(MediaType.APPLICATION_JSON))
  155. .andExpect(status().isOk())
  156. .andReturn();
  157. String content = result.getResponse().getContentAsString();
  158. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  159. Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode());
  160. UserEntity entity = userRepository.findByUuid(uuid);
  161. UserVO vo = JsonUtils.jsonToObject((Map) response.getData(), UserVO.class);
  162. Assert.assertNotNull(entity);
  163. Assert.assertEquals(vo.getName(), entity.getName());
  164. Assert.assertEquals(vo.getPhone(), entity.getPhone());
  165. Assert.assertEquals(vo.getEmail(), entity.getEmail());
  166. Assert.assertEquals(vo.getEmail(), updateEmail);
  167. Assert.assertEquals(vo.getPhone(), updatePhone);
  168. Assert.assertEquals(vo.getName(), updateName);
  169. session = result.getRequest().getSession();
  170. }
  171. private void testOnlineRestPwdSuccess() throws Exception {
  172. UserResetPwdDTO dto = new UserResetPwdDTO();
  173. dto.setUuid(uuid);
  174. dto.setOldPassword(password);
  175. dto.setNewPassword("12345678");
  176. password = "12345678";
  177. String resetPwdJson = JsonUtils.ObjectToJson(dto);
  178. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/onlineResetPwd.do")
  179. .session((MockHttpSession) session)
  180. .contentType(MediaType.APPLICATION_JSON)
  181. .content(resetPwdJson)
  182. .accept(MediaType.APPLICATION_JSON))
  183. .andExpect(status().isOk())
  184. .andReturn();
  185. String content = result.getResponse().getContentAsString();
  186. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  187. Assert.assertEquals(response.getMsg(), response.getCode(), ResponseCode.Success.getCode());
  188. session = result.getRequest().getSession();
  189. UserEntity userEntity = userRepository.findByUuid(uuid);
  190. Assert.assertEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8(password));
  191. }
  192. private void testLoginOutSuccess() throws Exception {
  193. MvcResult result = mvc.perform(MockMvcRequestBuilders.post(String.format("/user/loginOut.do?uuid=%s", uuid))
  194. .session((MockHttpSession) session)
  195. .accept(MediaType.APPLICATION_JSON))
  196. .andExpect(status().isOk())
  197. .andReturn();
  198. String content = result.getResponse().getContentAsString();
  199. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  200. Assert.assertEquals(response.getCode(), ResponseCode.Success.getCode());
  201. session = result.getRequest().getSession();
  202. }
  203. private void testUpdateInformationFailure() throws Exception {
  204. String updateName = "camile2";
  205. String updateEmail = "687643862@qq.com";
  206. String updatePhone = "14834671096";
  207. UserDTO dto = new UserDTO();
  208. dto.setUuid(uuid);
  209. dto.setName(updateName);
  210. dto.setEmail(updateEmail);
  211. dto.setPhone(updatePhone);
  212. String updateJson = JsonUtils.ObjectToJson(dto);
  213. MvcResult result = mvc.perform(MockMvcRequestBuilders.put("/user/information.do")
  214. .session((MockHttpSession) session)
  215. .contentType(MediaType.APPLICATION_JSON)
  216. .content(updateJson)
  217. .accept(MediaType.APPLICATION_JSON))
  218. .andExpect(status().isOk())
  219. .andReturn();
  220. String content = result.getResponse().getContentAsString();
  221. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  222. Assert.assertEquals(response.getMsg(), ResponseCode.Failure.getCode(), response.getCode());
  223. session = result.getRequest().getSession();
  224. }
  225. private void testOnlineRestPwdFailure() throws Exception {
  226. UserResetPwdDTO dto = new UserResetPwdDTO();
  227. dto.setUuid(uuid);
  228. dto.setOldPassword(password);
  229. dto.setNewPassword("123456789");
  230. String resetPwdJson = JsonUtils.ObjectToJson(dto);
  231. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/onlineResetPwd.do")
  232. .session((MockHttpSession) session)
  233. .contentType(MediaType.APPLICATION_JSON)
  234. .content(resetPwdJson)
  235. .accept(MediaType.APPLICATION_JSON))
  236. .andExpect(status().isOk())
  237. .andReturn();
  238. String content = result.getResponse().getContentAsString();
  239. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  240. Assert.assertEquals(response.getMsg(), response.getCode(), ResponseCode.Failure.getCode());
  241. session = result.getRequest().getSession();
  242. UserEntity userEntity = userRepository.findByUuid(uuid);
  243. Assert.assertNotEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8("123456789"));
  244. }
  245. private void testloginWithOldPwdFailure() throws Exception {
  246. UserLoginDTO dto = new UserLoginDTO(name, "newPassword");
  247. String loginJson = JsonUtils.ObjectToJson(dto);
  248. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do")
  249. .session((MockHttpSession) session)
  250. .contentType(MediaType.APPLICATION_JSON)
  251. .content(loginJson)
  252. .accept(MediaType.APPLICATION_JSON))
  253. .andExpect(status().isOk())
  254. .andReturn();
  255. String content = result.getResponse().getContentAsString();
  256. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  257. Assert.assertEquals(response.getCode(), ResponseCode.UserInfoError.getCode());
  258. session = result.getRequest().getSession();
  259. }
  260. private void testLoginWithNewInfoSuccess() throws Exception {
  261. UserLoginDTO dto = new UserLoginDTO(updateName, password);
  262. String loginJson = JsonUtils.ObjectToJson(dto);
  263. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/login.do")
  264. .session((MockHttpSession) session)
  265. .contentType(MediaType.APPLICATION_JSON)
  266. .content(loginJson)
  267. .accept(MediaType.APPLICATION_JSON))
  268. .andExpect(status().isOk())
  269. .andReturn();
  270. String content = result.getResponse().getContentAsString();
  271. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  272. Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode());
  273. session = result.getRequest().getSession();
  274. }
  275. private void testForgetPwdAndResetSuccess() throws Exception {
  276. MvcResult result = mvc.perform(MockMvcRequestBuilders.get(String.format("/user/forget/question?name=%s", updateName))
  277. .session((MockHttpSession) session)
  278. .contentType(MediaType.APPLICATION_JSON)
  279. .accept(MediaType.APPLICATION_JSON))
  280. .andExpect(status().isOk())
  281. .andReturn();
  282. String content = result.getResponse().getContentAsString();
  283. CommonResponse response = mapper.readValue(content, CommonResponse.class);
  284. Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode());
  285. session = result.getRequest().getSession();
  286. String question = (String) response.getData();
  287. Assert.assertEquals(question, this.question);
  288. UserQuestionDTO dto = new UserQuestionDTO();
  289. dto.setName(updateName);
  290. dto.setQuestion(question);
  291. dto.setAnswer(answer);
  292. String questionJson = JsonUtils.ObjectToJson(dto);
  293. result = mvc.perform(MockMvcRequestBuilders.post("/user/forget/checkAnswer.do")
  294. .session((MockHttpSession) session)
  295. .contentType(MediaType.APPLICATION_JSON)
  296. .content(questionJson)
  297. .accept(MediaType.APPLICATION_JSON))
  298. .andExpect(status().isOk())
  299. .andReturn();
  300. content = result.getResponse().getContentAsString();
  301. response = mapper.readValue(content, CommonResponse.class);
  302. Assert.assertEquals(ResponseCode.Success.getCode(), response.getCode());
  303. session = result.getRequest().getSession();
  304. String token = (String) response.getData();
  305. UserForgetResetPwdDTO userForgetResetPwdDTO = new UserForgetResetPwdDTO();
  306. userForgetResetPwdDTO.setForgetToken(token);
  307. userForgetResetPwdDTO.setName(updateName);
  308. userForgetResetPwdDTO.setNewPassword("superpwd!");
  309. password = "superpwd!";
  310. String resetPwdDTO = JsonUtils.ObjectToJson(userForgetResetPwdDTO);
  311. result = mvc.perform(MockMvcRequestBuilders.post("/user/forget/resetPassword.do")
  312. .session((MockHttpSession) session)
  313. .contentType(MediaType.APPLICATION_JSON)
  314. .content(resetPwdDTO)
  315. .accept(MediaType.APPLICATION_JSON))
  316. .andExpect(status().isOk())
  317. .andReturn();
  318. content = result.getResponse().getContentAsString();
  319. response = mapper.readValue(content, CommonResponse.class);
  320. Assert.assertEquals(response.getMsg(), ResponseCode.Success.getCode(), response.getCode());
  321. session = result.getRequest().getSession();
  322. UserEntity userEntity = userRepository.findByUuid(uuid);
  323. Assert.assertEquals(userEntity.getPassword(), MD5Util.MD5EncodeUtf8(password));
  324. }
  325. }

我们可以看到MockMvc的链式调用让代码可读性变得极强:

  1. MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/user/register.do")
  2. .contentType(MediaType.APPLICATION_JSON)
  3. .content(registerJson)
  4. .accept(MediaType.APPLICATION_JSON))
  5. .andExpect(status().isOk())
  6. .andReturn();

在这里,我们对MockMvc的对象设置了相应的URL以及Content类型、数据,并且期待了它的状态码。

小结

在这篇文章中,笔者和大家一起分析了ZStack的自动化测试,以及在JavaWeb应用中常见的测试方法。当然,这些测试都属于集成测试。而单元测试以及如何在自己的应用中编写一套更强大的自动测试框架这类主题,之后有机会笔者会再与大家分享。

扩展阅读:ZStack:管理节点基于模拟器的Integration Test框架