BDD是 測試驅動開發 (TDD) 的合理下一步 。
行為驅動的發展
本質上,BDD是一種交付需求的方法。 但不僅有任何要求,還包括可執行要求! 使用BDD,您可以以可以針對該軟件運行的格式來編寫方案 ,以確定該軟件是否按預期運行。
情境
場景以“ 給定,何時,然后”格式(也稱為Gherkin)編寫:
Given the ATM has $250
And my balance is $200
When I withdraw $150
Then the ATM has $100
And my balance is $50
Given表示初始上下文, When表示發生有趣的事件, 然后斷言預期的結果。 并且可以用來代替重復的關鍵字,以使方案更具可讀性。
Give / When / Then是一個非常強大的習慣用法 ,幾乎可以描述任何要求。 這種格式的場景也很容易解析,因此我們可以自動運行它們。
BDD場景對開發人員非常有用,因為它們提供了有關故事是否完成的快速而明確的反饋。 不僅可以提供主要成功方案,還可以提供替代方案和異常方案 ,如濫用案例 。 后者要求產品負責人不僅要與測試人員和開發人員合作,還要與安全專家合作。 結果是, 管理安全要求變得更加容易。
盡管BDD實際上是關于協作過程的,而不是關于工具的,但在本文的其余部分中,我將專注于工具。 請記住, 工具永遠無法挽救您,而溝通和協作則可以 。 消除了這一警告,讓我們開始使用一些開源工具來實現BDD。
杰貝夫

JBehave是Java的BDD工具。 它從故事文件中解析場景,將它們映射到Java代碼,通過JUnit測試運行它們,并生成報告。
JUnit的
這是我們使用JUnit運行故事的方式:
@RunWith(AnnotatedEmbedderRunner.class)
@UsingEmbedder(embedder = Embedder.class, generateViewAfterStories = true,ignoreFailureInStories = true, ignoreFailureInView = false, verboseFailures = true)
@UsingSteps(instances = { NgisRestSteps.class })
public class StoriesTest extends JUnitStories {@Overrideprotected List<String> storyPaths() {return new StoryFinder().findPaths(CodeLocations.codeLocationFromClass(getClass()).getFile(),Arrays.asList(getStoryFilter(storyPaths)), null);}private String getStoryFilter(String storyPaths) {if (storyPaths == null) {return '*.story';}if (storyPaths.endsWith('.story')) {return storyPaths;}return storyPaths + '.story';}private List<String> specifiedStoryPaths(String storyPaths) {List<String> result = new ArrayList<String>();URI cwd = new File('src/test/resources').toURI();for (String storyPath : storyPaths.split(File.pathSeparator)) {File storyFile = new File(storyPath);if (!storyFile.exists()) {throw new IllegalArgumentException('Story file not found: ' + storyPath);}result.add(cwd.relativize(storyFile.toURI()).toString());}return result;}@Overridepublic Configuration configuration() {return super.configuration().useStoryReporterBuilder(new StoryReporterBuilder().withFormats(Format.XML, Format.STATS, Format.CONSOLE).withRelativeDirectory('../build/jbehave')).usePendingStepStrategy(new FailingUponPendingStep()).useFailureStrategy(new SilentlyAbsorbingFailure());}}
這使用JUnit 4的@RunWith
注釋指示將運行測試的類。 AnnotatedEmbedderRunner
是JBehave提供的JUnit Runner
。 它尋找@UsingEmbedder
批注以確定如何運行故事:
-
generateViewAfterStories
指示JBehave在運行故事后創建測試報告 - 當故事失敗時,
ignoreFailureInStories
防止JBehave引發異常。 這對于與Jenkins集成至關重要,如下所示
@UsingSteps
批注將方案中的步驟鏈接到Java代碼。 下面的更多內容。 您可以列出多個類別。
我們的測試類重新使用了JBehave的JUnitStories類,這使得運行多個故事變得容易。 我們只需要實現兩種方法: storyPaths()
和configuration()
。
storyPaths()
方法告訴JBehave在哪里找到要運行的故事。 我們的版本有點復雜,因為我們希望能夠從IDE和命令行運行測試,并且希望能夠運行所有故事或特定子集。
我們使用系統屬性bdd.stories
指示要運行的故事。 這包括對通配符的支持。 我們的命名約定要求故事文件名以角色開頭,因此我們可以使用-Dbdd.stories=wanda_*
類的-Dbdd.stories=wanda_*
輕松地為單個角色運行所有故事。
configuration()
方法告訴JBehave 如何運行故事并對其進行報告 。 我們需要XML輸出,以便在Jenkins中進行進一步處理,如下所示。
感興趣的一件事是報告的位置。 JBehave支持Maven,這很好,但是他們假定每個人都遵循Maven約定,但事實并非如此。 默認情況下,輸出進入一個名為target
的目錄,但是我們可以通過指定相對于target
目錄的路徑來覆蓋該目錄。 我們使用Gradle代替Maven,并且Gradle的臨時文件進入build
目錄,而不是target
。 有關Gradle的更多信息,請參見下文。
腳步
現在我們可以運行我們的故事,但是它們會失敗。 我們需要告訴JBehave如何將場景中的Given / When / Then步驟映射到Java代碼。 步驟類確定可以在方案中使用的詞匯表。 因此,他們定義了一種用于接受測試我們的應用程序的領域特定語言 (DSL)。
我們的應用程序具有RESTful接口,因此我們編寫了通用的REST DSL。 但是,由于REST中的HATEOAS約束,客戶端需要大量調用才能發現其應使用的URI。 這樣寫場景變得很無聊和重復,因此我們在REST DSL之上添加了特定于應用程序的DSL。 這使我們可以用產品負責人理解的術語來編寫方案 。
在通用REST步驟之上分層特定于應用程序的步驟具有一些優點:
- 實施新的特定于應用程序的DSL很容易,因為他們只需要調用REST特定的DSL
- REST專用的DSL可以與其他項目共享
搖籃

