簡單聊聊TestNG中的並發


前言

最近在做項目里的自動化測試工作,使用的是TestNG測試框架,主要涉及的測試類型有接口測試以及基於業務實際場景的場景化測試。由於涉及的場景大多都是大數據的作業開發及執行(如MapReduce、Spark、Hql等任務的執行),而這些任務的執行都需要耗費較多的時間。舉一個普遍的例子,其中一條場景測試用例是:

  • 執行一個MapReduce作業,校驗作業的執行結果和執行日志。

對於一個最簡單的MR任務,如果YARN集群資源充足,它的執行時間也要花上將近一分鍾的時間。更不用說當YARN集群計算資源飽和時,任務還需要持續等待資源分配等。當測試回歸用例集里包含了大量此類的用例時,如果還用傳統的單線程執行方式,則一次自動化回歸將會耗費大量的時間。

多線程並行執行

基於上述場景,我們可以考慮將自動化用例中相互之間沒有耦合關系,相對獨立的用例進行並行執行。如,我可以通過起不同的線程同時去執行不同的MR任務、Spark任務,每個線程各自負責跟蹤任務的執行情況。

此外,即使是單純的接口自動化測試,如果測試集里包含了大量的用例時,我們也可以借助於TestNG的多線程方式提高執行速度。

必須要指出的是,通過多線程執行用例時雖然可以大大提升用例的執行效率,但是我們在設計用例時也要考慮到這些用例是否適合並發執行,以及要注意多線程方式的通病:線程安全與共享變量的問題。建議是在測試代碼中,盡可能地避免使用共享變量。如果真的用到了,要慎用synchronized關鍵字來對共享變量進行加鎖同步。否則,難免你的用例執行時可能會出現不穩定的情景(經常聽到有人提到用例執行地不穩定,有時100%通過,有時只有90%通過,猜測可能有一部分原因也是這個導致的)。

TestNG中的多線程使用姿勢

不同級別的並發

通常,在TestNG的執行中,測試的級別由上至下可以分為suite -> test -> class -> method,箭頭的左邊元素跟右邊元素的關系是一對多的包含關系。

這里的test指的是testng.xml中的test tag,而不是測試類里的一個 @Test。測試類里的一個 @Test實際上對應這里的method。所以我們在使用 @BeforeSuite、 @BeforeTest、 @BeforeClass、 @BeforeMethod這些標簽的時候,它們的實際執行順序也是按照這個級別來的。

suite

一般情況下,一個testng.xml只包含一個suite。如果想起多個線程執行不同的suite,官方給出的方法是:通過命令行的方式來指定線程池的容量。

java org.testng.TestNG -suitethreadpoolsize 3 testng1.xml testng2.xml testng3.xml

即可通過三個線程來分別執行testng1.xml、testng2.xml、testng3.xml。
實際上這種情況在實際中應用地並不多見,我們的測試用例往往放在一個suite中,如果真需要執行不同的suite,往往也是在不同的環境中去執行,屆時也自然而然會做一些其他的配置(如環境變量)更改,會有不同的進程去執行。因此這種方式不多贅述。

test, class, method

test,class,method級別的並發,可以通過在testng.xml中的suite tag下設置,如:

<suite name="Testng Parallel Test" parallel="tests" thread-count="5">
<suite name="Testng Parallel Test" parallel="classes" thread-count="5">
<suite name="Testng Parallel Test" parallel="methods" thread-count="5">

它們的共同點都是最多起5個線程去同時執行不同的用例。
它們的區別如下:

  • tests級別:不同test tag下的用例可以在不同的線程執行,相同test tag下的用例只能在同一個線程中執行。
  • classs級別:不同class tag下的用例可以在不同的線程執行,相同class tag下的用例只能在同一個線程中執行。
  • methods級別:所有用例都可以在不同的線程去執行。

搞清楚並發的級別非常重要,可以幫我們合理地組織用例,比如將非線程安全的測試類或group統一放到一個test中,這樣在並發的同時又可以保證這些類里的用例是單線程執行。也可以根據需要設定class級別的並發,讓同一個測試類里的用例在同一個線程中執行。

並發時的依賴

實踐中,很多時候我們在測試類中通過dependOnMethods/dependOnGroups方式,給很多測試方法的執行添加了依賴,以達到期望的執行順序。如果同時在運行testng時配置了methods級別並發執行,那么這些測試方法在不同線程中執行,還會遵循依賴的執行順序嗎?答案是——YES。牛逼的TestNG就是能在多線程情況下依然遵循既定的用例執行順序去執行。

不同dataprovider的並發

在使用TestNG做自動化測試時,基本上大家都會使用dataprovider來管理一個用例的不同測試數據。而上述在testng.xml中修改suite標簽的方法,並不適用於dataprovider多組測試數據之間的並發。執行時會發現,一個dp中的多組數據依然是順序執行。

解決方式是:在 @DataProvider中添加parallel=true。
如:


import org.testng.annotations.DataProvider;
import testdata.ScenarioTestData;


