javascript
使用Jasmine,Spock和Nashorn测试JVM服务器端JavaScript
JavaScript使用不僅限于瀏覽器中的客戶(hù)端代碼或NodeJS支持的服務(wù)器端代碼。 許多基于JVM的項(xiàng)目都將其用作內(nèi)部腳本語(yǔ)言。 測(cè)試這種功能既不簡(jiǎn)單也不標(biāo)準(zhǔn)。 在本文中,我打算演示一種使用成熟的工具(例如Jasmine , Spock和Nashorn在服務(wù)器端JVM環(huán)境中測(cè)試JavaScript的方法。
與客戶(hù)端編碼相比,在JVM應(yīng)用程序內(nèi)部使用JavaScript作為腳本引擎有很大的不同。 不幸的是,如今沒(méi)有用于測(cè)試它的工業(yè)標(biāo)準(zhǔn)工具。
關(guān)于Internet中現(xiàn)有的方法,我想強(qiáng)調(diào)以下缺點(diǎn):
- 缺乏與構(gòu)建和持續(xù)集成工具(Maven,Gradle,Jenkins等)的集成
- 與IDE的合作不足
- 無(wú)法運(yùn)行單個(gè)套件或通過(guò)IDE進(jìn)行測(cè)試
- 與瀏覽器環(huán)境緊密耦合
- 無(wú)法使用自定義的JavaScript執(zhí)行程序
據(jù)我所知,大多數(shù)項(xiàng)目通過(guò)調(diào)用JS引擎運(yùn)行器,將被測(cè)腳本傳遞給它并通過(guò)檢查腳本執(zhí)行后對(duì)引擎或模擬的副作用進(jìn)行斷言來(lái)測(cè)試其嵌入式業(yè)務(wù)腳本。
這些方法通常具有類(lèi)似的缺點(diǎn):
- 難以對(duì)JS代碼進(jìn)行存根或模擬,通常會(huì)導(dǎo)致對(duì)JS prototype黑客攻擊
- 腳本的模擬環(huán)境需要過(guò)多的編排
- 難以將測(cè)試組織到套件中并報(bào)告測(cè)試執(zhí)行錯(cuò)誤
- 以前的原因?yàn)樘囟?xiàng)目創(chuàng)建了自定義測(cè)試套件框架
- 不利用現(xiàn)有JavaScript測(cè)試工具和框架
因此,在JVM項(xiàng)目中需要舒適的嵌入式JavaScript測(cè)試的推動(dòng)下,我創(chuàng)建了此示例設(shè)置。 為了實(shí)現(xiàn)我們的目標(biāo),將使用下一個(gè)工具。
- Jasmine是最著名JavaScript TDD / BDD工具之一
- Spock是由Junit和Groovy支持的JVM的出色測(cè)試框架
- Nashorn是JDK8中引入的現(xiàn)代腳本引擎
定制JavaScript運(yùn)行器(基于Nashorn)
在非瀏覽器JS環(huán)境中不需要遵循標(biāo)準(zhǔn),因此開(kāi)發(fā)人員通常使用自定義函數(shù),內(nèi)置變量等擴(kuò)展腳本引擎。對(duì)于生產(chǎn)和測(cè)試目的,使用完全相同的運(yùn)行程序極為重要。
讓我們考慮一下,我們有這樣的自定義運(yùn)行器,接受腳本名稱(chēng)和預(yù)定義變量的映射作為參數(shù)并返回執(zhí)行腳本的結(jié)果值。
JavaScriptRunner.java
public class JavaScriptRunner {public static Object run(String script, Map<String, Object> params) throws Exception {ScriptEngineManager factory = new ScriptEngineManager();ScriptEngine engine = factory.getEngineByName("nashorn");engine.getBindings(ScriptContext.ENGINE_SCOPE).putAll(params);return engine.eval(new InputStreamReader(JavaScriptRunner.class.getResourceAsStream(script))); (1)} }| 1個(gè) | 在類(lèi)路徑中搜索腳本源。 |
茉莉花的設(shè)置
要開(kāi)始使用Jasmine框架,我們需要:
- 下載Jasmine并將其解壓縮到項(xiàng)目資源目錄中的/jasmine/jasmine-2.1.2文件夾中
- 自定義引導(dǎo)腳本,因?yàn)镴asmine不支持基于JVM的平臺(tái)
jasmine2-bootstrap.js
var loadFromClassPath = function(path) { (1)load(Java.type("ua.eshepelyuk.blog.nashorn.Jasmine2Specification").class.getResource(path).toExternalForm()); };var window = this;loadFromClassPath("/jasmine/jasmine-2.1.2/jasmine.js"); loadFromClassPath("/jasmine/jasmine2-html-stub.js"); (2) loadFromClassPath("/jasmine/jasmine-2.1.2/boot.js"); load({script: __jasmineSpec__, name: __jasmineSpecName__}); (3)onload(); (4)jsApiReporter.specs(); (5)| 1個(gè) | helper函數(shù)從類(lèi)路徑位置解析腳本路徑。 |
| 2 | Nashorn專(zhuān)用代碼可在非瀏覽器環(huán)境中調(diào)整Jasmine 。 不屬于Jasmine發(fā)行。 |
| 3 | 加載測(cè)試套件源代碼,有關(guān)詳細(xì)信息,請(qǐng)參見(jiàn)下一部分。 |
| 4 | 偽造瀏覽器load事件,應(yīng)觸發(fā)測(cè)試套件執(zhí)行。 |
| 5 | 該值將作為腳本結(jié)果返回。 |
將Jasmine報(bào)告轉(zhuǎn)換為Spock測(cè)試
使用Jasmine JS執(zhí)行程序和引導(dǎo)腳本,我們可以創(chuàng)建JUnit測(cè)試以遍歷套件結(jié)果并檢查是否全部成功。 但是,了解哪個(gè)特定測(cè)試失敗以及失敗的原因?qū)⒊蔀橐粓?chǎng)噩夢(mèng)。 我們真正想要擁有的是將每個(gè)Jasmine規(guī)范表示為JUnit測(cè)試的功能,因此任何Java工具都可以拾取并檢查結(jié)果。 這就是為什么Spock可以解決問(wèn)題的原因,它的數(shù)據(jù)驅(qū)動(dòng)測(cè)試允許開(kāi)發(fā)人員聲明輸入數(shù)據(jù)列表,并針對(duì)該數(shù)據(jù)集的每個(gè)項(xiàng)目創(chuàng)建并執(zhí)行新測(cè)試。 這與Junit 參數(shù)化測(cè)試非常相似,但功能更強(qiáng)大。
因此,想法是將運(yùn)行引導(dǎo)腳本后獲得的Jasmine測(cè)試套件結(jié)果視為輸入數(shù)據(jù)數(shù)組,其每一項(xiàng)都將傳遞給Spock測(cè)試。 然后測(cè)試本身將提供斷言以正確報(bào)告成功和失敗的測(cè)試,即斷言應(yīng)檢查Jasmine規(guī)范的狀態(tài)。
- 如果狀態(tài)為pending或passed ,則表示規(guī)范被忽略或成功
- 否則, Spock測(cè)試應(yīng)該拋出斷言錯(cuò)誤,并用Jasmine報(bào)告的失敗消息填充斷言異常
Jasmine2Specification.groovy
abstract class Jasmine2Specification extends Specification {@Shared def jasmineResultsdef setupSpec() {def scriptParams = ["__jasmineSpec__" : getMetaClass().getMetaProperty("SPEC").getProperty(null), (1)"__jasmineSpecName__": "${this.class.simpleName}.groovy"]jasmineResults = JavaScriptRunner.run("/jasmine/jasmine2-bootstrap.js", scriptParams) (2)}def isPassed(def specRes) {specRes.status == "passed" || specRes.status == "pending"}def specErrorMsg(def specResult) {specResult.failedExpectations.collect {it.value}.collect {it.stack}.join("\n\n\n")}@Unroll def '#specName'() {expect:assert isPassed(item), specErrorMsg(item) (3)where:item << jasmineResults.collect { it.value }specName = (item.status != "pending" ? item.fullName : "IGNORED: $item.fullName") (4)} }| 1個(gè) | 將Jasmine套件的源代碼公開(kāi)為jasmineSpec變量,可讓JS執(zhí)行器訪問(wèn)。 |
| 2 | Jasmine套件的實(shí)際執(zhí)行。 |
| 3 | 對(duì)于每個(gè)套件結(jié)果,我們assert要么成功,要么在失敗時(shí)使用Jasmine發(fā)起的消息引發(fā)斷言錯(cuò)誤。 |
| 4 | 附加的數(shù)據(jù)提供程序變量以突出顯示被忽略的測(cè)試。 |
完整的例子
讓我們?yōu)楹?jiǎn)單JavaScript函數(shù)創(chuàng)建測(cè)試套件。
mathUtils.js
var add = function add(a, b) {return a + b; };使用上一步中的基類(lèi),我們可以創(chuàng)建包含JavaScript測(cè)試的Spock套件。 為了演示我們解決方案的所有可能性,我們將創(chuàng)建成功,失敗和被忽略的測(cè)試。
MathUtilsTest.groovy
class MathUtilsTest extends Jasmine2Specification {static def SPEC = """ (1) loadFromClassPath("/js/mathUtils.js"); (2) describe("suite 1", function() {it("should pass", function() {expect(add(1, 2)).toBe(3);});it("should fail", function() {expect(add(1, 2)).toBe(3);expect(add(1, 2)).toBe(0);});xit("should be ignored", function() {expect(add(1, 2)).toBe(3);}); }) """ }| 1個(gè) | Jasmine套件的實(shí)際代碼表示為String變量。 |
| 2 | 使用從jasmine-bootstrap.js繼承的功能加載受測(cè)模塊。 |
圖1. IntelliJ IDEA的測(cè)試結(jié)果
IntelliJ Idea語(yǔ)言注入
盡管此微框架可以在所有IDE中使用,但由于其語(yǔ)言注入 ,它的最方便用法將在IntelliJ IDEA中 。 該功能允許將任意語(yǔ)言嵌入到以其他編程語(yǔ)言創(chuàng)建的文件中。 因此,我們可以將JavaScript代碼塊嵌入到用Groovy編寫(xiě)的Spock規(guī)范中。
圖2.語(yǔ)言注入
解決方案的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 使用兩種語(yǔ)言的行業(yè)標(biāo)準(zhǔn)測(cè)試工具
- 與構(gòu)建工具和持續(xù)集成工具的無(wú)縫集成
- 從IDE運(yùn)行單個(gè)套件的能力
- 借助Jasmine的突出功能,可以從特定套件運(yùn)行單個(gè)測(cè)試
缺點(diǎn)
- 在測(cè)試異常的情況下,沒(méi)有干凈的方法來(lái)檢測(cè)特定的源代碼行
- 一點(diǎn)面向IntelliJ IDEA的設(shè)置
聚苯乙烯
在此示例項(xiàng)目中,我使用了JDK8的現(xiàn)代Nashorn引擎。 但實(shí)際上對(duì)此沒(méi)有限制。 同樣的方法已成功應(yīng)用于使用較舊Rhino引擎的項(xiàng)目。 再說(shuō)一次, Jasmine只是我的個(gè)人喜好。 隨著其他工作代碼的調(diào)整,可以利用Mocha , QUnit等。
- 完整的項(xiàng)目代碼可在My GitHub上獲得 。
翻譯自: https://www.javacodegeeks.com/2014/12/testing-jvm-server-side-javascript-with-jasmine-spock-and-nashorn.html
總結(jié)
以上是生活随笔為你收集整理的使用Jasmine,Spock和Nashorn测试JVM服务器端JavaScript的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何轻松配置上网行为管理功能路由器如何设
- 下一篇: 研究Java 9 Money and C