雅關(guān)機(jī)的集成測試體系)
后端Web框架【免費(fèi)下載鏈接】playframeworkThe Community Maintained High Velocity Web Framework For Java and Scala.項(xiàng)目地址https://gitcode.com/gh_mirrors/pl/playframework點(diǎn)擊查看免費(fèi)下載本文基于 dev-mode/sbt-plugin/src/sbt-test/README.md 展開。該文檔是 Play Framework 的 sbt 插件sbt-plugin內(nèi)置 scripted 測試的總綱描述了 Play 如何通過 sbt 官方的 scripted 測試框架對(duì) Evolutions數(shù)據(jù)庫演進(jìn)、應(yīng)用優(yōu)雅關(guān)機(jī)Coordinated Shutdown與資源回收、HTTP 后端Pekko HTTP / Netty選擇等核心機(jī)制做端到端驗(yàn)證。讀完本文你將掌握這些測試套件的邏輯分組、典型腳本寫法、支撐命令的底層實(shí)現(xiàn)以及如何在自己的 Play 插件項(xiàng)目中搭建類似的 scripted 測試體系。一、什么是 sbt scripted 測試Play 如何組織它們sbt scripted是 sbt 官方提供的測試插件本身的機(jī)制每個(gè)測試是一個(gè)獨(dú)立的迷你 sbt 工程fixture外加一個(gè)名為test的腳本文件。腳本中的行表示在 sbt 里執(zhí)行命令$行表示執(zhí)行文件操作如exists、delete、copy-file、sleep前綴-表示反向斷言例如-$ exists表示斷言該文件不存在前綴-表示該命令應(yīng)當(dāng)失敗。Play 的 sbt 插件把這些測試全部收集在play-sbt-plugin目錄下其目的在 README 中寫得非常直白They are all in the play-sbt-plugin directory so we can use scripteds play-sbt-plugin/*1of3 feature to auto-split the tests into groups and run them in parallel build jobs.也就是說把測試都放在同一個(gè)目錄下是為了能使用 scripted 的play-sbt-plugin/*1of3特性把測試自動(dòng)切分成多組、在并行構(gòu)建任務(wù)中運(yùn)行從而縮短 CI 耗時(shí)。整個(gè)測試目錄的實(shí)際結(jié)構(gòu)見 dev-mode/sbt-plugin/src/sbt-test/play-sbt-plugin包含 40 余個(gè)以-分隔的 fixture 工程而 README 按邏輯分組將其歸為四大套件測試套件目錄前綴驗(yàn)證目標(biāo)Maven Layoutmaven-layout-回歸測試Maven 目錄布局下的資源/模板熱重載Evolutionsevolutions-數(shù)據(jù)庫演進(jìn)在 DEV / PROD 模式下的應(yīng)用與錯(cuò)誤處理Shutdownshutdown-各模式下應(yīng)用與 JVM 的資源回收Coordinated ShutdownHTTP backendhttp-backend-不同 HTTP 后端Pekko HTTP / Netty與 HTTP/2 的行為每個(gè) fixture 工程的project/plugins.sbt通過系統(tǒng)屬性引入當(dāng)前構(gòu)建中的插件版本例如 shutdown-happy-path 的 plugins.sbtupdateOptions : updateOptions.value.withLatestSnapshots(false) addSbtPlugin(org.playframework % sbt-plugin % sys.props(project.version)) addSbtPlugin(org.playframework % sbt-scripted-tools % sys.props(project.version))其中sbt-scripted-tools提供了測試腳本里用到的一系列輔助命令詳見本文第五節(jié)sys.props(project.version)確保測試的是當(dāng)前正在構(gòu)建的插件版本而不是發(fā)布到倉庫的舊版本。二、Maven Layout 測試套件maven-layout-README 對(duì)它的定位很簡短但意圖明確This holds a few regression tests. When Maven Layout becomes the default Layout for Play this test suite can be removed.它存放一批回歸測試防止 Maven 目錄布局下的行為退化一旦 Maven Layout 成為 Play 的默認(rèn) Layout這套測試就會(huì)被移除。從源碼結(jié)構(gòu)看它目前處于過渡期臨時(shí)存在的狀態(tài)。以 maven-layout-twirl-reload/test 為例可以直觀看到 scripted 腳本如何驅(qū)動(dòng) dev 模式并驗(yàn)證 Twirl 模板熱重載# Start dev mode clean run # Existing file change detection verifyResourceContains / 200 Original $ copy-file src/changes/index.scala.html.1 src/main/twirl/views/index.scala.html verifyResourceContains / 200 First $ copy-file src/changes/index.scala.html.2 src/main/twirl/views/index.scala.html verifyResourceContains / 200 Second playStop幾個(gè)要點(diǎn)目錄布局源文件不在src/main/scala下的標(biāo)準(zhǔn) sbt 布局而是src/main/twirl/views/這正是 Maven 布局在 Play 中的體現(xiàn)模板目錄隨布局移動(dòng)。熱重載驗(yàn)證用copy-file覆蓋模板文件后緊接著的verifyResourceContains請求必須能拿到新內(nèi)容Original → First → Second證明 dev 模式的 watch 服務(wù)捕捉到了文件變更并完成增量編譯。由于底層文件監(jiān)聽JDK watch service 可能退化為輪詢存在時(shí)間戳粒度問題verifyResourceContains本身內(nèi)置了最多 30 次、每次 500ms 的重試見第五節(jié)這正與 README 中回歸測試的定位相符它守護(hù)的是文件變更檢測這條關(guān)鍵鏈路不退化。三、Evolutions 測試套件evolutions-這是 README 中描述最詳細(xì)的一套驗(yàn)證 Play Evolutions數(shù)據(jù)庫演進(jìn)在 DEV 與 PROD 兩種模式下的完整行為。對(duì)應(yīng)目錄有evolutions-auto-apply-false、evolutions-auto-apply-true、evolutions-multiple-databases、evolution-path-config四個(gè) fixture。3.1 DEV 模式README 列出四條主線逐一對(duì)應(yīng)腳本實(shí)現(xiàn)1. 有變更但autoApplyfalse時(shí)詢問后再應(yīng)用見 evolutions-auto-apply-false/test run # Needs evolution since autoApplyfalse for this test verifyResourceContains / 500 evolution applyEvolutions /evolutions/apply/default verifyResourceContains / 200 1_PlayerFromFirstEvolution 2_PlayerFromStartupInit 3_PlayerFromControllerInit要點(diǎn)首次啟動(dòng)時(shí)存在待應(yīng)用的演進(jìn)但由于autoApplyfalse應(yīng)用不會(huì)自動(dòng)應(yīng)用頁面返回500且內(nèi)容包含evolution字樣即 Play 的需要演進(jìn)提示頁手動(dòng)調(diào)用applyEvolutions /evolutions/apply/default本質(zhì)是請求http://localhost:9000/evolutions/apply/default后演進(jìn)被應(yīng)用頁面恢復(fù)200并包含各演進(jìn)寫入的數(shù)據(jù)標(biāo)記隨后copy-file changes/2.sql conf/evolutions/default/2.sql放入一個(gè)新的演進(jìn)腳本文件 watcher 感知后再次出現(xiàn)500 evolution再次applyEvolutions后新數(shù)據(jù)出現(xiàn)——這正是 README 中Detects new evolution as ask to apply的場景。2.autoApplytrue時(shí)無需詢問自動(dòng)應(yīng)用見 evolutions-auto-apply-true/test run # Evolutions will be applied automatically since autoApplytrue verifyResourceContains / 200 1_PlayerFromFirstEvolution 2_PlayerFromStartupInit 3_PlayerFromControllerInit # Add a new evolution to verify that it will be applied automatically $ copy-file changes/2.sql conf/evolutions/default/2.sql $ sleep 2000 verifyResourceContains / 200 4_PlayerFromSecondEvolution 5_PlayerFromStartupInit 6_PlayerFromControllerInit啟動(dòng)后無需任何干預(yù)第一次請求就是200放入新演進(jìn)后等待 2 秒$ sleep 2000給文件 watcher 反應(yīng)時(shí)間新演進(jìn)也被自動(dòng)應(yīng)用。3. 演進(jìn)腳本有錯(cuò)誤時(shí)展示錯(cuò)誤并支持標(biāo)記為已解決繼續(xù)看evolutions-auto-apply-false的后半段# Copy evolution with invalid commands $ copy-file changes/3.sql conf/evolutions/default/3.sql $ sleep 2000 # First try to apply evolution applyEvolutions /evolutions/apply/default # It will then fail since there is error verifyResourceContains / 500 evolution # And it can be market as resolved applyEvolutions /evolutions/resolve/default/3 verifyResourceContains / 200 7_PlayerFromThirdEvolution含非法 SQL 的演進(jìn)在應(yīng)用時(shí)報(bào)錯(cuò)頁面回到500 evolution錯(cuò)誤態(tài)此時(shí)用戶可通過/evolutions/resolve/default/3將第 3 個(gè)演進(jìn)標(biāo)記為已手工修復(fù)隨后應(yīng)用恢復(fù)正常。這正是 README 中Accepts the user request to consider the error manually fixed的完整閉環(huán)。4. 多數(shù)據(jù)庫演進(jìn)見 evolutions-multiple-databases/test它對(duì)應(yīng) README 列出的四種組合場景兩庫都autoApplyfalse、一庫false一庫true、對(duì)false庫詢問、對(duì)true庫自動(dòng)應(yīng)用 run # We will see that groups need some evolution verifyResourceContains /groups 500 groups applyEvolutions /evolutions/apply/groups verifyResourceContains /users 200 Player1 verifyResourceContains /groups 200 Group1 # Add a new evolution to verify that it will be applied automatically $ copy-file changes/users/2.sql conf/evolutions/users/2.sql $ sleep 2000 verifyResourceContains /users 200 Player2 # Add a new evolution to the database that requires manual intervention $ copy-file changes/groups/2.sql conf/evolutions/groups/2.sql $ sleep 2000 verifyResourceContains /groups 500 groups applyEvolutions /evolutions/apply/groups verifyResourceContains /groups 200 Group2 playStop可以看到users庫配置為自動(dòng)應(yīng)用新演進(jìn)無需干預(yù)直接生效groups庫需要手動(dòng)確認(rèn)出現(xiàn)500后通過applyEvolutions /evolutions/apply/groups應(yīng)用注意路徑中帶庫名groups。所有演進(jìn) URL 都支持按數(shù)據(jù)庫名區(qū)分。演進(jìn)配置存放于 fixture 的conf/application.conf例如 evolutions-auto-apply-false 的配置db.default.driverorg.h2.Driver db.default.urljdbc:h2:mem:auto_apply_evolutions_false play.modules.enabled startup.StartupModule演進(jìn) SQL 按conf/evolutions/{數(shù)據(jù)庫名}/N.sql的約定存放腳本通過copy-file動(dòng)態(tài)追加新版本從而模擬運(yùn)行中新增演進(jìn)的真實(shí)場景。3.2 PROD 模式README 的兩條 PROD 斷言在evolutions-auto-apply-true與evolutions-auto-apply-false腳本尾部均有體現(xiàn)。autoApplytrue應(yīng)能正常啟動(dòng)# Generate a secret so that wont be the cause of the failure playUpdateSecret # Should start successfully when there are evolutions and autoApplytrue runProd --no-exit-sbt $ sleep 4000 $ exists target/universal/stage/RUNNING_PID verifyResourceContains / 200 1_PlayerFromFirstEvolution 2_PlayerFromSecondEvolution 3_PlayerFromStartupInit 4_PlayerFromControllerInit stopProd --no-exit-sbtrunProd --no-exit-sbt以 PROD 模式啟動(dòng)--no-exit-sbt表示不退出 sbt 進(jìn)程playUpdateSecret先生成應(yīng)用密鑰以避免secret 未配置這一無關(guān)因素干擾測試。啟動(dòng)后斷言RUNNING_PID文件存在且所有演進(jìn)數(shù)據(jù)含后來追加的第二版都已就緒。autoApplyfalse應(yīng)啟動(dòng)失敗 playUpdateSecret # Should fail to start when there are evolutions and autoApplyfalse - runProd $ sleep 4000 # And since it fail to start, there should be no pid file -$ exists target/universal/stage/RUNNING_PID- runProd斷言該命令預(yù)期失敗PROD 模式拒絕帶未應(yīng)用演進(jìn)而啟動(dòng)隨后 4 秒后斷言RUNNING_PID不存在——因?yàn)檫M(jìn)程根本沒能起來。這兩條測試把PROD 模式強(qiáng)約束的行為固化為可自動(dòng)驗(yàn)證的回歸用例。四、Shutdown 測試套件shutdown-這是 README 中篇幅最大的部分目標(biāo)是This collection of scripted tests helps ensuring the correct resource de-alloc in as many scenarios as possible.即在盡可能多的場景下確保資源被正確釋放線程池、ActorSystem、連接等。Play 3.x 基于 Apache Pekko應(yīng)用優(yōu)雅關(guān)機(jī)依賴 Pekko 的CoordinatedShutdown協(xié)調(diào)關(guān)機(jī)機(jī)制它按階段Phase依次執(zhí)行注冊的任務(wù)最后決定是否退出 JVM。4.1 測試分組與覆蓋矩陣README 明確指出為降低維護(hù)成本與執(zhí)行時(shí)間測試被合并進(jìn)少數(shù) scripted 套件代價(jià)是單個(gè)測試失敗時(shí)可見性降低。兩個(gè)套件分別是shutdown-happy-pathhappy path正常路徑6-Mode.Dev 文件變更觸發(fā) dev 模式重載5-Mode.Dev Ctrl-D 停止 dev 模式3-Mode.Test 非 fork 測試1-Mode.Prod SIGTERM結(jié)束shutdown-downingdowning集群降級(jí)路徑7-Mode.Dev Downing 事件停止 dev 模式4-Mode.Test fork 測試2-Mode.Prod Downing 事件結(jié)束數(shù)字編號(hào)對(duì)應(yīng) README Using default settings 一節(jié)的 1–7 號(hào)用例兩個(gè)套件合起來正好覆蓋全部 7 個(gè)場景。4.2 通用要求PID 文件README 的 General requirements 定義了一條硬性規(guī)則Only when running Play in Mode.Prod requires producing apidfilethat must be deleted when the process completes. That file must only exist during the life-span of a PROD process (never TEST nor DEV).只有 PROD 模式才需要產(chǎn)生pidfile即RUNNING_PID且進(jìn)程結(jié)束時(shí)必須刪除TEST 與 DEV 模式永遠(yuǎn)不應(yīng)當(dāng)存在該文件。這條規(guī)則在腳本中以正反斷言反復(fù)出現(xiàn)DEV/Test 階段用-$ exists target/universal/stage/RUNNING_PID斷言不存在PROD 階段用$ exists ...斷言存在、關(guān)機(jī)后用-$ exists ...斷言已被刪除。4.3 默認(rèn)設(shè)置下的 7 個(gè)用例逐一對(duì)照 shutdown-happy-path/test 與 shutdown-downing/test用例 1/2 — Mode.ProdSIGTERM或程序化事件Downing后進(jìn)程結(jié)束## 1. Start prod mode runProd --no-exit-sbt $ sleep 1000 verifyResourceContains / 200 # Mode.Prod creates a PID_FILE $ exists target/universal/stage/RUNNING_PID ## 2. Mode.Prod exits the JVM assertProcessIsStopped awaitPidfileDeletion $ exists target/proofs/application-actorsystem-name.txt -$ exists target/universal/stage/RUNNING_PIDassertProcessIsStopped的實(shí)現(xiàn)見 ScriptedTools.scala非常典型從RUNNING_PID讀取 PID調(diào)用PlayRun.stop發(fā)送SIGTERM然后用jps輪詢最多 30 秒、每 3 秒一次確認(rèn)ProdServerStart進(jìn)程退出若超時(shí)仍存活則拋異常。shutdown-downing的 PROD 分支則用 assertProcessIsStopped simulate-downing通過請求/simulate-downing端點(diǎn)觸發(fā)程序化關(guān)機(jī)模擬集群Downing 事件。用例 3/4 — Mode.Test非 fork 與 fork 測試## Run user integration tests test # Mode.Test doesnt create a PID_FILE but runs CoordinatedShutdown -$ exists target/universal/stage/RUNNING_PID $ exists target/proofs/application-actorsystem-name.txt兩種模式都不產(chǎn)生RUNNING_PID但都要求Coordinated Shutdown 確實(shí)執(zhí)行過——通過檢查證明文件target/proofs/application-actorsystem-name.txt是否存在來斷言。區(qū)別在 JVM 行為上非 fork 測試執(zhí)行完畢后不退出 JVMsbt 進(jìn)程繼續(xù)fork 測試則退出 JVM。README 強(qiáng)調(diào)這類斷言很多是隱式的some assertions on this test are implicit. e.g. asserting dev.mode doesnt exit the JVM is implicitly asserted by runningMode.Dev testsfirst and asserting the whole test moves on toMode.Test tests即dev 模式不退出 JVM是通過dev 測試跑完后整個(gè)腳本還能繼續(xù)執(zhí)行 Mode.Test 部分來間接證明的——如果 dev 模式把 JVM 退出了后面的步驟根本不會(huì)發(fā)生。用例 5 — Mode.Dev Ctrl-DplayStop## Stopping Mode.Dev runs Coordinated Shutdown in both Server and Application Actor Systems playStop $ exists target/proofs/application-actorsystem-name.txtplayStop等價(jià)于用戶在 dev 模式控制臺(tái)按 Ctrl-D。腳本斷言關(guān)機(jī)后證明文件存在說明 Coordinated Shutdown 已運(yùn)行。測試文件中的注釋也指出了該場景的一個(gè)驗(yàn)證難點(diǎn)# TODO: assert the play-dev-mode CS was executed # Asserting the play-dev-mode CS was executed is tricky because its not available to the user即 dev 模式自身的關(guān)機(jī)階段play-dev-mode不對(duì)用戶暴露難以直接斷言屬于已知的驗(yàn)證盲區(qū)。用例 6 — Mode.Dev 文件變更觸發(fā)熱重載# Force a reload (copy a new file produce a request) $ copy-file changes/HomeController.scala src/main/scala/controllers/HomeController.scala $ sleep 1000 verifyResourceContains / 200 $ exists target/proofs/application-actorsystem-name.txt $ delete target/proofs/application-actorsystem-name.txt -$ exists target/proofs/application-actorsystem-name.txt文件變更只應(yīng)殺死Application觸發(fā)一次 App 的協(xié)調(diào)關(guān)機(jī)dev 服務(wù)器與 JVM 都必須繼續(xù)存活——所以刪除證明文件后再觸發(fā)一次請求應(yīng)用能重新起來并返回200。用例 7 — Mode.Dev Downing 事件見 shutdown-downing/test 的 DEV 分支 verifyResourceContains /simulate-downing 200 $ sleep 4000 $ exists target/proofs/application-actorsystem-name.txt # Coordinated shutdown of the App has run but the Dev mode server isnt stopped (only the App) -$ exists target/universal/stage/RUNNING_PID $ sleep 1000 verifyResourceContains / 200請求/simulate-downing觸發(fā) App 的協(xié)調(diào)關(guān)機(jī)4 秒后證明文件存在、RUNNING_PID不存在dev 模式本就不該有最關(guān)鍵的一步是最后——關(guān)機(jī)后再請求依然返回 200證明只有 Application 被回收dev 服務(wù)器仍然健在并能重新拉起應(yīng)用。4.4 證明文件機(jī)制Coordinated Shutdown 的證據(jù)target/proofs/application-actorsystem-name.txt是怎么產(chǎn)生的看 shutdown-happy-path 的 HomeController.scalafixture 應(yīng)用在啟動(dòng)時(shí)向CoordinatedShutdown注冊了一個(gè)自定義任務(wù)class HomeController Inject() ( val controllerComponents: ControllerComponents, actorSystem: ActorSystem, cs: CoordinatedShutdown, futures: Futures )(implicit executionContext: ExecutionContext) extends BaseController { // This task generates a file so scripted tests can assert CoordinatedShutdown ran. cs.addTask(CoordinatedShutdown.PhaseServiceUnbind, application-cs-proof-of-existence) { () logger.info(Running custom Coordinated Shutdown task.) val f new java.io.File(target/proofs, actorSystem.name .txt) f.getParentFile.mkdirs f.createNewFile() Future.successful(Done) } ... }任務(wù)掛在PhaseServiceUnbind階段當(dāng)協(xié)調(diào)關(guān)機(jī)執(zhí)行到服務(wù)解綁時(shí)它把當(dāng)前ActorSystem的名字寫入target/proofs/{actorSystemName}.txt。測試腳本只需斷言該文件該出現(xiàn)時(shí)出現(xiàn)、該消失時(shí)消失就能判定關(guān)機(jī)流程是否走完、走的是哪個(gè) ActorSystem??刂破鬟€提供了一個(gè)slow端點(diǎn)futures.delay(2.seconds)延遲 2 秒響應(yīng)供 PROD 用例驗(yàn)證SIGTERM 時(shí)必須先處理完 in-flight 請求再退出# Send a request to the slow action and record the response body makeRequestAndRecordResponseBody /slow slow-request.txt $ sleep 1000 # Stops the process by sending a SIGTERM assertProcessIsStopped # In-flight request should be recorded as expected $ exists target/slow-request.txt checkRecordedRequestContains slow-request.txt DONE這正是 README 中SIGTERM-ing Mode.Prod must first finish in-flight requests的落地慢請求在關(guān)機(jī)信號(hào)發(fā)出時(shí)尚未完成但最終記錄下來的響應(yīng)體仍完整包含DONE。4.5 自定義設(shè)置WIP未完成部分README 的 Using custom settings (WIP) 列出幾個(gè)對(duì)關(guān)機(jī)行為有重大影響、值得專門測試的設(shè)置并說明使用其中任一設(shè)置都可能需要對(duì)默認(rèn)設(shè)置用例做一項(xiàng)或多項(xiàng)變體測試a使用pekko.coordinated-shutdown.exit-jvm是被禁止的——配置了它Mode.Prod 根本無法啟動(dòng)bpekko.coordinated-shutdown.reason-overrides....exit-jvm針對(duì)自定義關(guān)機(jī)原因指定是否退出 JVM應(yīng)當(dāng)被遵守cTODO自定義exit-code應(yīng)被遵守dTODO為自定義關(guān)機(jī)原因設(shè)置的exit-code應(yīng)被遵守。其中 c、d 兩項(xiàng)在文檔中仍標(biāo)記為 TODO說明這套測試還在持續(xù)演進(jìn)——這也從側(cè)面印證了 README 所述以犧牲部分失敗可見性為代價(jià)換取維護(hù)成本與執(zhí)行時(shí)間的降低的取舍。五、HTTP Backend 測試套件http-backend-Play 支持兩種 HTTP 服務(wù)器實(shí)現(xiàn)Pekko HTTP與Netty。README 指出本套件針對(duì)不同 HTTP 后端提供的少量自定義行為測試核心關(guān)注點(diǎn)是Test 模式必須使用用戶配置In Test mode, Play provides tools to handle the Server and Application lifecycles. These tools must create a server using the configured backend and the specified protocols.即 Play 的測試工具WithServer、WithApplication等管理 Server 與 Application 生命周期時(shí)必須按用戶的配置選擇后端并啟用/禁用相應(yīng)協(xié)議。對(duì)應(yīng)兩方面的驗(yàn)證后端是 Pekko HTTP 還是 NettyHTTP/2 是否被啟用/禁用。各 fixture 的驗(yàn)證方式直觀通過檢查響應(yīng)頭來判斷當(dāng)前后端。例如 http-backend-pekko-http/test 注釋寫明# Asserts the backend is Pekko HTTP via checking a particular header is added on the response # Asserts tests dont start an HTTP/2 endpoint evicted dependencyTree test而 http-backend-netty/test 則反向斷言該頭部缺失# Asserts the backend is Netty via checking a particular header is missing on the response # Asserts tests dont start an HTTP/2 endpoint testHTTP/2 場景由 http-backend-pekko-http-http2/test 覆蓋它斷言測試能啟動(dòng) HTTP/2 端點(diǎn)。此外還有兩個(gè)特殊 fixturehttp-backend-system-property驗(yàn)證通過-Dplay.server.provider系統(tǒng)屬性在 dev 模式切換后端。其 test 先以run -Dplay.server.providerplay.core.server.PekkoHttpServerProvider啟動(dòng)響應(yīng)含unknown標(biāo)記playStop后換成NettyServerProvider響應(yīng)含netty標(biāo)記再執(zhí)行 test驗(yàn)證系統(tǒng)屬性對(duì)測試同樣生效http-backend-netty-channel-options針對(duì) Netty 的 channel options 配置做驗(yàn)證其目錄下包含application.conf與 XML 配置可推斷是驗(yàn)證 Netty 相關(guān)底層選項(xiàng)的傳遞。六、測試基礎(chǔ)設(shè)施sbt-scripted-tools與輔助命令所有測試腳本中用到的命令并非 sbt 內(nèi)置而是由 dev-mode/sbt-scripted-tools 提供的。理解它的實(shí)現(xiàn)就能把腳本里的每一行翻譯成真實(shí)的網(wǎng)絡(luò)與進(jìn)程行為腳本命令底層實(shí)現(xiàn)verifyResourceContains path status [tokens...]請求http://localhost:9000path斷言狀態(tài)碼與響應(yīng)體包含指定 token失敗后最多重試 30 次、每次間隔 500ms實(shí)現(xiàn)見 L138-L191這是為容忍文件 watcher 延遲與 JVM 啟動(dòng)時(shí)間而設(shè)計(jì)applyEvolutions path即callUrl請求/evolutions/apply/default等端點(diǎn)L68-L69assertProcessIsStopped [simulate-downing]讀取RUNNING_PID中的 PID調(diào)用PlayRun.stop發(fā)SIGTERM或先請求/simulate-downing觸發(fā)程序化關(guān)機(jī)再用jps輪詢確認(rèn)ProdServerStart進(jìn)程退出L215-L245makeRequestAndRecordResponseBody path file請求指定路徑并把響應(yīng)體寫入目標(biāo)文件供后續(xù)checkRecordedRequestContains斷言playUpdateSecret/playStop/runProd/stopProd復(fù)用play.sbt.run.PlayRun與 sbt-packager 的 Universal 插件能力文件頂部 import 可見實(shí)現(xiàn)細(xì)節(jié)里有幾個(gè)值得注意的點(diǎn)文件監(jiān)聽容錯(cuò)verifyResourceContainsImpl的重試循環(huán)注釋明確寫道 Using 30 max attempts so that we can give more chances to the file watcher service. This is relevant when using the default JDK watch service which does uses polling.——即默認(rèn) JDK watch service 可能退化為輪詢重試機(jī)制正是為了抵消這種不確定性staging 目錄穩(wěn)定性stableUniversalStagingDirectory把 Universal 的 staging 目錄固定為target/universal/stageL46-L51并顯式加入cleanFiles原因是 sbt 2s target is configuration-specific, but these fixtures assert the stable sbt 1 path——fixture 斷言的是穩(wěn)定的 sbt 1 路徑因此需要跨 sbt 版本固定下來SSL 支持verifyResourceContainsSsl會(huì)先安裝信任所有證書的SSLContextsetupSsl用于https://localhost:9443的 HTTPS 場景。七、在本地運(yùn)行這些測試這些 scripted 測試隨 Play 源碼庫構(gòu)建時(shí)執(zhí)行無需單獨(dú)安裝額外依賴。運(yùn)行方式# 在倉庫根目錄執(zhí)行運(yùn)行 sbt-plugin 的全部 scripted 測試 sbt sbt-plugin/scripted # 只運(yùn)行特定套件fixture 目錄名 sbt sbt-plugin/scripted play-sbt-plugin/evolutions-auto-apply-true sbt sbt-plugin/scripted play-sbt-plugin/shutdown-happy-path # 利用 1ofN 分片并行CI 常用例如只跑 3 個(gè)分片中的第 1 片 sbt sbt-plugin/scripted play-sbt-plugin/*1of3fixture 工程通過sys.props(project.version)引用當(dāng)前源碼構(gòu)建的插件版本因此必須先對(duì)倉庫完成一次構(gòu)建使本地倉庫中存在該版本快照再運(yùn)行 scripted否則插件解析會(huì)失敗。若希望獨(dú)立復(fù)用這套機(jī)制只需在src/sbt-test/group/fixture/project/plugins.sbt中加入addSbtPlugin(org.playframework % sbt-plugin % version)編寫test腳本用、$、-$、-描述 sbt 命令與文件斷言若需要verifyResourceContains等輔助命令加入sbt-scripted-tools插件并在工程中引入ScriptedTools其trigger allRequirements自動(dòng)生效。八、小結(jié)這套測試體系的工程價(jià)值回顧整個(gè)src/sbt-test/README.md可以提煉出 Play 團(tuán)隊(duì)設(shè)計(jì)腳本測試的幾條方法論對(duì)任何 sbt 插件作者都有借鑒意義按目錄前綴分組、按邏輯套件組織物理上同處一個(gè)目錄以復(fù)用*1ofN并行分片邏輯上以maven-layout-、evolutions-、shutdown-、http-backend-前綴劃分套件兼顧 CI 并行度與可讀性用證明文件代替進(jìn)程內(nèi)狀態(tài)斷言通過注冊CoordinatedShutdown任務(wù)寫文件target/proofs/*.txt讓外部腳本能確認(rèn)內(nèi)部異步流程關(guān)機(jī)確實(shí)發(fā)生是一種低成本、高確定性的驗(yàn)證手法隱式斷言與容錯(cuò)重試并重JVM 是否存活這類狀態(tài)用腳本能否繼續(xù)執(zhí)行隱式斷言文件監(jiān)聽延遲等不確定性用最多 30 次、每次 500ms 的重試吸收區(qū)分模式DEV/Test/PROD與路徑happy path / downing同一能力如協(xié)調(diào)關(guān)機(jī)在不同模式下行為不同測試矩陣按模式 × 觸發(fā)方式鋪開覆蓋 README 中編號(hào) 1–7 的全部用例。對(duì)于閱讀者而言這套測試不僅是 Play 自身質(zhì)量的保障更是一份如何為 sbt 插件編寫端到端集成測試的活教材從 測試目錄 到 輔助命令實(shí)現(xiàn)每一層都有真實(shí)、可運(yùn)行的代碼可供對(duì)照與復(fù)用。贊分享后端Web框架【免費(fèi)下載鏈接】playframeworkThe Community Maintained High Velocity Web Framework For Java and Scala.項(xiàng)目地址https://gitcode.com/gh_mirrors/pl/playframework點(diǎn)擊查看免費(fèi)下載相關(guān)推薦如何用Fasttracker 2 Clone創(chuàng)作專業(yè)電子音樂完整入門指南如何用Fasttracker 2 Clone創(chuàng)作專業(yè)電子音樂完整入門指南 想要制作復(fù)古風(fēng)格的電子音樂但不知從何開始Fasttracker 2 Clone是你Play Framework Java 應(yīng)用單元測試實(shí)戰(zhàn)指南從 JUnit 到 Mockito 的完整測試體系Play Framework Java 應(yīng)用單元測試實(shí)戰(zhàn)指南從 JUnit 到 Mockito 的完整測試體系 導(dǎo)讀 本文圍繞 Play Framework后端Web框架unicode-segmentation如何實(shí)現(xiàn)UAX29標(biāo)準(zhǔn)剖析GraphemeCursor狀態(tài)機(jī)與GB規(guī)則判定邏輯unicode segmentation如何實(shí)現(xiàn)UAX 29標(biāo)準(zhǔn)剖析GraphemeCursor狀態(tài)機(jī)與GB規(guī)則判定邏輯 unicode segmentati創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考