相信每個軟件從業(yè)者都意識到測試在項目中重要性栓袖∶蛊欤“愛恨交織”足以形容我們和測試用例關系。愛它為我們發(fā)現的巨坑,恨它則因為絞盡腦汁構建測試場景棺妓。
毋庸置疑的是:測試用例是項目管理不可或缺部分。為了更好的服務項目鲤遥,豐富而完整的測試用例是必要的斋日。
在編寫測試用例時,函數中調用的其它函數挠羔,或者引用外部資源井仰。在不使用資源的情況下,我們想做到更好的測試破加,需要模擬外部函數的結果俱恶。今天的主角ScalaMock是便于我們模擬外部結果測試組件。
ScalaMock介紹
ScalaMock和JUnit, NUnit*測試框架不同,它沒有提供底層的測試代碼合是,而是基于兩種測試框架之上(ScalaTest和Specs2)了罪。它類似于給測試框架提供構建“假”的代碼運行環(huán)境或者叫“樁”。為編寫測試用例提供便捷語法糖聪全,很甜很甜那種泊藕,哈哈。本文通過使用scalamock構造簡單的測試場景难礼,來展現其功能娃圆。
用例運行環(huán)境
一切沒有預置前提的說明文檔都必然是耍流氓,_
// Add the ScalaMock library (versions 4.0.0 onwards)
libraryDependencies += "org.scalamock" %% "scalamock" % "4.4.0" % Test
// also add ScalaTest as a framework to run the tests
//scala test inter scala mock sugar
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % Test</pre>
本文用例基于 scalmock 4.4.0和scalatest 3.0.8的版本蛾茉。
ScalaMock的是與非
ScalaMock 運行基于 scalatest讼呢,我們編寫的用例形式上要遵循scalatest的形式。從以下3個方面介紹scalamock.
- 匿名函數mock
- trait mock
- class mock
在介紹使用場景前谦炬,先看看scalamock語法樣式吝岭。
- ScalaMock語法樣式
//匿名函數格式: mockFunction,入參 string吧寺,int , 返回值String
val funcMock = mockFunction[String, Int, String]
// 格式 變量 expects()關鍵字+ returning關鍵字 + 返回值("wayne mock") + 附加屬性(once()只執(zhí)行一次)
funcMock expects("hello world", 18) returning "wayne mock" once() //once
//樣式含義是:當輸入期望特定的輸入時窜管,返回returning 值。
基于函數的Mock
函數mock關鍵字是:mockFunction
.
- 匿名函數mock
//定義函數mock
val funcMock = mockFunction[String, Int, String]
funcMock expects("hello world", 18) returning "function mock" atLeastOnce() //call at least once
?
//調用
println(funcMock("hello world", 18)) //輸出: function mock </pre>
atLeastOnce()
使用scalamock 時稚机,設置屬性表示mock的內容被調用的次數幕帆。
匿名函數mock使用頻率不高,更多使用trait
class
方式進行mock赖条。下文有匿名函數進行mock的使用場景失乾。
基于trait
的mock
trait
進行mock操作關鍵字是:mock
.
源碼如下:
trait MyMockTest {
def funcOnePara(name: String): String = { //一個參數
name
}
?
def funcTwoPara(name: String, age:Int): String = name //2個參數
def funcNoArgs: String = "func no args" //無參數函數
val valPara="variable param"
}
-
mock trait 示例
- mock
trait
無參數示例
- mock
val my = mock[MyMockTest]
(my.funcNoArgs _ ).expects().returning("no args") //mock funcNoArgs返回no args
?
my.funcNoArgs shouldBe("no args")//scala test 結果比較
定義了my
變量之后,就可以對trait
內所有的函數進行mock操作了纬乍。其它沒有mock的函數不影響碱茁。
trait TraitExtend extends MyMockTest
val my = mock[TraitExtend]
println(my.funNoArgs) //輸出func no args
-
trait
有參調用mock
trait 有參數mock編寫方式有兩種,僅僅是寫法上差異仿贬,本質上沒有差別
- 根據參數類型進行mock
//使用指定 參數類型的方式纽竣,mock 有參函數
(my.funcOnePara(_:String)).expects("wayne") returning("one para")
println(my.funcOnePara("wayne"))
- 根據匿名函數,使用類型推導方式 mock
//使用指定 匿名函數的方式茧泪,利用依賴函數推導蜓氨,mock 有參函數
(my.funcOnePara _ :String => String).expects("wayne") returning("one para")
println(my.funcOnePara("wayne"))
-
val/var
變量不支持mock
我們無法在外部使用其它val變量時,模擬偽裝其結果队伟。 只能通過其它方式穴吹,見后文。
基于class
的mock
編寫測試用例時嗜侮,我們遇見更多場景是函數中調用其它類函數港令,或者引用外部資源啥容。在不使用資源的情況下,做到更好的測試顷霹,我們需要對外部函數進行mock操作咪惠。基于class進行mock是更普適使用場景泼返。
class
進行mock操作關鍵字是:mock
.
//源碼示例
class MyMockClass {
var age: Int = 0
def funcMock = ""
?
}
class
的構成和trait
構成類似,因此mock的方式也是一致的姨拥。類中函數模擬示例绅喉,和trait沒有任何區(qū)別。
//mock class without param
val t = mock[MyMockClass]
(t.funcMock _ ) expects() returning("mock class function")
?
println(t.funcMock)
值得關注的是:不論mock那種類型叫乌,都是對該類型進行操作柴罐。只影響指定輸入和返回值的函數,對其它沒有影響
基于case class
的mock
樣例類在scala中是特殊的存在憨奸,即作為數據的承載者革屠,有具備處理能力。我們采用投機的版本進行mock,你們函數的mock排宰。定義樣例類Person
進行mock測試似芝。示例如下:
case class Person(age:Int, address:String)
//case class mock 一個變量或者函數 沒有mock必要
val caseMock = mockFunction[Unit,Person]
(caseMock).expects().returning(Person(age= 12))
?
println(caseMock().age)
我們看到給Person
mock的方法是采用我們上文提到的匿名函數。
ScalaMock其它支持特性
支持重載方法的重載
支持java的類和接口
支持邊界測試板甘,即給出邊界值党瓮,拋出異常
支持統(tǒng)計函數調用次數,和函數調用次數類似
支持對返回值類型排序
支持在多線程環(huán)境進行mock
ScalaMock 力所不及
如果看到scalaMock的實現原理盐类,我們會發(fā)現我們常用的 單例對象寞奸、有參數class都不能進行mock操作。
下面列舉下scalamock
不支持的特征
不支持 final/private等關鍵字mock
-
不支持單例object對象 mock
注:一個可支持mock的方法是:把單例對象修改成
trait
對象在跳,或者把調用單例對象封裝到實現調用類中枪萄,通過mock方法形式達到mock 單例對象的目的,曲線“救國”吧猫妙。
以上敘述scalamock用法瓷翻,由于其內在的約束,要想用好scalamock割坠,既要了解用法逻悠,更重要的是編碼的合理性。工具有優(yōu)越性建立在優(yōu)秀的代碼之上韭脊。
下一篇童谒,介紹scala的其它mock組件。