public class ScenarioDataProvider {
	@DataProvider(name = "hadoopTest", parallel=true)
	public static Object [][] hadoopTest(){
		return new Object[][]{
			ScenarioTestData.hadoopMain,
			ScenarioTestData.hadoopRun,
			ScenarioTestData.hadoopDeliverProps
		};
	}
	
	@DataProvider(name = "sparkTest", parallel=true)
	public static Object [][] sparkTest(){
		return new Object[][]{
			ScenarioTestData.spark_java_version_default,
			ScenarioTestData.spark_java_version_162,
			ScenarioTestData.spark_java_version_200,
			ScenarioTestData.spark_python
		};
	}
	
	@DataProvider(name = "sqoopTest", parallel=true)
	public static Object [][] sqoopTest(){
		return new Object[][]{
			ScenarioTestData.sqoop_mysql2hive,
			ScenarioTestData.sqoop_mysql2hdfs
		};
	}
}

默認情況下,dp並行執行的線程池容量為10,如果要更改並發的數量,也可以在suite tag下指定參數data-provider-thread-count:

<suite name="Testng Parallel Test" parallel="methods" thread-count="5" data-provider-thread-count="20" >

同一個方法的並發

有些時候,我們需要對一個測試用例,比如一個http接口,執行並發測試,即一個接口的反復調用。TestNG中也提供了優雅的支持方式,在 @Test標簽中指定threadPoolSize和invocationCount。

@Test(enabled=true, dataProvider="testdp", threadPoolSize=5, invocationCount=10)

其中threadPoolSize表明用於調用該方法的線程池容量,該例就是同時起5個線程並行執行該方法;invocationCount表示該方法總計需要被執行的次數。該例子中5個線程同時執行,當總計執行次數達到10次時,停止。

注意,該線程池與dp的並發線程池是兩個獨立的線程池。這里的線程池是用於起多個method,而每個method的測試數據由dp提供,如果這邊dp里有3組數據,那么實際上10次執行,每次都會調3次接口,這個接口被調用的總次數是10*3=30次。threadPoolSize指定的5個線程中,每個線程單獨去調method時,用到的dp如果也是支持並發執行的話,會創建一個新的線程池(dpThreadPool)來並發執行測試數據。

示例代碼如下:

package testng.parallel.test;

import java.text.SimpleDateFormat;
import java.util.Date;

import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;


public class TestClass1 {
	private SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//設置日期格式
	@BeforeClass
	public void beforeClass(){
		System.out.println("Start Time: " + df.format(new Date()));
	}
		
	@Test(enabled=true, dataProvider="testdp", threadPoolSize=2, invocationCount=5)
	public void test(String dpNumber) throws InterruptedException{
		System.out.println("Current Thread Id: " + Thread.currentThread().getId() + ". Dataprovider number: "+ dpNumber);
		Thread.sleep(5000);
	}

	@DataProvider(name = "testdp", parallel = true)
	public static Object[][]testdp(){
		return new Object[][]{
			{"1"},
			{"2"}
		};
	}
	
	@AfterClass
	public void afterClass(){
		System.out.println("End Time: " + df.format(new Date()));
	}
}

測試結果:

Start Time: 2017-03-11 14:10:43
[ThreadUtil] Starting executor timeOut:0ms workers:5 threadPoolSize:2
Current Thread Id: 14. Dataprovider number: 2
Current Thread Id: 15. Dataprovider number: 2
Current Thread Id: 12. Dataprovider number: 1
Current Thread Id: 13. Dataprovider number: 1
Current Thread Id: 16. Dataprovider number: 1
Current Thread Id: 18. Dataprovider number: 1
Current Thread Id: 17. Dataprovider number: 2
Current Thread Id: 19. Dataprovider number: 2
Current Thread Id: 21. Dataprovider number: 2
Current Thread Id: 20. Dataprovider number: 1
End Time: 2017-03-11 14:10:58

Other TestNG Tips

TestNG作為一個成熟的、業界廣泛使用的測試框架,自然有其存在的合理性。這邊再分享一些簡單有用的標簽,具體的使用姿勢大家可以自己去探索,官網有比較全的介紹,畢竟自己探索的才會印象深刻。

  1. groups/dependsOnGroups/dependsOnMethods ——設置用例間依賴
  2. dataProviderClass ——將dataprovider單獨放到一個專用的類中,實現測試代碼、dataprovider、測試數據分層。
  3. timeout ——設置用例的超時時間(並發/非並發都可支持)
  4. alwaysRun ——某些依賴的用例失敗了,導致用例被跳過。對於一些為了保持環境干凈而“掃尾”的測試類,如果我們想強制執行可以使用此標簽。
  5. priority ——設置優先級,讓某些測試用例被更大概率優先執行。
  6. singleThreaded ——強制一個class類里的用例在一個線程執行,忽視method級別並發
  7. preserve-order ——指定是否按照testng.xml中的既定用例順序執行用例

總結

在TestNG中使用多線程的方式並行執行測試用例可以有效提高用例的執行速度,而且TestNG對多線程提供了很好的支持,即使是菜鳥也可以方便地上手多線程。此外,TestNG默認會使用線程池的方式創建線程,減小了程序的開銷。

參考鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM