【ZStack】15.自动化测试系统2——系统测试

750 阅读10分钟

ZStack的系统测试系统在真实的硬件环境中运行测试用例;像集成测试一样,这个系统测试也是全自动的,而且覆盖的层面包括:功能性测试、压力测试、性能测试。

概述

虽然集成测试系统,如我们在ZStack—自动化测试系统1:集成测试中所介绍的,强大到可以暴露开发过程中大多数的缺陷,也是有着固有的弱点的。首先,由于测试用例使用模拟器,它们不能测试真实场景,比如在一个物理的KVM主机上创建一个VM。第二,集成测试用例主要关注一个简单的场景,在一个简单的人造的环境中;举个例子,还是创建VM的这个用例,它可能只部署一个最小的环境,包括一个主机和一个L3网络,仅仅用于满足创建一个VM的需求。这些弱点,然而也是深思熟虑过的,因为我们想要开发人员能够在他们开发新特性时快速和容易地写测试用例,这是一个我们必须采取的权衡。

系统测试,目标在于测试整个软件,在一个真实的、复杂的环境中,很自然地补充集成测试。ZStack的系统测试系统被设计用于以下两个目标:

  1. 复杂的场景:这些场景应该比真实世界的使用场景更复杂,以测试软件的极限。举个例子,挂载和卸载磁盘的测试用例应该持续地、重复地对虚拟机执行,以一种非常快,人类无法手动做到的方式。
  2. 易于编写和维护测试用例:就像集成测试系统,系统测试系统接管了大多数无聊重复的任务,让测试人员有效率地写测试用例。

这个系统测试系统是一个Python项目,命名为zstack-woodpecker,由以下三个部分组成:

  1. 测试框架:一个测试框架,管理所有的测试用例,以及提供必须的库和工具。
  2. 环境部署工具:一个工具,用于从XML配置文件部署一个环境;它非常类似于集成测试系统的部署器。
  3. 模块化测试用例:测试用例是高度模块化的,而且覆盖了:功能测试、性能测试和压力测试。

系统测试

zstack-woodpecker完全由我们自己创建;在决定重新造这个轮子之前,我们试过了流行的Python测试框架,像nose,然后最终选择了创造一个新的工具,用以最大化地满足我们的目标。

套件配置

类似所有的其他测试框架,一个zstack-woodpecker中的测试套件是以suite setup开始,以suite teardown结束,在其中有一些测试用例。这里的suite setup和suite teardown是两个特殊的测试用例,suite setup负责准备后续的测试用例所需的环境,suite teardown负责在所有测试用例结束之后清理这个环境。一个典型的测试套件配置文件看起来像:

<integrationTest>
    <suite name="basic test" setupCase="suite_setup.py" teardownCase="suite_teardown.py" parallel="8">
        <case timeout="120" repeat="10">test_create_vm.py</case>
        <case timeout="220">test_reboot_vm.py</case>
        <case timeout="200">test_add_volume.py</case>
        <case timeout="200">test_add_volume_reboot_vm.py</case>
        <case timeout="400">test_add_multi_volumes.py</case>
        <case timeout='600' repeat='2' noparallel='True'>resource/test_delete_l2.py</case>
    </suite>
</integrationTest>

敏锐的读者可能会注意到一些参数是在其他的测试框架中看不到的。

第一个是timeout;每一个测试用例可以定义自己的超时时间,如果在这段时间内不能完成,它将被在最终的结果里被标记成超时。

第二个是repeat,允许你在测试套件中指定这个用例应该被执行多少次。

第三个,也是杀手级的参数是parallel,允许测试人员设定这个套件的并行级别;这是一个使得zstack-woodpecker运行测试用例非常快的关键特性;在上面这个例子中,parallel被设置成8,这意味着将有至多8个用例在同时运行;这不只是加速运行测试用例,也创造了一个复杂的场景,模拟许多用户在共享同一个环境时执行不同的任务。然而,不是所有的用例都可以被同时执行;在我们的例子中,用例test_delete_l2.py将会删除被其他用例依赖的L2网络,所以在其他用例执行时,它不能被执行;这就是第四个参数noparallel发挥作用的地方;一旦它被设置成true,这个用例将会单独被执行,不会有其他用例可以同时运行。

命令行工具

zstest.py是一个命令行工具,用于帮助测试人员控制测试框架,执行任务,像启动测试套件,列出测试用例,等等。zstest.py提供了丰富的选项帮助测试人员简化他们的工作。这些选项中的一些,用于在我们的日常测试中,特别有用,列在了下面。 测试人员可以通过选项-l获取可用的测试用例,例如:

./zstest.py -l

它将会展示如下的结果:

测试套件名,是测试用例的第一级文件夹的名称;例如,在上图中你看到了大量的用例以basic开头(例如:basic/test_reboot_vm.py),是的,basic就是这个测试套件的名字。测试人员可以通过选项-s启动一个套件,使用套件名的全称或者部分都行,只要它是独一无二的,例如:./zstest.py -s basic 或

./zstest.py -s ba

测试人员也可以选择性地执行测试用例,通过使用它们的名字或者ID,已经选项-c;例如:

./zstest.py -c 1,6
或 ./zstest.py -c suite_setup,test_add_volume.py

记住,你需要运行suite setup的用例:suite_setup.py作为第一个用例,除非你已经这么做了。 由于一个测试套件将会执行所有的测试用例,清理环境,发出一个结果报告,测试人员有时可能想要停止测试套件,并在一个用例失败时保持环境,这样他们就可以深入查看失败结果并调试;选项-n和-S就是为此准备的;-n指示测试框架不要清理环境,-S要求跳过没有被执行的用例;例如:

./zstest.py -s
virtualrouter -n -S

另外,选项-b可以拉取最新的源代码并构建一个全新的zstack.war,这在Nightly测试中特别有用,这种测试被假定为测试最新的代码:

./zstest.py -s virtualrouter -b

一旦所有的测试用例完成,一个报告将会被生成并被打印到屏幕上:

测试框架将会保存所有的日志,并直接输出每一个失败日志的绝对路径,如果存在的话。为了在一般的日志中记录更多的细节,有一种特殊的日志action log,用于记录每一个API调用;因为这是一个完全纯粹关于API的日志,我们可以容易地找到一个失败的根本来源,而不用被测试框架的日志分散注意力。另外,它是一种重要的工具,可以自动地生成一个新的用例用于重现失败,这是一个我们所使用的魔法武器,用于在基于模型的测试(每个用例都随机地执行各种API)中调试失败。你可以在ZStack--自动化测试系统3:基于模型的测试中找到细节。Action log的片段如下:

#环境部署工具 类似于集成测试,对每一个测试用例来说,准备环境是频繁且重复的任务;例如,用于测试创建虚拟机的用例需要去配置独立的资源,像zone,cluster,host等等。Zstack-woodpecker调用zstack-cli,这个ZStack的命令行工具去从一个XML配置文件部署测试环境。例如:zstack-cli -d zstack-env.xml 这里的XML配置文件的格式类似于集成测试所用的,一个片段看起来像这样:

...
  <zones>
    <zone name="$zoneName" description="Test">
      <clusters>
        <cluster name="$clusterName" description="Test"
          hypervisorType="$clusterHypervisorType">
          <hosts>
            <host name="$hostName" description="Test" managementIp="$hostIp"
              username="$hostUsername" password="$hostPassword" />
          </hosts>
          <primaryStorageRef>$nfsPrimaryStorageName</primaryStorageRef>
          <l2NetworkRef>$l2PublicNetworkName</l2NetworkRef>
          <l2NetworkRef>$l2ManagementNetworkName</l2NetworkRef>
          <l2NetworkRef>$l2NoVlanNetworkName1</l2NetworkRef>
          <l2NetworkRef>$l2NoVlanNetworkName2</l2NetworkRef>
          <l2NetworkRef>$l2VlanNetworkName1</l2NetworkRef>
          <l2NetworkRef>$l2VlanNetworkName2</l2NetworkRef>
        </cluster>
      </clusters>
...
      <l2Networks>
        <l2VlanNetwork name="$l2VlanNetworkName1" description="guest l2 vlan network"
          physicalInterface="$l2NetworkPhysicalInterface" vlan="$l2Vlan1">
          <l3Networks>
            <l3BasicNetwork name="$l3VlanNetworkName1" description = "guest test vlan network with DHCP DNS SNAT PortForwarding EIP and SecurityGroup" domain_name="$L3VlanNetworkDomainName1">
              <ipRange name="$vlanIpRangeName1" startIp="$vlanIpRangeStart1" endIp="$vlanIpRangeEnd1"
               gateway="$vlanIpRangeGateway1" netmask="$vlanIpRangeNetmask1"/>
              <dns>$DNSServer</dns>
              <networkService provider="VirtualRouter">
                <serviceType>DHCP</serviceType>
                <serviceType>DNS</serviceType>
                <serviceType>SNAT</serviceType>
                <serviceType>PortForwarding</serviceType>
                <serviceType>Eip</serviceType>
              </networkService>
              <networkService provider="SecurityGroup">
                <serviceType>SecurityGroup</serviceType>
              </networkService>
            </l3BasicNetwork>
          </l3Networks>
        </l2VlanNetwork>
...

部署工具通常在运行任何用例前被suite setup调用,测试人员可以在XML配置文件中通过以$符号开头来定义变量,然后在一个独立的配置文件中解析。通过这种方式,这个XML配置文件像模板一个工作,可以产生不同的环境。配置文件的例子如下:

