ZStack的系統(tǒng)測試系統(tǒng)在真實的硬件環(huán)境中運行測試用例;像集成測試一樣,這個系統(tǒng)測試也是全自動的,而且覆蓋的層面包括:功能性測試、壓力測試、性能測試。
概述
雖然集成測試系統(tǒng),如我們在ZStack—自動化測試系統(tǒng)1:集成測試中所介紹的,強大到可以暴露開發(fā)過程中大多數(shù)的缺陷,也是有著固有的弱點的。首先,由于測試用例使用模擬器,它們不能測試真實場景,比如在一個物理的KVM主機上創(chuàng)建一個VM。第二,集成測試用例主要關(guān)注一個簡單的場景,在一個簡單的人造的環(huán)境中;舉個例子,還是創(chuàng)建VM的這個用例,它可能只部署一個最小的環(huán)境,包括一個主機和一個L3網(wǎng)絡(luò),僅僅用于滿足創(chuàng)建一個VM的需求。這些弱點,然而也是深思熟慮過的,因為我們想要開發(fā)人員能夠在他們開發(fā)新特性時快速和容易地寫測試用例,這是一個我們必須采取的權(quán)衡。
系統(tǒng)測試,目標(biāo)在于測試整個軟件,在一個真實的、復(fù)雜的環(huán)境中,很自然地補充集成測試。ZStack的系統(tǒng)測試系統(tǒng)被設(shè)計用于以下兩個目標(biāo):
- 復(fù)雜的場景:這些場景應(yīng)該比真實世界的使用場景更復(fù)雜,以測試軟件的極限。舉個例子,掛載和卸載磁盤的測試用例應(yīng)該持續(xù)地、重復(fù)地對虛擬機執(zhí)行,以一種非???,人類無法手動做到的方式。
- 易于編寫和維護(hù)測試用例:就像集成測試系統(tǒng),系統(tǒng)測試系統(tǒng)接管了大多數(shù)無聊重復(fù)的任務(wù),讓測試人員有效率地寫測試用例。
這個系統(tǒng)測試系統(tǒng)是一個Python項目,命名為zstack-woodpecker,由以下三個部分組成:
- 測試框架:一個測試框架,管理所有的測試用例,以及提供必須的庫和工具。
- 環(huán)境部署工具:一個工具,用于從XML配置文件部署一個環(huán)境;它非常類似于集成測試系統(tǒng)的部署器。
- 模塊化測試用例:測試用例是高度模塊化的,而且覆蓋了:功能測試、性能測試和壓力測試。
系統(tǒng)測試

zstack-woodpecker完全由我們自己創(chuàng)建;在決定重新造這個輪子之前,我們試過了流行的Python測試框架,像nose,然后最終選擇了創(chuàng)造一個新的工具,用以最大化地滿足我們的目標(biāo)。
套件配置
類似所有的其他測試框架,一個zstack-woodpecker中的測試套件是以suite setup開始,以suite teardown結(jié)束,在其中有一些測試用例。這里的suite setup和suite teardown是兩個特殊的測試用例,suite setup負(fù)責(zé)準(zhǔn)備后續(xù)的測試用例所需的環(huán)境,suite teardown負(fù)責(zé)在所有測試用例結(jié)束之后清理這個環(huán)境。一個典型的測試套件配置文件看起來像:
<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>
敏銳的讀者可能會注意到一些參數(shù)是在其他的測試框架中看不到的。
第一個是timeout;每一個測試用例可以定義自己的超時時間,如果在這段時間內(nèi)不能完成,它將被在最終的結(jié)果里被標(biāo)記成超時。
第二個是repeat,允許你在測試套件中指定這個用例應(yīng)該被執(zhí)行多少次。
第三個,也是殺手級的參數(shù)是parallel,允許測試人員設(shè)定這個套件的并行級別;這是一個使得zstack-woodpecker運行測試用例非常快的關(guān)鍵特性;在上面這個例子中,parallel被設(shè)置成8,這意味著將有至多8個用例在同時運行;這不只是加速運行測試用例,也創(chuàng)造了一個復(fù)雜的場景,模擬許多用戶在共享同一個環(huán)境時執(zhí)行不同的任務(wù)。然而,不是所有的用例都可以被同時執(zhí)行;在我們的例子中,用例test_delete_l2.py將會刪除被其他用例依賴的L2網(wǎng)絡(luò),所以在其他用例執(zhí)行時,它不能被執(zhí)行;這就是第四個參數(shù)noparallel發(fā)揮作用的地方;一旦它被設(shè)置成true,這個用例將會單獨被執(zhí)行,不會有其他用例可以同時運行。
命令行工具
zstest.py是一個命令行工具,用于幫助測試人員控制測試框架,執(zhí)行任務(wù),像啟動測試套件,列出測試用例,等等。zstest.py提供了豐富的選項幫助測試人員簡化他們的工作。這些選項中的一些,用于在我們的日常測試中,特別有用,列在了下面。
測試人員可以通過選項-l獲取可用的測試用例,例如:
./zstest.py -l
它將會展示如下的結(jié)果:

測試套件名,是測試用例的第一級文件夾的名稱;例如,在上圖中你看到了大量的用例以basic開頭(例如:basic/test_reboot_vm.py),是的,basic就是這個測試套件的名字。測試人員可以通過選項-s啟動一個套件,使用套件名的全稱或者部分都行,只要它是獨一無二的,例如:./zstest.py -s basic 或
./zstest.py -s ba

測試人員也可以選擇性地執(zhí)行測試用例,通過使用它們的名字或者ID,已經(jīng)選項-c;例如:
./zstest.py -c 1,6
或 ./zstest.py -c suite_setup,test_add_volume.py
記住,你需要運行suite setup的用例:suite_setup.py作為第一個用例,除非你已經(jīng)這么做了。
由于一個測試套件將會執(zhí)行所有的測試用例,清理環(huán)境,發(fā)出一個結(jié)果報告,測試人員有時可能想要停止測試套件,并在一個用例失敗時保持環(huán)境,這樣他們就可以深入查看失敗結(jié)果并調(diào)試;選項-n和-S就是為此準(zhǔn)備的;-n指示測試框架不要清理環(huán)境,-S要求跳過沒有被執(zhí)行的用例;例如:
./zstest.py -s
virtualrouter -n -S
另外,選項-b可以拉取最新的源代碼并構(gòu)建一個全新的zstack.war,這在Nightly測試中特別有用,這種測試被假定為測試最新的代碼:
./zstest.py -s virtualrouter -b
一旦所有的測試用例完成,一個報告將會被生成并被打印到屏幕上:

測試框架將會保存所有的日志,并直接輸出每一個失敗日志的絕對路徑,如果存在的話。為了在一般的日志中記錄更多的細(xì)節(jié),有一種特殊的日志action log,用于記錄每一個API調(diào)用;因為這是一個完全純粹關(guān)于API的日志,我們可以容易地找到一個失敗的根本來源,而不用被測試框架的日志分散注意力。另外,它是一種重要的工具,可以自動地生成一個新的用例用于重現(xiàn)失敗,這是一個我們所使用的魔法武器,用于在基于模型的測試(每個用例都隨機地執(zhí)行各種API)中調(diào)試失敗。你可以在ZStack--自動化測試系統(tǒng)3:基于模型的測試中找到細(xì)節(jié)。Action log的片段如下:

環(huán)境部署工具
類似于集成測試,對每一個測試用例來說,準(zhǔn)備環(huán)境是頻繁且重復(fù)的任務(wù);例如,用于測試創(chuàng)建虛擬機的用例需要去配置獨立的資源,像zone,cluster,host等等。Zstack-woodpecker調(diào)用zstack-cli,這個ZStack的命令行工具去從一個XML配置文件部署測試環(huán)境。例如: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調(diào)用,測試人員可以在XML配置文件中通過以$符號開頭來定義變量,然后在一個獨立的配置文件中解析。通過這種方式,這個XML配置文件像模板一個工作,可以產(chǎn)生不同的環(huán)境。配置文件的例子如下:
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配置文件部署一個云環(huán)境;更進(jìn)一步,管理員們做相反的事情,將一個云環(huán)境寫入到一個XML配置文件,通過zstack-cli -D xml-file-name.
對于性能和壓力測試,環(huán)境通常需要大量的資源,例如100個zone,1000個cluster。為了避免手動在配置文件中重復(fù)1000行,我們引入了一個屬性duplication,用于幫助創(chuàng)建重復(fù)的資源。例如:
...
<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>
...
備注:這段不翻譯了。
模塊化的測試用例
在系統(tǒng)測試中測試用例可以被高度模塊化。每一個用例本質(zhì)上執(zhí)行以下三步:
- 創(chuàng)建要被測試的資源
- 驗證結(jié)果
- 清理環(huán)境
Zstack-woodpecker 本身提供一個完整的庫用于幫助測試人員調(diào)度這些活動。API也很好地被封裝在一個,從zstack源代碼自動生成的庫中。測試人員不需要去寫任何的原生的API調(diào)用。檢查器,用于驗證測試結(jié)果,也已為每一個資源創(chuàng)建;例如,VM檢查器,云盤檢查器。測試人員可以很容易地調(diào)用這些檢查器去驗證他們創(chuàng)建的資源,而不需寫成頓成噸的代碼。如果當(dāng)前檢查器不能滿足某些場景,測試人員也能創(chuàng)建自己的檢查器,并作為插件放入測試框架。
一段測試用例看起來像:
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')
像集成測試一樣,測試人員可以僅以十幾行便寫出一個測試用例。模塊化不只是幫助簡化測試用例的編寫,也為基于模型的測試構(gòu)建了一個堅實的基礎(chǔ),下篇文章我們會詳細(xì)討論。
總結(jié)
在這篇文章中,我們引入了我們的系統(tǒng)測試系統(tǒng)。通過執(zhí)行比現(xiàn)實世界的用例更復(fù)雜的測試,系統(tǒng)測試可以給我們更多的自信,關(guān)于ZStack在真實的硬件環(huán)境中的表現(xiàn)。使得我們可以快速進(jìn)化成一個成熟的產(chǎn)品。