測試,對於一個IaaS軟件的可靠性、成熟度和可維護性而言,是一個重要的因素.測試在ZStack中是全自動的。這個自動化測試系統包括了三個部分:集成測試,系統測試,基於模塊的測試。其中集成測試構建於Junit之上,使用了模擬器。經過這個集成測試系統提供的各類各樣的功能,開發人員能夠快速的寫出測試用例,用於驗證一個新特性或者一個缺陷修復。後端
概述api
這個關鍵因素,在構建一個可靠的、成熟的和可維護的軟件產品中,就是架構;這是咱們自始自終相信的設計原則。ZStack已經付出了大量的努力,以設計這麼一個架構:始終保持軟件穩定,不管是添加新特性,常規的操做錯誤,仍是爲特殊目的裁剪;咱們以前的文章:ZStack—進程內微服務架構、ZStack—通用插件系統、ZStack—工做流引擎、ZStack—標籤系統,已經表現了咱們的一些嘗試。然而,咱們也充分理解測試在軟件開發中的重要性。ZStack,從第一天開始,設定了這麼一個目標:每個特性都必須有測試用例保證,測試必須是所有自動化的,寫單元測試應該是驗證一個新特性或任何代碼改變的惟一方式。服務器
爲了實現這個目標,咱們把咱們的測試系統分紅了三個組件:集成測試,系統測試,模塊測試。分類方式是經過它們的關注點和功能。網絡
從這篇文章開始,咱們將會有一系列的,共計三篇文章,來詳細闡述咱們的測試架構,以向你展現咱們保證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;
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
用例也能夠在一個組裏被執行,例如:
[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行的代碼來完成大多數的用例,這是很是容易和有效率的。