有了步驟,我們可以從我們最喜歡的IDE中運行我們的故事。 這對開發人員來說效果很好,但不能用于持續集成 (CI)。
我們的CI服務器運行無頭構建,因此我們需要能夠從命令行運行BDD方案。 我們使用Gradle使構建自動化,并且Gradle已經可以運行JUnit測試。 但是,我們的構建是一個多項目構建。 在構建所有項目,創建發行版并啟動應用程序之前,我們不希望運行BDD方案。
因此,首先,我們禁止在包含BDD故事的項目上運行測試:
test {onlyIf { false } // We need a running server
}
接下來,我們創建另一個可以在啟動應用程序后運行的任務:
task acceptStories(type: Test) {ignoreFailures = truedoFirst {// Need 'target' directory on *nix systems to get any outputfile('target').mkdirs()def filter = System.getProperty('bdd.stories') if (filter == null) {filter = '*'}def stories = sourceSets.test.resources.matching { it.include filter}.asPathsystemProperty('bdd.stories', stories)}
}
在這里,我們看到了Gradle的力量。 我們定義了一個Test
類型的新任務,以便它已經可以運行JUnit測試。 接下來,我們使用一些Groovy腳本配置該任務。
首先,我們必須確保target
目錄存在。 我們不需要甚至不需要它,但是如果沒有它,JBehave將無法在* nix系統上正常工作。 我想這有點Maven主義
接下來,再次使用bdd.stories
系統屬性添加對運行故事子集的支持。 我們的故事文件位于src/test/resources
,因此我們可以使用標準Gradle test
源集輕松訪問它們。 然后,我們為運行測試的JVM設置系統屬性bdd.stories
。
詹金斯
現在,我們可以從IDE和命令行運行BDD方案。 下一步是將它們集成到我們的CI版本中。
我們可以將JBehave報告存檔為工件,但是,老實說,JBehave生成的報告并不是很好。 幸運的是,JBehave團隊還維護了Jenkins CI服務器的插件 。 該插件需要事先安裝xUnit插件 。
將xUnit和JBehave插件安裝到jenkins中之后,我們可以配置Jenkins作業以使用JBehave插件。 首先,添加xUnit構建后操作。 然后,選擇JBehave測試報告。


使用此配置,在我們的BDD故事上運行JBehave的輸出看起來像常規單元測試的輸出:

請注意,圖中的黃色部分表示待處理的步驟。 那些用于BDD場景,但是在Java Steps類中沒有。 等待結果顯示在測試結果的“ Skip
列中:

請注意,JBehave Jenkins插件如何將故事轉換為測試,將場景轉換為測試方法。 這樣可以很容易地發現哪些方案需要更多工作。
盡管JBehave插件運行良好,但是有兩點可以改進:
- 測試的輸出未顯示。 這使得很難弄清楚場景失敗的原因。 因此,我們還存檔了JUnit測試報告
- 如果將
ignoreFailureInStories
配置為false
,則JBehave會在失敗時引發異常,該異常會截斷XML輸出。 然后,JBehave Jenkins插件將無法再解析XML(因為它的格式不正確),并且會完全失敗,從而使您沒有測試結果
所有這些都是不便之處,我們對自動化的BDD方案感到非常滿意。
祝您編程愉快,別忘了分享!
參考: 安全軟件開發博客上來自JCG合作伙伴 Remon Sinnema的JBehave,Gradle和Jenkins的行為驅動開發(BDD) 。
翻譯自: https://www.javacodegeeks.com/2012/09/behavior-driven-development-bdd-with.html