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行的代碼來完成大多數的用例,這是很是容易和有效率的。

相關文章
相關標籤/搜索