ZStack——自动化测试系统1:集成测试

 

测试,对于一个IaaS软件的可靠性、成熟度和可维护性而言,是一个重要的因素.测试在ZStack中是全自动的。这个自动化测试系统包括了三个部分:集成测试,系统测试,基于模块的测试。其中集成测试构建于Junit之上,使用了模拟器。经过这个集成测试系统提供的各类各样的功能,开发人员能够快速的写出测试用例,用于验证一个新特性或者一个缺陷修复。后端

 

概述api

这个关键因素,在构建一个可靠的、成熟的和可维护的软件产品中,就是架构;这是咱们自始自终相信的设计原则。ZStack已经付出了大量的努力,以设计这么一个架构:始终保持软件稳定,不管是添加新特性,常规的操做错误,仍是为特殊目的裁剪;咱们以前的文章:ZStack—进程内微服务架构、ZStack—通用插件系统、ZStack—工做流引擎、ZStack—标签系统,已经表现了咱们的一些尝试。然而,咱们也充分理解测试在软件开发中的重要性。ZStack,从第一天开始,设定了这么一个目标:每个特性都必须有测试用例保证,测试必须是所有自动化的,写单元测试应该是验证一个新特性或任何代码改变的惟一方式。服务器

 

为了实现这个目标,咱们把咱们的测试系统分红了三个组件:集成测试,系统测试,模块测试。分类方式是经过它们的关注点和功能。网络

  1. 集成测试系统构建于Junit,所有使用模拟器;测试用例存放在ZStack的Java源代码中;开发人员能够轻松地使用常规的Junit命令来启动测试套件。
  2. 系统测试系统是一个独立的Python项目,称之为zstack-woodpecker,基于ZStack的API;在一个真实的硬件环境中测试一切。
  3. 基于模块的测试系统构建于基于模块的测试这么一个理论,是zstack-woodpecker中的一个子项目。这个系统中的测试用例将会持续地,以随机的方式,执行API,直到一些预约义的条件被知足。

从这篇文章开始,咱们将会有一系列的,共计三篇文章,来详细阐述咱们的测试架构,以向你展现咱们保证ZStack每个特性的方式。架构

 

单元测试的几句话app

好奇的读者可能已经在他们的心中问了这么一个问题,为何咱们没有提到单元测试,这么一个多是最著名的,也是每个冷静的测试驱动的开发人员会强调的测试概念。咱们确实有单元测试。若是你看到了后续的章节:测试框架,你可能会困惑,为何用在命令中的命名相似于:UnitTest balabala,但在这篇文章中被命名为集成测试。框架

一开始,咱们认为咱们的测试就是单元测试,由于每个用例都是用于验证一个独立的组件,而不是整个软件;例如,这么一个用例:TestCreateZone,只测试Zone服务,其余的组件,像VM服务、存储服务将甚至不会被加载。然而,咱们作测试的方式确实和传统的单元测试概念有所不一样,传统的方式是测试一小段代码,一般是针对内部结构的白盒测试,使用mock和stub的方法论。当前的ZStack有大概120个测试用例知足这个定义,而剩下的500多个并不。大多数的测试用例,甚相当注于独立服务或组件的,都更像集成测试用例,由于会加载多个依赖的服务、组件用以执行一个测试活动。微服务

另外一方面,咱们大多数的,基于模拟器的测试用例,都实际上在API层面进行测试,这对单元测试的定义而言,这就是倾向于集成测试的黑盒测试。基于这些事实,咱们最终改变了咱们的主意,咱们将要作的是集成测试,不过保留了大量的旧的命名方式,相似UnitTest balabla。单元测试

 

集成测试测试

从咱们先前的经验中,咱们深入地意识到,开发人员持续忽视测试的一个主要缘由是:写测试太难了,有的时候甚至比实现一个特性还要难。当咱们设计这个集成测试系统的时候,一个反复考虑的地方即是尽量地从开发人员那边卸下负担,让系统自身作绝大多数无聊、繁杂的工做。

对于几乎全部的测试用例而言,有两种重复性的工做。其中一个是准备一个最小的可是能够工做的软件;例如,为了测试一个zone,你只须要核心的库和zone服务被加载,没有必要加载其余的服务,由于咱们不须要它们。另外一个是准备环境;例如,一个测试VM建立的用例,会须要这么一个环境,有一个zone、一个cluster、一个host、存储、网络和全部的其余必须的资源准备就绪;开发人员不会想去重复无聊的事情,像建立一个zone,添加一个host,在他们可以真正开始测试本身的东西以前;理想的状况是,他们能够以最小的努力便得到一个准备就绪的环境,以集中精力与他们想测试的东西。

 

组件加载器

咱们解决了全部的这些问题,经过一个构建于JUnit之上的框架。在一切开始以前,因为ZStack经过使用Spring管理着全部的组件,咱们建立了一个BeanConstruct,这样测试人员能够按需指定他们想要加载的组件:

public class TestCreateZone {

    Api api;

    ComponentLoader loader;

    DatabaseFacade dbf;

 

    @Before

    public void setUp() throws Exception {

        DBUtil.reDeployDB();

        BeanConstructor con = new BeanConstructor();

        loader = con.addXml("PortalForUnitTest.xml").addXml("ZoneManager.xml").addXml("AccountManager.xml").build();

        dbf = loader.getComponent(DatabaseFacade.class);

        api = new Api();

        api.startServer();

    }

在上面这个例子中,咱们添加了三个Spring配置到BeanConstructor,它们的名字暗示了将会为帐户服务、zone服务和其余包括在PortalForUnitTest.xml中的库加载组件。经过这种方式,测试人员能够把软件定制成一个最小的尺寸,仅包含须要的组件,以便加速测试过程和使东西易于调试。

 

环境部署器

为了帮助测试人员准备一个环境,包含将被测试的活动的全部必须依赖,咱们建立了一个部署器,能够读取一个XML配置文件以部署一个完整的模拟器环境:

publicclassTestCreateVm{
Deployerdeployer;   
Apiapi;   
ComponentLoaderloader;   
CloudBusbus;   
DatabaseFacadedbf;   
 
@Before   
publicvoidsetUp()throwsException{   
DBUtil.reDeployDB();       
deployer=newDeployer("deployerXml/vm/TestCreateVm.xml");       
deployer.build();       
api=deployer.getApi();       
loader=deployer.getComponentLoader();       
bus=loader.getComponent(CloudBus.class);       
dbf=loader.getComponent(DatabaseFacade.class);       
}   
   
@Test    
publicvoidtest()throwsApiSenderException,InterruptedException{   
InstanceOfferingInventoryioinv=api.listInstanceOffering(null).get(0);       
ImageInventoryiminv=api.listImage(null).get(0);       
VmInstanceInventoryinv=api.listVmInstances(null).get(0);       
Assert.assertEquals(inv.getInstanceOfferingUuid(),ioinv.getUuid());       
Assert.assertEquals(inv.getImageUuid(),iminv.getUuid());       
Assert.assertEquals(VmInstanceState.Running.toString(),inv.getState());       
Assert.assertEquals(3,inv.getVmNics().size());       
VmInstanceVOvm=dbf.findByUuid(inv.getUuid(),VmInstanceVO.class);       
Assert.assertNotNull(vm);       
Assert.assertEquals(VmInstanceState.Running,vm.getState());       
for(VmNicInventorynic:inv.getVmNics()){       
VmNicVOnvo=dbf.findByUuid(nic.getUuid(),VmNicVO.class);           
Assert.assertNotNull(nvo);           
}       
VolumeVOroot=dbf.findByUuid(inv.getRootVolumeUuid(),VolumeVO.class);       
Assert.assertNotNull(root);       
for(VolumeInventoryv:inv.getAllVolumes()){       
if(v.getType().equals(VolumeType.Data.toString())){           
VolumeVOdata=dbf.findByUuid(v.getUuid(),VolumeVO.class);               
Assert.assertNotNull(data);               
}           
}       
}   
}

在上面这个TestCreateVm的用例中,部署器读取了一个配置文件,存放在deployerXml/vm/TestCreateVm.xml,而后部署了一个完整的,准备好建立新的VM的环境;更进一步,咱们事实上让部署器建立了这个VM,正如你并无在test方法看到任何代码调用api.createVmByFullConfig();测试人员真正作的事情是,验证这个VM是否按照咱们在deployerXml/vm/TestCreateVm.xml中指定的条件正确地建立。如今你看到了这一切是多么的容易了,测试人员只写了大概60行代码,而后将一个IaaS软件中最重要的部分——建立VM,测试好。

 

这个在上面例子中的配置文件TestCreateVm.xml看起来像:

<?xml version="1.0" encoding="UTF-8"?>
<deployerConfigxmlns="http://zstack.org/schema/zstack">
<instanceOfferings>   
<instanceOfferingname="TestInstanceOffering"       
description="Test"memoryCapacity="3G"cpuNum="1"cpuSpeed="3000"/>           
</instanceOfferings>   
 
<backupStorages>   
<simulatorBackupStoragename="TestBackupStorage"       
description="Test"url="nfs://test"/>           
</backupStorages>   
 
<images>   
<imagename="TestImage"description="Test"format="simulator">       
<backupStorageRef></backupStorageRef>           TestBackupStorage
</image>       
</images>   
 
<diskOfferingname="TestRootDiskOffering"description="Test"   
diskSize="50G"/>       
<diskOfferingname="TestDataDiskOffering"description="Test"   
diskSize="120G"/>       
 
<vm>   
<userVmname="TestVm"description="Test">       
<rootDiskOfferingRef></rootDiskOfferingRef>           TestRootDiskOffering
<imageRef></imageRef>           TestImage
<instanceOfferingRef></instanceOfferingRef>           TestInstanceOffering
<l3NetworkRef></l3NetworkRef>           TestL3Network1
<l3NetworkRef></l3NetworkRef>           TestL3Network2
<l3NetworkRef></l3NetworkRef>           TestL3Network3
<defaultL3NetworkRef></defaultL3NetworkRef>           TestL3Network1
<diskOfferingRef></diskOfferingRef>           TestDataDiskOffering
</userVm>       
</vm>   
 
<zones>   
<zonename="TestZone"description="Test">       
<clusters>            
<clustername="TestCluster"description="Test">               
<hosts>                   
<simulatorHostname="TestHost1"description="Test"                       
managementIp="10.0.0.11"memoryCapacity="8G"cpuNum="4"cpuSpeed="2600"/>                           
<simulatorHostname="TestHost2"description="Test"                       
managementIp="10.0.0.12"memoryCapacity="4G"cpuNum="4"cpuSpeed="2600"/>                           
</hosts>                   
<primaryStorageRef></primaryStorageRef>                   TestPrimaryStorage
<l2NetworkRef></l2NetworkRef>                   TestL2Network
</cluster>               
</clusters>           
 
<l2Networks>           
<l2NoVlanNetworkname="TestL2Network"description="Test"               
physicalInterface="eth0">                   
<l3Networks>                   
<l3BasicNetworkname="TestL3Network1"description="Test">                       
<ipRangename="TestIpRange1"description="Test"startIp="10.0.0.100"                           
endIp="10.10.1.200"gateway="10.0.0.1"netmask="255.0.0.0"/>                               
</l3BasicNetwork>                       
<l3BasicNetworkname="TestL3Network2"description="Test">                       
<ipRangename="TestIpRange2"description="Test"startIp="10.10.2.100"                           
endIp="10.20.2.200"gateway="10.10.2.1"netmask="255.0.0.0"/>                               
</l3BasicNetwork>                       
<l3BasicNetworkname="TestL3Network3"description="Test">                       
<ipRangename="TestIpRange3"description="Test"startIp="10.20.3.100"                           
endIp="10.30.3.200"gateway="10.20.3.1"netmask="255.0.0.0"/>                               
</l3BasicNetwork>                       
</l3Networks>                   
</l2NoVlanNetwork>               
</l2Networks>           
 
<primaryStorages>           
<simulatorPrimaryStoragename="TestPrimaryStorage"               
description="Test"totalCapacity="1T"url="nfs://test"/>                   
</primaryStorages>           
 
<backupStorageRef></backupStorageRef>           TestBackupStorage
</zone>       
</zones>   
</deployerConfig>

模拟器

大多数集成测试用例都构建于模拟器之上;每个资源,只要它须要和后端设备通讯,都有一个模拟器实现;例如,KVM模拟器,虚拟路由虚拟机的模拟器,NFS主存储的模拟器。由于如今的资源后端都是基于Python的HTTP服务器,大多数模拟器经过嵌入了HTTP服务器的Apache Tomcat被构建。KVM模拟器的一小段代码看起来像:

@RequestMapping(value=KVMConstant.KVM_MERGE_SNAPSHOT_PATH,method=RequestMethod.POST)
public@ResponseBodyStringmergeSnapshot(HttpServletRequestreq){   
HttpEntity<String>entity=restf.httpServletRequestToHttpEntity(req);       
MergeSnapshotCmdcmd=JSONObjectUtil.toObject(entity.getBody(),MergeSnapshotCmd.class);       
MergeSnapshotRsprsp=newMergeSnapshotRsp();       
if(!config.mergeSnapshotSuccess){       
rsp.setError("on purpose");           
rsp.setSuccess(false);           
}else{       
snapshotKvmSimulator.merge(cmd.getSrcPath(),cmd.getDestPath(),cmd.isFullRebase());           
config.mergeSnapshotCmds.add(cmd);           
logger.debug(entity.getBody());           
}       
 
replyer.reply(entity,rsp);       
returnnull;       
}   
 
@RequestMapping(value=KVMConstant.KVM_TAKE_VOLUME_SNAPSHOT_PATH,method=RequestMethod.POST)   
public@ResponseBodyStringtakeSnapshot(HttpServletRequestreq){   
HttpEntity<String>entity=restf.httpServletRequestToHttpEntity(req);       
TakeSnapshotCmdcmd=JSONObjectUtil.toObject(entity.getBody(),TakeSnapshotCmd.class);       
TakeSnapshotResponsersp=newTakeSnapshotResponse();       
if(config.snapshotSuccess){       
config.snapshotCmds.add(cmd);           
rsp=snapshotKvmSimulator.takeSnapshot(cmd);           
}else{        
rsp.setError("on purpose");           
rsp.setSuccess(false);           
}       
replyer.reply(entity,rsp);       
returnnull;       
}   

每个模拟器都有一个配置对象,像KVMSimulatorConfig,能够被测试人员用于控制模拟器的行为。

 

测试框架

因为全部的测试用例都事实上是Junit测试用例,测试人员可使用一般的Junit命令单独地跑每个测试用例,例如:

[root@localhost test]# mvn test -Dtest=TestAddImage

并且一个测试套件中的全部用例能够用一条命令执行,例如:

[root@localhost test]# mvn test -Dtest=UnitTestSuite 

http://zstack.org/images/blogs/scalability/testing1.png

用例也能够在一个组里被执行,例如:

[root@localhost test]# mvn test -Dtest=UnitTestSuite -Dconfig=unitTestSuiteXml/eip.xml

一个XML配置文件列出了一个组里的用例,好比,上面的eip.xml看起来像:

<?xml version="1.0" encoding="UTF-8"?>
<UnitTestSuiteConfigxmlns="http://zstack.org/schema/zstack"timeout="120">
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip1"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip2"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip3"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip4"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip5"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip6"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip7"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip8"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip9"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip10"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip11"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip12"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip13"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip14"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip15"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip16"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip17"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip18"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip19"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip20"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip21"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip22"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip23"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip24"/>   
<TestCaseclass="org.zstack.test.eip.TestVirtualRouterEip25"/>   
<TestCaseclass="org.zstack.test.eip.TestQueryEip1"/>   
<TestCaseclass="org.zstack.test.eip.TestEipPortForwardingAttachableNic"/>   
</UnitTestSuiteConfig>

多个用例也能够在一条命令中执行,只要填充它们的名字,例如:

[root@localhost test]# mvn test -Dtest=UnitTestSuite -Dcases=TestAddImage,TestCreateTemplateFromRootVolume,TestCreateDataVolume

 

总结

在这篇文章中,咱们引入了ZStack自动化测试系统的第一部分——集成测试。经过它,开发人员能够以100%的信心写代码。并且写测试用例也再也不是一个使人气馁和无聊的任务;开发人与能够以少于100行的代码来完成大多数的用例,这是很是容易和有效率的。

相关文章
相关标签/搜索