簡單重構,顯著效果----提升scala自動化測試效率
如何提升Scala 測試效率
一 為何提高?
敏捷對於單元測試和集成測試的自動化運行效率要求很高,期望在短時間裡完成代碼提交後的用例運行。常常會存在這種情況,代碼運行時間很短,但是自動化用例耗時很長,一個用例執行可能不到一秒鐘,但是在自動化中可能要超過好幾秒,整個自動化效率低下,這是自動化測試中常見的場景。
本文結合項目實際,談談如何提高測試效率。
先看下我們的數據:
190個用例,CI在性能好的機器上運行花費時間8分半(512s),而在同台機器上scala統計只有26s,差距巨大。在性能不好的機器上運行CI測試需要20分鐘,這效率無法滿足敏捷開發。為了提升效率,通過大量的分析和代碼重構,使得最終的效率提升將近11倍,以前8分半左右的CI用例執行時間,重構後不到1分鐘即可運行完畢。
如何提高?
軟體上,提升測試效率可以從以下幾方面進行考慮
- 測試系統啟動效率
- 用例組合運行效率
- 單步驟用例執行效率
- 並行運行用例
下面重點講述如何重構測試用例代碼來提升效率
為了減少文章的篇幅,下文代碼以一個簡單的測試類來說明,相關的運行時長也是簡單類的運行時長,在實際項目中,各步驟的運行時長比例子更長。如創表操作平均在2.5s
- 測試系統啟動效率
測試系統或被測對象有時需要啟動一些所需的伺服器,此時,可以採取mock的方式,或者整體啟動一次(見下)。
- 用例組合運行效率
整體用例運行效率和用例的編寫有很大關係,例如,目前的用例大多如下編寫,
單個測試class實現方式:class
UsergroupActorTest
extends
FunSpec
with
Matchers
with
BeforeAndAfterEach
with
StoragePrepare
with
TablesDBServicePrepare{
override
protected
def beforeEach():
Unit
=
{
super.beforeEach()
UsergroupActor.actorOf()
}
describe("UsergroupActorTest")
{
it("handleSubSystemInfoGetRecord"){
val request:
String
=
TestData.subSystemInfopostrecord1
var result =
UsergroupActor.handleSubSystemInfoPostRecord(request)
result.status should be(ResponseStatus(200,
"OK"))
result =
UsergroupActor.handleSubSystemInfoGetRecord()
}
it("handleGetSubSystemInfoByGroupname"){
val subsysRecord =
Array[String]("IOT","app","IoT","IOT APP","http://10.63.246.83:2016","true")
SubSystemInfoDBService().insertRecord(subsysRecord) should be(true)
val userGroupRecord =
Array[String]("0","individual","[{"subsystem":"IOT","rolename":["individual"]}]")
UserGroupRolesDBService().insertRecord(userGroupRecord) should be(true)
val result =
UsergroupActor.handleGetSubSystemInfoByGroupname("individual")
result.status should be(ResponseStatus(200,
"OK"))
}
}
其中:
StoragePrepare :每個it運行前連接資料庫,運行後如果表存在則整個刪除,同時記錄也被刪除
trait StoragePrepare
extends
ScalatraSuite
with
BeforeAndAfterEach
{
private val dbName =
"uTest"
override
protected
def beforeEach():
Unit
=
{
super.beforeEach()
DBService.connect("localhost",
"root",
"",
"3306", dbName)
}
override
protected
def afterEach():
Unit
=
{
super.afterEach()
DBService.execute(SQL(s"drop database if exists $dbName"))
}
}
TablesDBServicePrepare:數據準備,包括創建數據表,以及插入基礎表數據
trait TablesDBServicePrepare
extends
ScalatraSuite
with
BeforeAndAfterEach
{
override
protected
def beforeEach():
Unit
=
{
super.beforeEach()
UserGroupsDBService().createTable()
UserGroupsDBService().insertRecord(Array[String]("0","individual"))
UsersDBService().createTable()
UsersDBService().insertRecord(Array[String]("0","admin",
"admin123",
"test@test.com",
"18254565825","cop","shanghai","individual","1"))
UsersDBService().insertRecord(Array[String]("0","admin2",
"admin123",
"test@test.com",
"18254565825","cop","shanghai","individual","1"))
SubSystemInfoDBService().createTable()
SubSystemRolesDBService().createTable()
UserGroupRolesDBService().createTable()
}
}
整體運行過程如下,(以創5個簡單的表為例,數字為運行平均時長,單位ms)
問題在哪?
1 一個測試集下,測試前後的動作有通用部分,需要在suite間、suite內、it內進行提煉。
以上的測試方式,將創建表和插數據混在一起作為每個it的操作,導致it操作每次都進行啟動服務,連接資料庫、創表動作,降低了用例執行效率,同時在代碼結構上也不清晰。創建表應該是suite一次性動作,用beforeAll來實現。
2 it測試單獨測試和多個it測試串列測試時,滿足童子軍軍規,以減少相互影響。
上述運行採用里簡單粗暴的方式,it只能在表的層次上刪除整個表,無法採用清除記錄,其原因正是beforeEach中的表和記錄同時操作。
3 在領域層次方面的劃分比較混亂,如果多個it測試要求的數據完全相同,可以進行子suite的拆分。
改造後,連接伺服器、創表、表記錄初始化、刪除表都作為獨立trait,在suite和it層次上進行合理調用。執行流程如下
重構後代碼
class
UsergroupActorTest
extends
FunSpec
with
Matchers
with
BeforeAndAfterEach
with
StoragePrepareOnceJetty
with
TablesDBServiceCreateTableOnce
with
TablesDBServiceRecordPrepare{
//it先置條件
override
protected
def beforeEach():
Unit
=
{
super.beforeEach()
UsergroupActor.actorOf()
}
override
protected
def afterEach():
Unit
=
{
super.afterEach()
//clear the records which created in this test
UsersDBService().updateEx("delete from users")
UserGroupsDBService().updateEx("delete from usergroups")
SubSystemInfoDBService().updateEx("delete from subsysteminfo")
UserGroupRolesDBService().updateEx("delete from usergrouproles")
}
describe("UsergroupActorTest")
{
it("handleSubSystemInfoGetRecord"){
......
//test code here
}
it("handleGetSubSystemInfoByGroupname"){
......
//test code here
}
}
}
trait StoragePrepareOnce
extends
ScalatraSuite
with
BeforeAndAfterAll
{
private val dbName =
"uTest"
override
protected
def beforeAll():
Unit
=
{
super.beforeAll()
DBService.connect("localhost",
"root",
"",
"3306", dbName)
}
override
protected
def afterAll():
Unit
=
{
super.afterAll()
DBService.execute(SQL(s"drop database if exists $dbName"))
}
}
trait TablesDBServiceCreateTableOnce
extends
ScalatraSuite
with
BeforeAndAfterAll
{
override
protected
def beforeAll():
Unit
=
{
super.beforeAll()
UserGroupsDBService().createTable()
UsersDBService().createTable()
SubSystemInfoDBService().createTable()
SubSystemRolesDBService().createTable()
UserGroupRolesDBService().createTable()
}
}
trait TablesDBServiceRecordPrepare
extends
ScalatraSuite
with
BeforeAndAfterEach
{
override
protected
def beforeEach():
Unit
=
{
super.beforeEach()
UserGroupsDBService().insertRecord(Array[String]("0","individual"))
UsersDBService().insertRecord(Array[String]("0","admin",
"admin123",
"test@test.com",
"18254565825","cop","shanghai","individual","1"))
UsersDBService().insertRecord(Array[String]("0","admin2",
"admin123",
"test@test.com",
"18254565825","cop","shanghai","individual","1"))
}
}
這樣優化後,大量的減少了重複操作,單測試class的重構基本完成。
那麼,對於一個project,多個測試類怎麼處理呢?我們需要繼續優化下去。
同樣的思想,在不同文件(class suites)共用前置操作和後置操作。
通過Suites(new UsergroupActorTest, new UsersCfgActorTest)將幾個測試類整合到一個測試類中,有兩種實現,1 類
class
NestSuiteTest
extends
Suites(new
UsergroupActorTest,
new
UsersCfgActorTest)
with
BeforeAndAfterAll{
override
protected
def beforeAll():
Unit
=
{
super.beforeAll()
println("-------NestSuiteTest act firstly within whole project")
}
override
def afterAll()
{
super.afterAll()
println("write ur action here!")
//所有suites運行完後,一次性回收動作
}
}
@DoNotDiscover
class
UsergroupActorTest
extends
FunSpec
with
Matchers
with
BeforeAndAfterEach
with
StoragePrepareOnceJetty
with
TablesDBServiceCreateTableOnce
with
TablesDBServiceRecordPrepare{
……
}
我們來看看gradle運行結果:
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$1.UsergroupActorTest handleUserGroupPostRecord STANDARD_OUT
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$2.UsersCfgActorTest handleuserpostrecord STANDARD_OUT
ActorTest.usersManage.UsersCfgActorTest UsersCfgActorTest handleuserpostrecord STANDARD_OUTActorTest.usersManage.UsergroupActorTest UsergroupActorTest handleUserGroupPostRecord STANDARD_OUT
發現了嗎,UsergroupActorTest,UsersCfgActorTest都運行了兩遍。這下暈菜了,這個咋辦?
好吧,換種寫法抽象類
class
NestSuiteTest
extends
Suites(new
UsergroupActorTest{},
new
UsersCfgActorTest{})
with
BeforeAndAfterAll{
override
protected
def beforeAll():
Unit
=
{
super.beforeAll()
println("-------NestSuiteTest act firstly within whole project")
}
override
def afterAll()
{
super.afterAll()
println("write ur action here!")
//所有suites運行完後,一次性回收動作
}
}
@DoNotDiscover
abstract
class
UsergroupActorTest
extends
FunSpec
with
Matchers
with
BeforeAndAfterEach
with
StoragePrepareOnceJetty
with
TablesDBServiceCreateTableOnce
with
TablesDBServiceRecordPrepare{
……//抽象類
}
我們再來看看gradle運行結果:
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$1.UsergroupActorTest handleUserGroupPostRecord STANDARD_OUT
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$2.UsersCfgActorTest handleuserpostrecord STANDARD_OUT
執行結果顯示,只執行了一遍,但是,IDEA中單獨執行UsergroupActorTest也成了泡影,因為abstract class UsergroupActorTest,你懂的。
這也是條死路啊,由此牽扯出另外一個問題,如何執行指定測試類?
如何執行指定測試類
如果你知道,可以果斷跳過
先看看gradle的幫助,gradle help —task :test 查看任務test的幫助,
幫助會告訴 —tests Sets test class or method name to be included, 『*』 is supported.
網上一邊倒的說用這個參數可以運行單個測試類但是,但是,這個是個大餡餅啊
在非SBT方式安裝的scala下,這個寫法完全無效,依然是所有的suite玩命地運行,而碼農們只能苦苦等待。不靠譜的幫助。沒招,只好轉投IDEA,在IntelliJ IDEA中運行用例的方式(gradle沒配置junit),
- 如果用例編寫採用@RunWith(classOf[JUnitRunner]),則可以選擇在gradle、Junit或scalatest下運行,並在IntelliJ IDEAJunit下自動添加配置,後續默認以第一次選擇的方式執行,直到手工刪除該配置。如果選了gradle方式,杯具了,後面還是得所有用例全部執行。
- 如果用例編寫未指定JUnitRunner,IntelliJ IDEA默認調用scalatest運行。此情況下,命令行gradle test則無法運行該用例
- 如果在gradle配置下加入運行,實際效果就是全用例運行。
好吧,為了自動化,只能採用在IDEA運行單個suite,在集成CI中運行所有用例。
BUT,不死心啊,因為gradle下無法指定class運行,意味著NestSuite必須本身和子集都運行一遍,等於重複運行了一遍。搜啊搜,試啊試,蒼天不負有心人啊,最後,終於找到了 gradle下單獨運行測試類的方法,(關鍵先生來了)
gradle -Dtest.single=NestSuiteTest(指定測試類) test -i
雖然是個就命令,但是管用,真的管用,看著下面的運行結果,這才長舒口氣,忍不住要問候幾句,
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$1.UsergroupActorTest handleUserGroupPostRecord STANDARD_OUT
NestSuiteTest.NestSuiteTest NestSuiteTest.NestSuiteTest$$anon$2.UsersCfgActorTest handleuserpostrecord STANDARD_OUT
至此,我們測試用例重構大功告成。
- 單用例執行效率
這個和實際測試內容有關,八仙過海各顯神通,改進不具備借鑒性,略過。
- 並行運行測試用例
並行運行可通過CI將用例在不同機器分布運行,或者採用用例並行運行的方式,略
推薦閱讀:
※python selenium2自動化測試系列電子書
※在Selenium Webdriver中使用XPath Contains、Sibling函數定位
TAG:自動化測試 |