TEST_ROOT=/usr/local/zstack/root/
zstackPath = $TEST_ROOT/sanitytest/zstack.war
apachePath = $TEST_ROOT/apache-tomcat
zstackPropertiesPath = $TEST_ROOT/sanitytest/conf/zstack.properties
zstackTestAgentPkgPath = $TEST_ROOT/sanitytest/zstacktestagent.tar.gz
masterName = 192.168.0.201
DBUserName = root

node2Name = centos5
node2Ip = 192.168.0.209
node2UserName = root
node2Password = password

node1Name = 192.168.0.201
node1Ip = 192.168.0.201
node1UserName = root
node1Password = password


instanceOfferingName_s = small-vm
instanceOfferingMemory_s = 128M
instanceOfferingCpuNum_s = 1
instanceOfferingCpuSpeed_s = 512

virtualRouterOfferingName_s = virtual-router-vm
virtualRouterOfferingMemory_s = 512M
virtualRouterOfferingCpuNum_s = 2
virtualRouterOfferingCpuSpeed_s = 512

sftpBackupStorageName = sftp
sftpBackupStorageUrl = /export/backupStorage/sftp/
sftpBackupStorageUsername = root
sftpBackupStoragePassword = password
sftpBackupStorageHostname = 192.168.0.220

注意:正如你可能会猜测的,这个工具可以被管理员用于从一个XML配置文件部署一个云环境;更进一步,管理员们做相反的事情,将一个云环境写入到一个XML配置文件,通过zstack-cli -D xml-file-name.

对于性能和压力测试,环境通常需要大量的资源,例如100个zone,1000个cluster。为了避免手动在配置文件中重复1000行,我们引入了一个属性duplication,用于帮助创建重复的资源。例如:

...
    <zones>
      <zone name="$zoneName" description="10 same zones" duplication="100">
        <clusters>
          <cluster name="$clusterName_sim" description="10 same Simulator Clusters" duplication="10"
            hypervisorType="$clusterSimHypervisorType">
            <hosts>
              <host name="$hostName_sim" description="100 same simulator Test Host"
                managementIp="$hostIp_sim"
                cpuCapacity="$cpuCapacity" memoryCapacity="$memoryCapacity"
                duplication="100"/>
            </hosts>
            <primaryStorageRef>$simulatorPrimaryStorageName</primaryStorageRef>
            <l2NetworkRef>$l2PublicNetworkName</l2NetworkRef>
            <l2NetworkRef>$l2ManagementNetworkName</l2NetworkRef>
            <l2NetworkRef>$l2VlanNetworkName1</l2NetworkRef>
          </cluster>
...

备注:这段不翻译了。

模块化的测试用例

在系统测试中测试用例可以被高度模块化。每一个用例本质上执行以下三步:

  1. 创建要被测试的资源
  2. 验证结果
  3. 清理环境

Zstack-woodpecker 本身提供一个完整的库用于帮助测试人员调度这些活动。API也很好地被封装在一个,从zstack源代码自动生成的库中。测试人员不需要去写任何的原生的API调用。检查器,用于验证测试结果,也已为每一个资源创建;例如,VM检查器,云盘检查器。测试人员可以很容易地调用这些检查器去验证他们创建的资源,而不需写成顿成吨的代码。如果当前检查器不能满足某些场景,测试人员也能创建自己的检查器,并作为插件放入测试框架。 一段测试用例看起来像:

def test():
    test_util.test_dsc('Create test vm and check')
    vm = test_stub.create_vlan_vm()
    test_util.test_dsc('Create volume and check')
    disk_offering = test_lib.lib_get_disk_offering_by_name(os.environ.get('rootDiskOfferingName'))
    volume_creation_option = test_util.VolumeOption()
    volume_creation_option.set_disk_offering_uuid(disk_offering.uuid)
    volume = test_stub.create_volume(volume_creation_option)
    volume.check()
    vm.check()
    test_util.test_dsc('Attach volume and check')
    volume.attach(vm)
    volume.check()
    test_util.test_dsc('Detach volume and check')
    volume.detach()
    volume.check()
    test_util.test_dsc('Delete volume and check')
    volume.delete()
    volume.check()
    vm.destroy()
    vm.check()
    test_util.test_pass('Create Data Volume for VM Test Success')

像集成测试一样,测试人员可以仅以十几行便写出一个测试用例。模块化不只是帮助简化测试用例的编写,也为基于模型的测试构建了一个坚实的基础,下篇文章我们会详细讨论。

总结

在这篇文章中,我们引入了我们的系统测试系统。通过执行比现实世界的用例更复杂的测试,系统测试可以给我们更多的自信,关于ZStack在真实的硬件环境中的表现。使得我们可以快速进化成一个成熟的产品。