tag:blogger.com,1999:blog-12989741424451621862024-03-16T09:09:06.509+08:00搞笑談軟工敏捷開發,設計模式,精實開發,Scrum,軟體設計,軟體架構Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.comBlogger2369125tag:blogger.com,1999:blog-1298974142445162186.post-34827525054432144502024-03-10T23:15:00.001+08:002024-03-10T23:15:58.322+08:002024 新課程:【重構既有系統:邁向整潔架構實作班】<p>March 10 21:59~23:14</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczOFxrgDf66ULUGXHkDTyLIj25A_-iqvNb7znMwCMeLvk68mvuPUTV9XtpMAgZK4G5Cbf0C_r-BxXLKZe4rHk_MbSiBFgwh-62LT9bhdNs9mtBZLXcd-LLFF8j-oh8DKt6A1MOJxd6itwu_OC5h_gjcbsg=w2582-h1234-s-no-gm?authuser=0" width="722" height="345" /></p> <p>▲實作Clean Architecture需要了解的「設計模式」</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>2023年七月Teddy開始每天錄製一則<a href="https://www.youtube.com/@TeddyChen" target="_blank">YouTube</a>影片之後就荒廢了部落格寫作,這兩天有鄉民提醒Teddy:「blog也重要,文字也是好東西」。鄉民說得有道理啊,還是要分配點時間給blog才對。</p> <p>2024年第一篇blog,就先打個廣告,介紹泰迪軟體的最新課程:【<a href="https://teddysoft.tw/courses/refactor-to-ca/" target="_blank">重構既有系統:邁向整潔架構實作班</a>】。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">課程介紹</font></strong></p> <p>Teddy在幾年前開過軟體重構的課,當初的想法是完整介紹Refactoring這本書,從Bad Smells出發,然後舉很多小例子說明不同的重構方法如何移除這些壞味道。這種重構方式固然有用,但Teddy總是覺得有點「小打小鬧」的感覺,對於整個系統的架構改善有限。<strong><font color="#000000">就好像「你花了時間把家裡整理乾淨,但對於整個市容其實沒什麼影響,還是一樣中華民國美學:斑駁的外牆、陽台外推,以及到處都是鐵皮屋加蓋的違建。」</font></strong></p> <p><strong><font color="#000000">軟體架構層面的「大規模重構」,一直是很困難的一件事。</font></strong>去年Teddy與ezKanban團隊著手開發ezDoc:一個Living Documentation(活文件)系統,功能性需求開發完成之後,這幾個月花了很多時間在重構系統。一開始團隊也是採用傳統「由下而上」的重構方式,重構完成之後,可能是受到這幾年深入研究整潔架構(Clean Architecture)與領域驅動設計(Domain-Driven Design)的影響,Teddy突然有一個想法:<strong><font color="#000000">「設計應是由上而下的過程,如果反過來先確定就是要把軟體系統重構成Clean Architecture,接著再依據Clean Architecture的架構指導原則並套用DDD戰術設計模式來重構系統,會有怎樣的結果?」</font></strong></p> <p>Teddy與ezKanban團隊把ezDoc的兩個主要功能:Living Readme(活自我說明檔)與Living Glossary(活字彙表)依據這個想法重構,期間Teddy另外找了<a href="https://kata-log.rocks/task-list-kata" target="_blank">Task List Kata</a>這個很經典的重構範例來驗證這個做法,發現的確比傳統由下而上的重構方式要好很多。</p> <p>Teddy整理了一套<strong><font color="#000000">「整潔架構驅動重構方法(Clean Architecture-Driven Refactoring)」</font></strong>,就誕生了這個課程(還是要想辦法賺錢XD)。在這個課程中,Teddy以<a href="https://kata-log.rocks/task-list-kata" target="_blank">Task List Kata</a>作為範例,逐步說明Teddy所發明的「整潔架構驅動重構方法」。這門課不使用紙本講義,Teddy會先引導學員思考一個核心的設計問題:「為什麼軟體架構重構很難?」,接著Teddy用實作的方式,藉由重構Task List Kata,把逐步將「整潔架構驅動重構方法」介紹給學員。在這個過程中,主要由Teddy在課堂上引導設計思考與重構程式碼給學員看,學員則採用mob programming的方式,以組為單位撰寫Teddy所安排的練習範例。</p> <p>課程涉及Clean Architecture與領域驅動設計的以下內容:</p> <ul> <li>分層原則:將軟體架構分為四層。</li> <li>建立Entities Layer:透過移除基本型別依戀壞味道尋找物件,建立通用語言。並使用DDD戰術設計模式,遵守Aggregate Root設計原則。</li> <li> 建立Use Cases Layer物件、區分Command Use Case與Query Use Case(在使用案例層讀寫分離)、依賴反轉、依賴注入、套用Repository、Controller、Presenter、DTO(Data Transfer Object)、Mapper等設計模式。。</li> <li>在Interface Adapters Layer形成Controllers、依賴注入、Presenter設計模式的使用方法討論。</li> <li>Main Component,使用Spring Boot作為依賴注入框架。</li> <li> 在Interface Adapters Layer增加Web Controllers (使用Spring Boot)。</li> <li> 使用Persistent Object 以滿足跨層原則,重新設計Repository介面與Mapper。</li> <li>使用關連式資料庫,透過 JPA (Java Persistence API) 使用Spring Boot連結H2資料庫。</li> </ul> <p>課程練習範例<a href="https://kata-log.rocks/task-list-kata" target="_blank">Task List Kata</a>有C#, Go, Java, Kotlin, Python, Ruby, Scala, Typescript這幾種語言,上課練習Teddy用Java語言示範,團隊mobbing也是使用Java。使用不同語言的學員在了解「整潔架構驅動重構方法」並看過一次Java重構範例後,在課後可選擇自己熟悉的語言重新練習一次,將你所常用的語言範例版本重構成整潔架構。</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczPyPDY9Ja43rptEsGaPVg-fOJ8aTB-aXuM5iAlyRIeXalI2cZCeKXxy-PixWtl4o-4DipGeFgF7EPRyycqdXwHhoV-s-QD5MRLx4ectJwAobGT4OOnCONhYJuK4JfZ1hsvZGCy_2uj4Zn3o0MxdiEDHQA=w598-h1500-s-no-gm?authuser=0" width="334" height="838" /></p> <p>▲Teddy套用Clean Architecture重構後的<a href="https://kata-log.rocks/task-list-kata" target="_blank">Task List Kata</a>,包含測試案例由3個Java程式變成106個。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">報名網址</font></strong></p> <p>本課程今年上半年兩個班次已經確定開課:</p> <ul> <li>2024年4月22、23日(一、二)</li> <li>2024年5月25、26日(六、日)</li> </ul> <p> <br /> 課程介紹與報名網址:<a title="https://teddysoft.tw/courses/refactor-to-ca/" href="https://teddysoft.tw/courses/refactor-to-ca/">https://teddysoft.tw/courses/refactor-to-ca/</a>,有興趣的鄉民歡迎參考。</p> <p align="center">***</p> <p>友藏內心獨白:一個課程四種享受—Refactoring、Clean Architecture、DDD與Teddy。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-33238655258769046832023-09-23T00:00:00.000+08:002024-03-11T11:40:05.974+08:00練好基本功<p>September 19 21:00~21:35</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczOgDNkH8r3mEN5eP6LBl6zQpy2ew5KvXbAubbKyBPN4FmSSJ1GP_UdsingaNThOeKychvkR6JYeo-GCnAKqRfJSYFaeHEfi1tAUrF_pYBybMPHyEZmHL5XfrXalvlVw933qRRPeZ8odmHeHrtRvap7zsQ=w1534-h1022-s-no-gm?authuser=0" width="471" height="314" /></p> <p> </p> <p>今天跟ezKanban團隊mobbing,Teddy說:「我這兩天跟別人講理,你們有沒有注意到一件事情,就是平常mobbing我會一直問你們定義。我知道定義,所以我不怕跟別人講理。」</p> <p>定義,很重要,也很無聊。想走捷徑的人,只看到別人光鮮亮麗的表皮,看不到別人蹲馬步時的付出。抄襲了皮,外表漂漂亮亮,骨子裏還是Teddy的指導教授常說的那句話:<strong><font color="#000000">「只有薄薄的一層,一刮就掉」</font></strong>。</p> <p>這不是,Teddy才說一句話,一個定義,一刮就掉。</p> <p>Teddy在北科上課會告訴學生:「Teddy和學生之間是平行關係,不是上下關係,Teddy只是在課堂上扮演老師的角色而已。上課Teddy會問你們很多問題,你們對我說的話如果有疑問,也可以直接說。如果我說錯了,最好可以打臉我。」</p> <p>Teddy帶的學生,應該都覺得Teddy很嚴格,甚至嚴厲。Teddy常跟他們說:「實驗室很單純,外面世界很黑暗,壞人很多。你不把專業能力練好,出去很容易被壞人唬得一愣一愣的。」我好歹也是在業界工作的人,先讓你們體驗一點人性險惡,以後出校門才有抵抗力。</p> <p>壞人的專業能力不會比你好,但他強在「夠壞」。這一點,我們永遠比不上,也不想比上。</p> <p align="center">***</p> <p align="left"> <br />Teddy在外面開課,都是先寫部落格文章,然後再整理成上課教材。Teddy可以說,收費課程的教材內容,絕大部分我都放在部落格上。Teddy做事不會留一手,知道什麼寫什麼,並提供參考資料。我巴不得鄉民們追朔源頭,多讀書。</p> <p>沒錢上課的學員,只要肯學,把「搞笑談軟工」部落格文章讀完、讀懂,Teddy相信在軟工這一塊,絕對可以排進台灣軟體工程師的前10%。付費上課的學員,是Teddy的衣食父母,沒有他們,泰迪軟體就經營不下去。 <br /></p> <p>這就是Teddy照顧小白、普通人、大神、有錢人、沒錢人的方法。</p> <p>所以,Teddy不主動參加社群活動,把時間留下來專心讀書、寫作、寫程式,還有這幾個月開始新的嘗試:錄製YouTube影片。以前的敏捷高峰會和這幾年的DDD TW年會,都是主辦單位看得起Teddy,主動邀請參加。不邀請了,人家不需要了,也就不參加了。</p> <p>台灣的社群活動,主辦單位很辛苦,也有很負責的講師。但是也有一群人,Teddy沒有實際測量過,但希望是一小群人,到處刷存在感、打知名度。這種人,空殼子又想出名收割韭菜,先搞小團體,放一些稻草人,再互相取暖。</p> <p> <br />這也是一種Pattern,叫什麼、什麼神來著? <br /></p> <p align="center">***</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-52226375182869925102023-09-22T08:02:00.004+08:002024-03-10T23:37:43.672+08:00改行寫網路小說算了(25)<p>September 20 07:39~09:34;11:16~11:48</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczNfa4bEmn59y6v8KRgoyNmCInsBxPCBNlE2SaQ8t5k6WmDL-oJBc2JXEqNi94lwkqli2tOvHichkuILA6AWbHWAyM9znSITKOUN-RpquJJ6U7L_a4DAdX6yQr0ly74f68zce1-ervJ58ihwIE6IOrP5-A=w1534-h818-s-no-gm?authuser=0" width="526" height="280" />   </p> <p>▲畫面來源:電影「九品芝麻官」</p> <p> </p> <strong><font color="#008000" size="5">上課</font></strong> <p>開場旁白:不知道是不是因為<strong><font color="#000000">教改</font></strong>的緣故,這些年學生的造句能力越來愈差,今天國文課<strong><font color="#000000">小明老師</font></strong>來幫同學做個造句練習。</p> <p align="center">***</p> <p>小明老師:請同學依據以下句型,在空白處填入你覺得最合適的文字,老師會依據同學的作答內容來評分,選出第一名給予表揚。</p> <p><strong><font color="#0000ff">我當然也是讀了_______的文章加入_______,也有拜過碼頭,但我沒錢上_______的課,所以得到了屬於我自己的見解(大多數和_______的見解是一樣的,但少數是不同的)。</font></strong></p> <p><strong><font color="#0000ff">我跟_______一樣的見解是_______,不同的見解是_______</font></strong></p> <p align="center">***</p> <p><strong><font color="#008000" size="5">水豚君同學造句練習</font></strong></p> <p>小明老師出完題目之後,班上最聰明的班長<strong><font color="#000000">水豚君</font></strong>同學第一個跳出來作答。</p> <p>水豚君:老師,我想好了,以下是我的造句。</p> <p>我當然也是讀了<strong><font color="#000000">愛因斯坦</font></strong>的文章加入<strong><font color="#000000">物理系</font></strong>,也有拜過碼頭,但我沒錢上<strong><font color="#000000">愛因斯坦</font></strong>的課,所以得到了屬於我自己的見解(大多數和<strong><font color="#000000">愛因斯坦</font></strong>的見解是一樣的,但少數是不同的)。</p> <p>我跟<strong><font color="#000000">愛因斯坦</font></strong>一樣的見解是<strong><font color="#000000">E = mc²</font></strong>,不同的見解是<strong><font color="#000000">物理的「理」就是品質</font></strong></p> <p align="center">***</p> <p><strong><font color="#008000" size="5">稻草人同學造句練習</font></strong></p> <p>水豚君話音剛落,副班長<strong><font color="#000000">稻草人</font></strong>同學接著說:</p> <p>我當然也是讀了<strong><font color="#000000">Kent Beck</font></strong>的文章加入<strong><font color="#000000">TDD陣營</font></strong>,也有拜過碼頭,但我沒錢上<strong><font color="#000000">Kent Back</font></strong>的課,所以得到了屬於我自己的見解(大多數和<strong><font color="#000000">Kent Beck</font></strong>的見解是一樣的,但少數是不同的)。</p> <p>我跟<strong><font color="#000000">Kent Beck</font></strong>一樣的見解是<strong><font color="#000000">先寫測試再寫程式</font></strong>,不同的見解是<strong><font color="#000000">TDD的「Development」就是品質</font></strong></p> <p align="center">***</p> <p align="left"><strong><font color="#008000" size="5">蔣孝華同學造句練習</font></strong></p> <p>第三位作答的是班上的開心果,蔣孝華同學。</p> <p>蔣孝華:老師,各位同學,我的造句和前兩位同學不同,比較生活化且呼應時事。</p> <p>我當然也是讀了<strong><font color="#000000">唐綺陽</font></strong>的文章加入<strong><font color="#000000">星座趨勢分析</font></strong>,也有拜過碼頭,但我沒錢上<strong><font color="#000000">唐綺陽</font></strong>的課,所以得到了屬於我自己的見解(大多數和<strong><font color="#000000">唐綺陽</font></strong>的見解是一樣的,但少數是不同的)。</p> <p>我跟<strong><font color="#000000">唐綺陽</font></strong>一樣的見解是<strong><font color="#000000">本周天蠍座的朋友水星逆行,須提防小人</font></strong>,不同的見解是<strong><font color="#000000">星座趨勢分析的</font></strong><strong><font color="#000000">「分析」就是品質</font></strong></p> <p align="center">***</p> <p align="left"><strong><font color="#008000" size="5">王同學造句練習</font></strong></p> <p>我當然也是讀了_______<strong><font color="#000000"></font></strong>的文章加入_______,也有拜過碼頭,但我沒錢上_______的課,所以得到了屬於我自己的見解(大多數和_______的見解是一樣的,但少數是不同的)。</p> <p>我跟_______一樣的見解是_______,不同的見解是_______</p> <p align="center">***</p> <p align="left">小明老師:王同學。</p> <p align="left">小明老師:王同學、王同學,老師在叫你怎麼不回應?</p> <p align="left">王同學:(抬頭望著老師)</p> <p align="left">小明老師:這個練習,你為什麼不作答?</p> <p align="left">王同學:老師…..這個句型…..我……不會寫(啜泣聲)</p> <p align="left">小明老師:王同學,沒關係。來,老師秀秀,不哭、不哭。</p> <p align="left">小明老師:這樣吧,老師看水豚君同學造句表現最好,一開頭就搬出舉世聞名的諾貝爾獎得主愛因斯坦<strong><font color="#000000">大神</font></strong>當例子(豹子頭:先嚇嚇他),整個氣勢、高度與企圖心就是不同。而且水豚君同學居然可以在<strong><font color="#000000">碼的多重宇宙</font></strong>,只花不到10秒,就同步發現愛因斯坦花了一輩子才想出來的E = mc²公式,堪為全班表率、台灣之光。</p> <p align="left">小明老師:這樣子,老師請水豚君同學下課後來教你,好不好?</p> <p align="left">王同學:….好….好……..喲……謝..謝..老.師…..</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">下課</font></strong></p> <p>小明老師:好,各位同學,今天課就上到這邊,等一下中午吃營養午餐,今天的主食是<font color="#000000">韭菜水餃</font>,請值日生稻草人同學跟蔣孝華同學到廚房領取。對了,記得要多拿一點,不要讓<font color="#000000">集體肚子</font>餓著了。</p> <p>小明老師:下課。</p> <p>水豚君同學:起立、立正、敬禮。</p> <p>全體同學:謝謝老師。</p> <p align="center">***</p> <p>友藏內心獨白:找水豚君來教王同學,這個小明老師,是GTO嗎?。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-84558288948274754412023-09-20T14:44:00.004+08:002024-03-11T16:39:36.245+08:00鐵帽子王事件的Teddy觀點<p>September 20 12:30~14:43 </p> <p><img height="339" src="https://lh3.googleusercontent.com/pw/AP1GczOiODCnzzoVgRmhK3e5C3hseRKgKsX_A7nDERO_8V2Nzqpu6t7KvCCk03V4XoAcrVDmyP9dJ7ddAlMpIHY3MQXZj_z0EBmUqWnHfe2xgqoeHJ48zMOZYOxsHDGg0-k8uGon2QqdSsLczi6HoYJLT1NZ0Q=w1274-h774-s-no-gm?authuser=0" width="558" /></p> <p>▲先看個貓照片消消火 XD</p> <p> </p> <p><strong><font color="#008000" size="5">緣起</font></strong></p> <p>Teddy明明是一位被鄉民認證過的文人,這幾天卻搞得好像變成武將一樣。Teddy就再講一個文人與<strong><font color="#000000">水球潘</font></strong>的故事,扳回點文人的形象。</p> <p>幾年前,水球潘加Teddy FB好友。認識Teddy的鄉民應該知道,Teddy基本上不加沒在真實世界見過面的人為好友,但Teddy加了水球潘。為什麼?<strong><font color="#000000">因為看到Teddy的指導教授鄭老師,也是水球潘的好友。</font></strong></p> <p>Teddy看了一下水球潘公開在FB的基本資料,事隔多年如果記憶有錯請更正,<strong><font color="#000000">他是銘傳大學畢業,當時正在唸台大資工碩士</font></strong>。</p> <p>Teddy知道鄭老師認識台大資工系的老師,於是自己腦補:<strong><font color="#000000">「會不會是教授們彼此有交流合作,因此當時還是學生身份的水球潘,才會認識鄭老師?」</font></strong></p> <p>Teddy也不會為了這麼小不拉機的事跑去跟鄭老師求證,於是就加了水球潘好友。</p> <p>這中間,如果沒記錯,Teddy應該沒有跟他有什麼互動。一直到疫情期間的某一天水球潘分享一個Clean Architecture範例。Teddy也在教Clean Architecture,因此下載來參考看看。這是一個很小的專案,Teddy覺得水球潘的Repository設計有可以改善之處,於是留言告訴他。</p> <p>當時Teddy對於水球潘的反應就覺得有點奇怪,說不上來,有一點他不太歡迎Teddy給他的建議的感覺。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">第二次接觸</font></strong></p> <p>第二次是水球潘在「<a href="https://www.facebook.com/groups/teddy.tw" target="_blank">搞笑談軟工臉書社團</a>」跟Teddy討論Value Object,<strong><font color="#000000">Teddy覺得水球潘對於DDD Building Block的一些基本認知,從Teddy的角度來看,是錯的。</font></strong>一開始Teddy很有耐心地跟他討論,但後來對於他那種幾乎可以說是<strong><font color="#000000">詭辯</font></strong>的態度,真的受不了,放棄。</p> <p>認識Teddy的人應該看得出來,Teddy對水球潘的耐心,已經超過對一般鄉民的耐心。為何?<strong><font color="#000000">還不是看在鄭老師的面子上,自己腦補說,總是老師認識的學生,就包容一些。</font></strong></p> <p>接下來就是今年8/11~13,Teddy在上「<a href="https://teddysoft.tw/courses/design-patterns-1/" target="_blank">Design Patterns這樣學就會了–入門實作班</a>」,有學員跑來問Teddy是否認識水球潘?<strong><font color="#000000">他用跟Teddy一樣的講法也在教Design Patterns。</font></strong></p> <p> </p> <p><img height="246" src="https://lh3.googleusercontent.com/pw/AP1GczNzR-iukkzncUhSDVb4uTaiYtYmT0mdHaE-MOuCW8aw-awBD8o_g1qc54H7I97bVDH-6USL9f-tetrJjUtTOKrNUtxWaVU9ZCAa_ciIuFwwF6jGYTOYgPq1s-pu6JjAwTjPGhgE0Uy45hP8Xtd51uwL8w=w1354-h684-s-no-gm?authuser=0" width="487" /> </p> <p> </p> <p>這事件,因為「鐵帽子王」事件(水球潘在搞笑談軟工FB社群送給Teddy的幾頂帽子)發生後,Teddy才在FB公開。如果Teddy在意,在八月上課當下就馬上在FB發文了酸水球潘,不會忍到前幾天。Teddy當時笑笑地告訴學員<font color="#ff0000">:<strong>「我知道這個人,他有加入搞笑談軟工社團,我跟他不熟,印象中是很認真(我知道他有弄社群活動)的人,也很有主見。之前他跟我在FB討論過事情,但雙方看法不同。」</strong></font></p> <p>當時問Teddy問題學員,好像有參加水球潘的活動,如果有看到,可以出來佐證。</p> <p><strong><font color="#000000">當時Teddy對於水球潘,一句不好聽的話都沒說,甚至還小小小的誇他。</font></strong></p> <p>為什麼?<strong><font color="#000000">還不是看在鄭老師的面子上。</font></strong></p> <p align="center">***</p> <p><strong><font color="#008000" size="5">現況</font></strong></p> <p>再來就是這幾天發生的事。上禮拜日下午,因為禮拜六剛參加完DDD TW 2023年會,想說在YouTube找看看有沒有更新什麼影片上去,於是下了DDD關鍵字,就看到水球潘的影片。點開一看,一開頭解釋DDD的Design,Teddy覺得基本觀念就錯了。以下是Teddy當時的貼文:</p> <p><strong><font color="#000000">閒來無聊在YouTube找DDD的影片,看到這個影片中說到:DDD的「Design的意思其實就是品質。」 <br />Domain-Driven Design的「Design」不是在講品質,而是「決定邊界」,這是基本中的基本。 </font></strong></p> <p><strong><font color="#000000">我只聽到這裡,後面的內容我就沒聽了。</font></strong></p> <p> </p> <p><img height="536" src="https://lh3.googleusercontent.com/pw/AP1GczN2rDgFY1Snue_ssTAnrkl_TBaoBWI2voT4TxUcwOtoJLP3Bg6KjHJ2UHppAAA146a9XsgzskwUFQvuP5GIbyTuzyDx95WnTID7hBQo-MSHN-SSMr2kSVFtFcVnnGmvAGno-qONQ2G4ioryi7h7yunMGQ=w1263-h1591-s-no-gm?authuser=0" width="425" /> </p> <p> </p> <p>水球潘一開始非常強勢,覺得Teddy是「<strong><font color="#000000">文人相輕</font></strong>」故意找他麻煩。<strong><font color="#000000">但他卻後來又自己承認,他的說法應該稍加修正。一般人遇到這種情況,就說聲:「噯呀,口誤」,這不就沒事了。</font></strong></p> <p>後續水球潘的回應,就是<strong><font color="#000000">給Teddy扣帽子、潑髒水</font></strong>。更有趣的是,水球潘還自爆內幕:</p><p><br /></p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZYYMbr3L_AHB6FSRFQgSHiMPzDJY18Fs_-hSF1EWWlMpKxYthovAgjVRmA4rWTvE2HoJb2jwsINHQWYj_M4o54qgRyLO2DZFyeEx_NGwJ0mNGyGuyVGCdGZZ7TAX0n_F2-L8ZQiG2G4S0-nz0x93Dq6zb9QFZWmFA6CCh7MpEQwkhyphenhyphenbWpu9dHRxwgmNoI/s1591/2023-09-18%20%E4%B8%8B%E5%8D%884.19.22.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1591" data-original-width="642" height="1296" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZYYMbr3L_AHB6FSRFQgSHiMPzDJY18Fs_-hSF1EWWlMpKxYthovAgjVRmA4rWTvE2HoJb2jwsINHQWYj_M4o54qgRyLO2DZFyeEx_NGwJ0mNGyGuyVGCdGZZ7TAX0n_F2-L8ZQiG2G4S0-nz0x93Dq6zb9QFZWmFA6CCh7MpEQwkhyphenhyphenbWpu9dHRxwgmNoI/w523-h1296/2023-09-18%20%E4%B8%8B%E5%8D%884.19.22.jpg" width="523" /></a></div><br /><p><strong><font color="#ff0000">既然他都自爆他向Teddy「致敬」的內幕</font></strong>,那就不好意思,水球潘已經用完<font color="#000000"><strong>鄭老師面子點數</strong>。</font></p><p align="center">***</p> <p><strong><font color="#008000" size="5">內心話</font></strong></p> <p>這中間還穿插一些,從Teddy的角度來看,<strong><font color="#000000">屬於幼幼班行為的事情</font></strong>,就不提了。</p> <p><strong><font color="#000000">事情演變至此,問題已經不是原本討論關於「Design」定義</font></strong>。Teddy早上去復健,吊脖子的時候在想,以Teddy的身分,在台灣軟工界,不敢說是大佬,好歹也是小佬:</p> <p><strong><font color="#ff0000">Teddy寫文章、經營泰迪軟體、寫書、辦C. C. Agile社群活動(曾經XD)、經營臉書社群,都已經十年以上,還有最近釋出DDD、DBC與BDD/SBE開源軟體、錄製YouTube頻道等。另外,Teddy的學歷,再怎麼不濟,好歹也是個本土博士(北科大資工)以及北科大兼任助理教授。</font></strong></p> <p><strong><font color="#ff0000">Teddy這樣的人,水球潘都敢跑到Teddy經營的「<a href="https://www.facebook.com/groups/teddy.tw" target="_blank">搞笑談軟工社團</a>」來鬧。如果Teddy這次又包容水球潘,日後一般軟體工程師對於水球潘的分享如果有任何疑問,誰還敢發言啊?</font></strong></p> <p>看到有些鄉民的思維,有時Teddy都會懷疑:<strong><font color="#000000">這是在台灣還是在中國?</font><font color="#000000">喔,你放在網路上公開廣告的教材內容,不准任何人公開提出疑問?那你不會不要放廣告膩,這樣就沒人會批評了啊!</font></strong></p> <p align="center">***</p> <p>若干年前神收割了一波小白韭菜,很抱歉<strong><font color="#000000">當時Teddy實力不夠,也不懂神的技術</font></strong>,幫不上忙。但Teddy自己偷偷覺得,<strong><font color="#000000">應該有小小阻擋了一下神的勢力蔓延到Agile這邊</font></strong>。賣東西卻不允許別人討論產品內容,態度還這麼<strong><font color="#000000">囂張</font></strong>,這是什麼樣的生意可以這樣?<strong><font color="#000000">想想這些轉職者與技術小白,真的是挺可憐的,想要努力變好,還要被騙。有種海龜寶寶破殼而出,努力爬向大海的途中,還有一堆海鳥等在旁邊要吃他們的即視感。</font></strong></p> <p align="center">***</p> <p>友藏內心獨白:我不入地獄,誰入地獄。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-73411697329253181822023-09-20T00:00:00.043+08:002024-03-11T11:47:22.859+08:00改行寫網路小說算了(24)<p>September 19 16:10~18:50</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczNJ9huHPYUjWo9RfRxOHd8mEw1ZC7oCqjAkIstgRDe_DnOqJNHFlmvoLkuLu3wQAGyee9hr3Iur868IimSnvtkm7HuPXJKXb5GsJ1ZwCEukOtfGLvUviG7QTn6p_wGokuWLuiyEdh7d5iUchR5zFigcCw=w1534-h1184-s-no-gm?authuser=0" width="386" height="298" /></p> <p> </p> <p><strong><font color="#008000" size="5">炸神誕生</font></strong></p> <p>約莫2500年前,羅剎國的土地上出現了一位傳說中的神級人物。其出生地、父母、生日與姓名均不詳。因成年後的職業為農夫,且養馬,江湖人稱<strong><font color="#000000">馬農</font></strong>。</p> <p>馬農雖為農夫,但並不耕作,專以收割別人土地上長出的<strong><font color="#000000">韭菜</font></strong>為生。因為騎馬,跑得快,<strike>上面坐個老太太</strike> 眾多受害農夫想抓馬農久已,可惜連他的馬尾燈都看不到,反倒吃了滿嘴灰。於是農夫們集體討論想出一個辦法,準備很多鞭炮,當馬農出現時,用鞭炮炸他身體,把他轟下馬。</p> <p>某日,馬農出現在羅剎國的霄霄村,準備再次收割別人農田中的韭菜。就在此時,一位正好在田邊樹陰下大便的農民發現馬農。農夫顧不了WIP,稍微施力剪斷大便,立馬讓WIP歸零。農夫緊接著來到工作間拿出準備以久的鞭炮丟向馬農,並大喊:「<strong><font color="#000000">炸他身體、炸他身體」</font></strong>。無奈,馬農<strong><font color="#000000">騎術優良,能打勝仗</font></strong>,一轉眼就消失在樹林中,隱姓埋名,從此江湖中再也無人見過馬農。此後,農夫們改稱馬農為<strong><font color="#000000">炸身</font></strong>---炸他身體的簡稱。炸身、炸身,農夫們希望喊久之後,下次再見到馬農,可以炸他身體,一炮斃命。</p> <p>若干年後,炸身的傳奇故事,傳來傳去,不知怎麼地居然被神格化。炸身被尊稱為<strong><font color="#000000">炸神</font></strong>,持續保有一群忠實信徒,不離不棄。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">主角小刀</font></strong></p> <p>本故事主角,<strong><font color="#000000">小刀</font></strong>,是村民所認證的<strong><font color="#000000">三字經知識權威</font></strong>,以三字經教學為生。小刀平日閉門在家讀經,鮮少涉足農田,自然不知曉炸神這號人物。小刀第一次看到神的名字,是在「小刀讀經班」的課堂上。</p> <p>消失多年的神,再次下凡化身為一位普通書生。神在課堂上的表現,並沒有特別引人注意。上課當時,小刀有眼無珠,還不知道眼前這位平凡書生,就是大名鼎鼎的炸神(奏樂,噹噹)!</p> <p>在課堂上的經文討論活動中,神總是特立獨行。不是默默不語,自顧自的下著他帶來的<strong><font color="#000000">一盤大旗</font></strong>(當下流行的一種虛擬挖人礦桌遊)<strong><font color="#000000"></font></strong>,就是大言不慚地發表一些驚世駭俗的言論。難道是神覺得小刀的三字經講解的不到位,所以故意搗蛋引發關注嗎?好像也不是這樣,因為神還報名了一次夜間讀經活動,應該是對小刀的三字經有信心才是。神在課堂上的表現實在是太平凡了,完全沒有神該有的樣子,小刀一直以為神就是個普通書生,還好心告訴神一些關於讀經的建議。</p> <p>直到多年後,才有高人在夢中告訴小刀,當年那位在課堂上不起眼的書生,其實就是神,大名鼎鼎的炸神!</p> <p align="center">***</p> <p>所有的大神都有一個共同特點,就是具備一擊必殺的<strong><font color="#000000" size="6">神邏輯</font></strong>。神用神的視野「發現」平常人老早就已經知道的東西,並將其宣稱為<strong><font color="#000000">神發明</font></strong>。例如,神再次發現了火,可以煮飯;水,可以喝;<strong><font color="#000000">屎,可以施</font></strong>……….肥。多麼了不起的的發明啊!一般人類,是無法用<strong><font color="#000000">人邏輯</font></strong>與神溝通。因為,神,活在神界!</p> <p>神邏輯還可以不必遵守人間一切物理定律。</p> <p><strong><font color="#000000">神說智慧,就有了智障</font></strong></p> <p align="center"> </p> <p><font color="#000000" size="5"><strong>不可以質疑你的神!</strong></font></p> <p align="center">***</p> <p>有人說,神的口才很好,擁有大批信眾。每逢初一、十五,信徒提供大量韭菜供養神,讓神過著優渥的生活。關於神的好口才這個傳聞,小刀無法證實。因為在短短兩次聆聽神諭的機會中,化身為書生的神,是那麼的低調。涉世未深的小刀,著實體察不出神的嘴砲神功之威力。</p> <p>有了信徒的供養,神此後不用再騎馬割韭菜。神賣掉了馬,出門改搭<strong><font color="#000000">屋駁</font></strong>,一種有蓋子的轎子,負責在霄霄村內短期通勤之用。</p> <p>又過了幾年,神再度轉趨低調,眾多信徒們已有多年沒有神的消息。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">神蹟再現</font></strong></p> <p>此刻,神再次降臨。本番下凡,雖然神的性別不明、長相不同,但能力依舊。</p> <p>神在下凡時大喊一聲:</p> <p>萬能的天神,請賜與我葛雷堡神奇的力量!</p> <p><strong><font color="#000000">神說獨創,就有了模仿</font></strong></p> <p>可憐,凡間</p> <p><strong><font color="#000000">小白</font><font color="#000000">花</font></strong>盛開的田裡</p> <p>韭菜塗炭</p> <p align="center">***</p> <p>後人寫詩一首以茲紀念:</p> <p><strong><font color="#000000">智慧曰智障</font></strong></p> <p><strong><font color="#000000">獨創</font><font color="#000000">喚<font color="#000000">模仿</font></font></strong></p> <p><strong><font color="#000000">施展神邏輯</font></strong></p> <p><strong><font color="#000000">引燃自爆彈</font></strong></p> <p align="center">***</p> <p align="left">友藏內心獨白:橫批--集體智障。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com2tag:blogger.com,1999:blog-1298974142445162186.post-88743708179609518262023-09-19T06:18:00.001+08:002024-03-11T11:48:56.176+08:00創業初期的陳年往事<p>September 19 05:01~06:17</p> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczMRaf9PlrFF4em9zbtN_orVNd2uBnbNQdsHgGMrj_ycvbpQ4KHU0cpbPnxbEA1HIT-jwRyrNFIcZfby-zpDHOaXQm8ol2HS32P5Nm38W_0ygQ2ChiiMx57Tv7Fd1Jbjtyere9x3wJpwYdeBpTYFUIDRPg=w1534-h944-s-no-gm?authuser=0" width="653" height="402" /> </p> <p>▲就好像吃飯睡覺一樣,還不是你做什麼他也做什麼</p> <p> </p> <p><font color="#008000" size="5"><strong>緣起</strong></font></p> <p>上周六Teddy參加了DDD TW 2023年會活動,禮拜天下午閒來無事在YouTube上找DDD的影片來看。滑到某個影片,沒想到因此發生了一連串讓人哭笑不得的事,細節Teddy就不重複描述,今天想聊一件陳年往事。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">永遠的泰迪之友</font></strong></p> <p>十年前泰迪軟體剛成立沒多久,有一位<strong><font color="#000000">YA先生</font></strong>主動找Teddy聊天。YA先生早年也經常出現在「搞笑談軟工部路格」文章,當時他在新創公司上班,遇到一些問題。因為敏捷開發是當時泰迪軟體的主要業務,YA先生想了解導入敏捷開發能不能對他的工作有所幫助。</p> <p>YA先生是一位非常熱心的人,幾年前他去了德國工作,是Teddy心目中永遠的泰迪之友。因為YA先生是泰迪軟體成立之後第一位找Teddy「諮詢」的客戶,Teddy當時也是剛創業的菜鳥一隻,雙方約在北科大對面的伯朗咖啡聊聊天。Teddy並沒有跟YA先生收費,只是讓他請了一杯飲料。可能是因為這樣,YA先生覺得過意不去,好心找Teddy去跟他們公司的新創顧問談一談,看看對Teddy的創業有沒有什麼幫助。</p> <p align="center">***</p> <p>Teddy記得對方是一位長相帥氣的年輕ABC(以下就以ABC稱呼這位先生),對話的時候英文、中文交錯使用,好像在美國某創投公司待過。由於年代久遠,細節Teddy已經記不清了。ABC聊到創投的一些生態,他說以前他們常常看很多新創的案子,因此衍生出一些快速篩選案子的原則。就好比大公司HR每天可能收到幾百份履歷,總不可能每份都逐一閱讀,因此用學歷、畢業學校作與工作年資為條件,快速過濾掉不符標準的求職者。</p> <p>ABC:有一種case,就算是business model再棒,我們絕對不會投。</p> <p>Teddy:為什麼?</p> <p>ABC:<strong><font color="#000000" size="5">因為非法!</font></strong></p> <p>ABC:我們看過一個case,公司計畫從YouTube下載各種影片、音樂,然後再提供服務給第三者。</p> <p>ABC:<strong><font color="#000000">侵權、非法的case,就算他們現在活耀的使用者數量再多,我們都不考慮,這是底線。</font></strong></p> <p align="left">Teddy原本以為新創公司因為新的業務模型,遊走在法律邊緣是很常見的,不然當年YouTube一開始營運上面不是有很多非法的影片。不知道是這位ABC待過的創投比較不一樣,還是這是一個業界標準,當時Teddy也沒多問。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>老美對於做生意不可剽竊他人智慧財產這件事,相對而言還是比較嚴肅看待。在美國念書,如果作業被抓到抄襲,是一件很嚴重的事情。</p> <p>抄襲者為什麼要抄襲別人?因為可以<strong><font color="#000000">彎道超車</font></strong>,自己不用花很多時間,就可以看起來好棒棒。抄襲的過程中,不只被抄襲的人受害,消費者也受害。</p> <p>先幫「小白」默哀三秒! <br /></p> <p align="center">***</p> <p>友藏內心獨白:用新的謊去圓舊的謊,就容易自爆。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-7687308299258846432023-07-10T18:36:00.001+08:002024-03-11T16:13:22.372+08:00搞笑談軟工YouTube頻道開張<h6><font style="font-weight: normal;" size="3">July 10 18:24~18:36</font></h6> <p><img src="https://lh3.googleusercontent.com/pw/AP1GczNUYoHMww-SIwC6l4E24u-wlw18HflazWbGMhifc9DmVySS_asTlfAhUKVkawsH2MdWgzF5bPBDdTzUuZjg8mVQx-5q0Ls_vqzdt_pFutYaE2UkdUqTlnDzGacZrWb1MTz83Sk2OKgfwEjGkvCTLIGELQ=w1174-h924-s-no-gm?authuser=0" width="560" height="441" />  </p> <p>▲用講的好像比用打字寫部落格文章還要簡單啊</p> <p><strong></strong></p> <p><strong><font color="#008000" size="5">緣起</font></strong></p> <p>6月20日Teddy舉辦「你就是寫太多測試才會沒時間: 在領域驅動設計與事件溯源架構中使用合約式設計」線上活動,並錄影。活動結束後Teddy準備把影片放到YouTube上,原本泰迪軟體就有一個YouTube頻道但是都沒有經營,只放了兩個影片。想一想,這幾年Teddy比較少寫部落格文章(常寫文章打太多字、用太多滑鼠,很容易肩頸痠痛),幹脆試著錄影用講的,看看能不能吸引到另外的客群幫泰迪軟體拉點生意。</p> <p>於是,就開了搞笑談軟工YouTube頻道。目前打算先來個一年的每日更新,Teddy每天放一個影片,分享一些Teddy覺得對軟體開發人員會感到有趣且有用的知識(常識XD)。  </p> <p>頻道網址在此:<a title="https://www.youtube.com/@TeddyChen" href="https://www.youtube.com/@TeddyChen">https://www.youtube.com/@TeddyChen</a>,歡迎舊雨新知幫Teddy訂閱、按讚、分享、開啟小鈴噹 XD。</p> <p align="center">***</p> <p>友藏內心獨白:影片一刀不剪,因為我也不會剪。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-33373547228895014552023-07-04T19:22:00.005+08:002024-03-11T16:47:51.247+08:00買到還不錯用的筆電收納架<h6><font size="3" style="font-weight: normal;">July 04 18:48~19:22</font></h6> <p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUcVrmQ4a8OBPoDXZ2dWzoteJ4U_UDxDjGiCe-lBiW8a7xCleAljxCZC4tvwAJu6-HsHdyGtCigX1ik8DUsaw4wgc7uJT_XGibzCVAmDb-VFdqerS2X-ttFp41acMMOQ5jALHmFC7H19qBNs9A37AKbKW3OBFZ_PBAvukZuU_N1mGlXsgg91_fn9MlAjgX/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.07.15.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1172" data-original-width="1534" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUcVrmQ4a8OBPoDXZ2dWzoteJ4U_UDxDjGiCe-lBiW8a7xCleAljxCZC4tvwAJu6-HsHdyGtCigX1ik8DUsaw4wgc7uJT_XGibzCVAmDb-VFdqerS2X-ttFp41acMMOQ5jALHmFC7H19qBNs9A37AKbKW3OBFZ_PBAvukZuU_N1mGlXsgg91_fn9MlAjgX/w469-h358/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.07.15.jpg" width="469" /></a></div><br /> <p></p> <p>▲圖1:把兩台筆電立起來收納</p> <p><strong></strong></p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>Teddy有兩台筆電,第一台是2018年從Amazon買的LG gram 15,這台筆電非常棒,很輕續航力又強,這的是沒什麼好挑的。唯一的問題是年華老去,速度有點跟不上,去年不知哪根神經不對勁,居然買了Asus UX5401Z。Asus 這台筆電,才14吋但體感重量比起LG gram 15,有種沉甸甸的感覺。如果是買水果沉甸甸也就算了,買筆電沉甸甸真的不優。</p> <p>不過今天不是要談筆電,而是要談筆電收納。Teddy平常在家中工作,使用的是2019 iMac 27”,筆電只有外出上課時才會用到。這兩台筆電,平時就被Teddy丟在不同的位置,平躺著有點占空間。前天晚上在PChome上面隨意選了「<strong><font color="#000000">YUNMI 重力感應筆電直立式收納支架</font></strong>」,收到之後沒想到還不錯用。今天介紹這個支架,提供有需要的鄉民做為參考。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">非廣告</font></strong></p><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlj-4rJD6kF1v95UWqc8Qsom1i51BaNxCsGxs-WMC6p2guALvx309jgZHq5Z0i7nVb0hovl7NNmDJF80h0A5WFRg7CnnJ5Ss10RrxUGF012RTD1njkpTnHNmoaAvXVzZTBI8Y4GjKKkLUpZe2diDEYHtHE3sC9Flldlg-o5cDjRWthor_awA8gA-dUrM2I/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.05.28.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="624" data-original-width="1534" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlj-4rJD6kF1v95UWqc8Qsom1i51BaNxCsGxs-WMC6p2guALvx309jgZHq5Z0i7nVb0hovl7NNmDJF80h0A5WFRg7CnnJ5Ss10RrxUGF012RTD1njkpTnHNmoaAvXVzZTBI8Y4GjKKkLUpZe2diDEYHtHE3sC9Flldlg-o5cDjRWthor_awA8gA-dUrM2I/w558-h227/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.05.28.jpg" width="558" /></a></p><p><br /> ▲圖2:PChome的產品介紹畫面</p> <p> </p> <p>502元,價錢還可以。Teddy以前曾經想買一個筆電收納架放Apple Air與MacBook Pro,但後來換了iMac之後就作罷。這次是整理書桌,順便把找個架子收納筆電。看到這個產品,可以收納多台筆電,抱著可能採雷的覺悟,就買了。</p> <p> </p> <p>▼開箱之後就一個塑膠做的架子,長成下面這樣。</p> <p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-Bbmd-wyyghbUP3HQuHok7zVcs_mlxgyuBfN4Bu_EkdOknLD2oLZwVu_tyr3pYJ8LxUrLLHyH-3WH5IoxNHsO3k3MCMtcHfVSyb5F-qfl_8zeuOsiyn3lkaBO8l8uJddZeY9OJ7OM5TcCngXBc-fjYLPfj_W9WQHgrnHfFb4Besa4C9by_VWA-rWUUT_-/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.10.37.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1217" data-original-width="1534" height="333" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-Bbmd-wyyghbUP3HQuHok7zVcs_mlxgyuBfN4Bu_EkdOknLD2oLZwVu_tyr3pYJ8LxUrLLHyH-3WH5IoxNHsO3k3MCMtcHfVSyb5F-qfl_8zeuOsiyn3lkaBO8l8uJddZeY9OJ7OM5TcCngXBc-fjYLPfj_W9WQHgrnHfFb4Besa4C9by_VWA-rWUUT_-/w419-h333/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.10.37.jpg" width="419" /></a></div><br /> <p></p> <p>▼準備放下兩台筆電,還沒放到底部,此時筆電架還沒有<strong><font color="#000000">夾緊</font></strong>。</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsgrn99PkBA-I91X85ndVDo-es90m_X7H4ZLtbL_PoobJtXEqmIzn5dsPlzKsyuoHE4Z9RxFueSL0qcenvXi2sR_RFHKpTm6QFxqqaXs9mOU7NEsDxXOqMi_JHo7FxbCqUpvhuXtFvgEpyZkZKlb45ixJzGHpbTz1_3c6o4-2N8EfSn3zo_qV0Ju2PwYvl/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.12.39.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1154" data-original-width="1534" height="324" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsgrn99PkBA-I91X85ndVDo-es90m_X7H4ZLtbL_PoobJtXEqmIzn5dsPlzKsyuoHE4Z9RxFueSL0qcenvXi2sR_RFHKpTm6QFxqqaXs9mOU7NEsDxXOqMi_JHo7FxbCqUpvhuXtFvgEpyZkZKlb45ixJzGHpbTz1_3c6o4-2N8EfSn3zo_qV0Ju2PwYvl/w430-h324/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.12.39.jpg" width="430" /></a></div><p><br /></p><p>▼放到底部之後,筆電架就將兩台筆電夾緊。</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtcKfb3afdlNdmmaSe6vNhnxCVnezmFPC5-PZMkx8N8c9z_-JFhnLfIILjF4WMhCTC7GFoPCRtX6Q8V48F_GeO_YJp5NDGUrPQUoKWYTj_E-DyZEY7j3hdTe3bwUDicbbaYrVedijkFzPv0y4OQS0u_AyWlHWIi7MIjSeDbczJXN1uLERyw26m5-8q2bYG/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.14.15.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1140" data-original-width="1534" height="297" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtcKfb3afdlNdmmaSe6vNhnxCVnezmFPC5-PZMkx8N8c9z_-JFhnLfIILjF4WMhCTC7GFoPCRtX6Q8V48F_GeO_YJp5NDGUrPQUoKWYTj_E-DyZEY7j3hdTe3bwUDicbbaYrVedijkFzPv0y4OQS0u_AyWlHWIi7MIjSeDbczJXN1uLERyw26m5-8q2bYG/w400-h297/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.14.15.jpg" width="400" /></a></div><p> </p><p>▼空拍圖</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_1KvaVQ81thoVd-vhQacK3p2ZaTsMMfWcVqkqnJ3KlRi-8zN2al7xX5UvbDqloVDv4EFHtEjQ1xTxG3urnuTekGpd5rfP-vRmVLE5h6rCQLqbl1HCxqGwrnt9cYV9M1S18P88egrKQqINP-mSdW-OM2OMREsJlYnyZzAPrf9pAFspb5ljNasLc_u9j7z3/s1139/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.15.46.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="571" data-original-width="1139" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_1KvaVQ81thoVd-vhQacK3p2ZaTsMMfWcVqkqnJ3KlRi-8zN2al7xX5UvbDqloVDv4EFHtEjQ1xTxG3urnuTekGpd5rfP-vRmVLE5h6rCQLqbl1HCxqGwrnt9cYV9M1S18P88egrKQqINP-mSdW-OM2OMREsJlYnyZzAPrf9pAFspb5ljNasLc_u9j7z3/w400-h200/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.15.46.jpg" width="400" /></a></div><p><br /></p><p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>▼收納之後將筆電放在書桌後方,留一條Type C線讓兩台筆電輪流充電,還行。</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ4kMXeHJLQQZhmcNl4eDT0B5ubUMFzfGdacXRgOyU0PZaXVwq1VCrcrLah9hF1baxSqy-JEc1rHagvITsOVUMMwEpHBfAdnyH6UGv99yJuL39Qp5FINifqKbhuMtdXzJMv87blPFQfKuFW3FmRv3RZNsFlohWU5YbFOI-xqBDiefZ1if8AKVA2OUDtKrZ/s1534/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.18.09.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1149" data-original-width="1534" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ4kMXeHJLQQZhmcNl4eDT0B5ubUMFzfGdacXRgOyU0PZaXVwq1VCrcrLah9hF1baxSqy-JEc1rHagvITsOVUMMwEpHBfAdnyH6UGv99yJuL39Qp5FINifqKbhuMtdXzJMv87blPFQfKuFW3FmRv3RZNsFlohWU5YbFOI-xqBDiefZ1if8AKVA2OUDtKrZ/w400-h300/%E6%88%AA%E5%9C%96%202023-07-04%20%E4%B8%8B%E5%8D%887.18.09.jpg" width="400" /></a></div><p> </p> <p align="center">***</p> <p>友藏內心獨白:可能因為難得沒採雷所以特別高興。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com1tag:blogger.com,1999:blog-1298974142445162186.post-91274365024850576372023-07-03T23:37:00.002+08:002024-03-11T16:49:10.127+08:00為什麼開發人員會過度設計?<h5><font style="font-weight: normal;">July 03 23:10~23:45</font></h5> <p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8fDac_0HP2yq4WMb_H3yZv70CU5QD4iZl5PiDaIuJhRevkWsrEDoMQFyLrEkC5aI9yfIsxpbLLUA_1xra27bVdKNJ1xF3M1thPOVFZi2HPxqljpuKu03v6zO99zOS7K46Gn40VH-2Aq1cdbiYugZSiP9so_1qkI9hknqNVzgsuQGYI9cFqu1155dVwbfw/s1534/%E6%88%AA%E5%9C%96%202023-06-30%20%E4%B8%8B%E5%8D%881.07.02.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1115" data-original-width="1534" height="291" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8fDac_0HP2yq4WMb_H3yZv70CU5QD4iZl5PiDaIuJhRevkWsrEDoMQFyLrEkC5aI9yfIsxpbLLUA_1xra27bVdKNJ1xF3M1thPOVFZi2HPxqljpuKu03v6zO99zOS7K46Gn40VH-2Aq1cdbiYugZSiP9so_1qkI9hknqNVzgsuQGYI9cFqu1155dVwbfw/w400-h291/%E6%88%AA%E5%9C%96%202023-06-30%20%E4%B8%8B%E5%8D%881.07.02.jpg" width="400" /></a></div><br /> <p></p> <p>▲圖1:軟體開發的三個圈圈</p><p><br /></p> <p><strong></strong></p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>今天學生問Teddy一個問題:<strong><font color="#000000">為什麼會產生過度設計(Over Design)?</font></strong>開發人員的時間不是都很寶貴嗎,怎麼會做出「超出目前需要」的設計?</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">原因很多</font></strong></p> <p>造成過度設計的原因很多,常見的有:</p> <ol> <li><strong><font color="#000000">需求不清</font></strong>:很多開發人員沒有機會或意願接觸業務人員或領域專家去<strong><font color="#000000">持續釐清需求</font></strong>,因此在開發系統的時候,只能依據文件或是對於需求模糊的認知來做設計。因此,很可能產生存在於設計中,但卻不存在於需求中的軟體,如圖1的區域6。</li> <li><strong><font color="#000000">超前佈署</font></strong>:不管需求是否明確,開發人員通常會有一種「預留彈性」的傾向。「雖然客戶現在沒有要求,但是這個地方如果可以這樣再那樣設計,套某某設計模式,以後需求改變就輕鬆多了。」</li> <li><strong><font color="#000000">改動架構成本很高</font></strong>:這一點和超前佈署有關,但發生在軟體架構層面。因為軟體架構的修改成本很高,所以如果可以在架構設計階段就「留有彈性」,那未來的日子就好過多了。很可惜,此時的彈性絕大部分都是開發人員腦補的結果,不但沒有幫未來鋪路,反倒增加人為的複雜度。</li> <li><strong><font color="#000000">展現技術能力</font></strong>:有時候,過度設計就僅是開發人員展現自我技術能力的結果。「08學得一身功夫,就該好好施展一番。先來個23個設計模式漱漱口。」</li> <li><strong><font color="#000000">演化的結果</font></strong>:有時候系統初期過度設計並不明顯,但隨著系統不斷開發與重構,演化的結果導致系統存在一些過於彈性的設計。例如,想要code reuse與去除重複程式碼,就很容易產生過於彈性的設計。</li> </ol> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>過度設計的原因很多,Teddy覺得主要還是對於需求的理解是否到位,以及對於改變的反應能力是否足夠,會造成開發人員有沒有信心只要當下做出「將將好的設計」(Just Enough Design)即可。從敏捷開發的角度來看,採用TDD/BDD/SBE的開發方法,可以讓 (Specification = Test) = Program,在理想狀況下,可以大幅減少過度設計。</p> <p align="center">*** </p> <p>友藏內心獨白:要做到Lean,並不容易。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com1tag:blogger.com,1999:blog-1298974142445162186.post-13351538725765390572023-06-20T11:46:00.005+08:002024-03-11T18:07:27.757+08:00你就是寫太多測試才會沒時間(2):自動化測試是金字塔嗎?<p>June 20 09:55~11:46</p> <p> </p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi05gmukuN5lelBYOLspaQjxTTNxntxQM9wv8qLc5o4wyZ6isofYJZZvTElLuCu_I1QY7ElCxwOppqodjxcx2vLkoM3-E25GOkKw-3gUnvQ98DBn8dfgTo4pZr2hNBIdMCkLYedksUwnBevo9okhq61XvRqcsubThnB6j2h0Q0dXbnzAAep2NX0EtklVVX-/s1534/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%889.54.29.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="560" data-original-width="1534" height="217" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi05gmukuN5lelBYOLspaQjxTTNxntxQM9wv8qLc5o4wyZ6isofYJZZvTElLuCu_I1QY7ElCxwOppqodjxcx2vLkoM3-E25GOkKw-3gUnvQ98DBn8dfgTo4pZr2hNBIdMCkLYedksUwnBevo9okhq61XvRqcsubThnB6j2h0Q0dXbnzAAep2NX0EtklVVX-/w593-h217/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%889.54.29.jpg" width="593" /></a></div><br /><p></p> <p>▲圖1:傳統的自動化測試形成金字塔形狀,單元測試占比最大</p><p><br /></p> <p><strong></strong></p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>傳統軟體自動化測試形成如圖1的金字塔形狀,作為驗證個別軟體元件正確性的<strong><font color="#000000">單元測試</font></strong>數量做多,整合測試次之,透過使用者介面驗證系統功能的使用者驗收測試或稱為End-To-End測試數量最少。</p> <p>多年來,很多撰寫自動化測試的開發人員心中大致依循著測試金字塔去規劃與撰寫他們的測試案例。但是,雖著單元測試越來越多,系統功能不斷地演化以及持續重構改善設計,開發人員經常會發現:<strong><font color="#000000">「靠,剛剛的修改造成N個單元測試失敗。更慘的是,我看不懂這些失敗的單元測試為什麼失敗。」</font></strong></p> <p>整合測試因為以黑箱的角度測試「系統功能」,因此相對而言對於軟體修改與重構的<strong><font color="#000000">抵抗力</font></strong>比較高。但光使用整合測試驗證系統品質存在兩個問題:</p> <ol> <li><strong><font color="#000000">發生錯誤時不易除錯(不容易明確看出錯誤發生在哪個地方)</font></strong></li> <li><strong><font color="#000000">執行速度比單元測試要慢很多,導致回饋路徑較長,降低開發人員持續測試的意願</font></strong></li> </ol> <p>如果可以<strong><font color="#000000">減少單元測試的數量同時維持單元測試的效果</font></strong>(避免上述兩個問題)<strong><font color="#000000"></font></strong>,自動測試有沒有可能從金字塔變成如圖2所示的<strong><font color="#000000">菱形</font></strong>?</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwFNw8G1gJuDEBxF7VwzYBz32UlYIVLgf1w58l0Z2KW2FhipLBgwv7YFjJqvGPo9v0knMr60lkbFcK7aVfxwa_m1pRPqzJZDe5zEkO-blmK67j1WBQ8f0Pkdf7oeAetckOhBrcCFHgiy0sWPSchJkImWyVvCt9J5XANrGxf8ZjU6qJQD6pIR9Lk4IXhA9h/s1534/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8810.19.33.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="889" data-original-width="1534" height="231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwFNw8G1gJuDEBxF7VwzYBz32UlYIVLgf1w58l0Z2KW2FhipLBgwv7YFjJqvGPo9v0knMr60lkbFcK7aVfxwa_m1pRPqzJZDe5zEkO-blmK67j1WBQ8f0Pkdf7oeAetckOhBrcCFHgiy0sWPSchJkImWyVvCt9J5XANrGxf8ZjU6qJQD6pIR9Lk4IXhA9h/w400-h231/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8810.19.33.jpg" width="400" /></a></div> <p>▲圖2:自動化測試有沒有可能是菱形?</p> <p> </p> <p>今天這一集先談第一個問題,下一集再談測試執行速度的問題。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">用合約取代單元測試</font></strong></p> <p>測試只是一種<strong><font color="#000000">驗證系統行為</font></strong>的方法,在軟體工程中除了測試以外還有一種也算是<strong><font color="#000000">廣為人知但較少人做</font></strong>的方法:<strong><font color="#000000">合約式設計(Design by Contract;DBC)</font></strong>也可以規範系統行為。DBC的作法很簡單,模仿真實世界人類的合約,幫軟體元件撰寫合約。軟體合約主要包含:<strong><font color="#000000">pre-conditions(前置條件)</font></strong>、<strong><font color="#000000">post-conditions(後置條件)</font></strong>以及<strong><font color="#000000">class invariants(類別不變量)</font></strong>。為了簡化起見之後的範例只討論前兩者,先忽略class invariants。</p> <p>圖3是ezKanban系統中的Workflow aggregate單元測試,這也是傳統用來驗證程式行為的做法。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTb67Qbw8qPa1HxcpQjoNICyElB0Pg1y4_SiXtwX9UGwZAqXA-PTgKqQbXXW4u27QPpOtaMNZZv9pjIfd0iA-QcCBLBWkDabWNhfRIPkIsUVJFhcpB4_7Dh_SWpXm0us-d4oyJ1WLm0GZrTia3_1DAtun0IlyGfdxQLtEQCGMRjqrUtWInf65qb8XAVXBl/s1374/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8810.56.23%20(1).jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="722" data-original-width="1374" height="303" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTb67Qbw8qPa1HxcpQjoNICyElB0Pg1y4_SiXtwX9UGwZAqXA-PTgKqQbXXW4u27QPpOtaMNZZv9pjIfd0iA-QcCBLBWkDabWNhfRIPkIsUVJFhcpB4_7Dh_SWpXm0us-d4oyJ1WLm0GZrTia3_1DAtun0IlyGfdxQLtEQCGMRjqrUtWInf65qb8XAVXBl/w577-h303/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8810.56.23%20(1).jpg" width="577" /></a></div> <p>▲圖3:Workflow單元測試</p> <p> </p> <p>圖4是幫Workflow的建構函數撰寫合約的程式範例,其中第42~44是pre-conditions,第48~61是post-conditions,第46行是method body。當程式執行的時候,只要pre-conditions和post-conditions都通過,那麼不管method body如何實作,它的<strong><font color="#000000">行為</font></strong>就被視為具備<strong><font color="#000000">正確性(correctness)</font></strong>。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMb0S_lTBExSmMgILVVBILR2wpboYXr0s070fO1qYswcTi65Cy4YZsXUTpuNhYscz6ktHNAq6DWubApNUhdl3GnZsCO-xqahJ4DOsd049J_8Nd0S0U6T8o5RQzZlblq58UnXOAj6_hsavBjKtnmNVL8i5ZVee3Uv2KaulCQb-sa0reCy4jC5y-cHxpMbx6/s1534/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8811.10.18.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="839" data-original-width="1534" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMb0S_lTBExSmMgILVVBILR2wpboYXr0s070fO1qYswcTi65Cy4YZsXUTpuNhYscz6ktHNAq6DWubApNUhdl3GnZsCO-xqahJ4DOsd049J_8Nd0S0U6T8o5RQzZlblq58UnXOAj6_hsavBjKtnmNVL8i5ZVee3Uv2KaulCQb-sa0reCy4jC5y-cHxpMbx6/w566-h310/%E6%88%AA%E5%9C%96%202023-06-20%20%E4%B8%8A%E5%8D%8811.10.18.jpg" width="566" /></a></div> ▲圖4:幫Workflow寫合約 <p align="center">***</p> <p>看到圖4的範例,鄉民們可能會覺得:「類別的合約就是幫method做輸入參數檢查,然後把平時寫在單元測試裡面的assertions移到production code裡面而已啊。」這樣做雖然不用寫單元測試,但是這些合約也是程式碼,也是要花時間撰寫與維護,這樣有省到時間嗎?</p> <p>這個問題可以從幾個方面來討論:</p> <ul> <li><strong><font color="#000000">不用寫arrange和act</font></strong>:撰寫單元測試有三個步驟,<strong><font color="#000000">arrange</font></strong>、<strong><font color="#000000">act</font></strong>和<strong><font color="#000000">assert</font></strong>。寫成合約之後,assert部分還是存在,但少了arrange與act。寫過自動化測試的鄉民們應該很有感,很多時候花在arrange的時間甚至比assert還多。減少arrange與act除了少掉撰寫的時間,也避免了之後需求變更或軟體重構導致需要<strong><font color="#000000">維護</font></strong>單元測試的時間。</li> <li><strong><font color="#000000">和Production Code生活在一起有助於開發與維護</font></strong>:寫在production code裡面的合約(post-conditions),看起來跟寫在測試裡面的assertions很像,但<strong><font color="#000000">合約並不是把測試寫在production code,而是把規格寫在production code</font></strong>,這兩者有很大的差別。將規格寫在production code,當規格改變之後,可以直接修改production code(反之亦然),減少context switching。</li> <li><strong><font color="#000000">Caller和Callee責任清楚</font></strong>:撰寫合約也可以釐清物件之間的責任,Caller需要滿足pre-conditions,Callee需要滿足post-conditions。當合約被違反時,丟出的例外訊息可以協助找出錯誤,這一點可以達到和單元測試一樣,甚至更好的除錯效果。</li> </ul> <p align="center">***</p> <p><strong><font color="#008000" size="5">誰來驗證合約?</font></strong></p> <p>看到這裡鄉民們應該有一個疑問:「我可以直接執行單元測試,寫在production code裡面的合約要怎麼執行?」另外,「合約也是程式碼,寫錯了怎麼辦?要不要寫測試來驗證合約?」</p> <p>合約是在系統執行期間(runtime)被執行與驗證,所以還是要有「人」來執行這些合約。這就是圖2中的整合測試所要負擔的責任:<strong><font color="#000000">透過整合測試來執行合約</font></strong>。</p> <p>有沒有需要另外寫其他類型的測試來驗證合約?基本上不需要,當執行驗收測試時如果合約失敗,和單元測試執行失敗一樣,可能有兩個原因:production code寫錯或測試寫錯(合約寫錯),然後開發人員就必須介入排除錯誤發生的原因。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>透過驗收測試驅動合約,可以極大幅度減少單元測試的數量,接下來只要可以加速驗收測試執行速度,實務上就有可能落實Teddy所介紹的這套方法。至於如何加速驗收測試執行,這個問題比較複雜,下集再談。</p> <p align="center">***</p> <p>友藏內心獨白:「你就是寫太多測試才會沒時間」都是真的 XD。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-85188944656024432342023-06-17T17:45:00.011+08:002024-03-11T18:22:22.980+08:00你就是寫太多測試才會沒時間(1):證明自己的清白<p>June 17 16:31~18:24</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtFcNO-9mQOkvt3qpZAQ-4swYP21Fs5VX87AuFMPxQHOL5FBN4ZvxB_iP1n5uWjX-HUcmiNm426L6e1WEXL-af4YdvgTrHO3h7S3L3bxfhN8Tvp5_2y4C8GGFF_q0V9s8KdKkyE_IjUJsPXn74xqr9Z3Wz0w5-L7gzJ29y-WBH32Doq0I9ZzOJGR8NeE5_/s1308/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%884.26.45.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="676" data-original-width="1308" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtFcNO-9mQOkvt3qpZAQ-4swYP21Fs5VX87AuFMPxQHOL5FBN4ZvxB_iP1n5uWjX-HUcmiNm426L6e1WEXL-af4YdvgTrHO3h7S3L3bxfhN8Tvp5_2y4C8GGFF_q0V9s8KdKkyE_IjUJsPXn74xqr9Z3Wz0w5-L7gzJ29y-WBH32Doq0I9ZzOJGR8NeE5_/w489-h252/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%884.26.45.jpg" width="489" /></a></div><p>▲圖1:單元測試驗證修改過的email是否正確 </p><p> </p> <p><font color="#008000" size="5"><strong>前言</strong></font></p> <p>Teddy的朋友Kuma幾個月前寫了一本書:《<a href="https://www.tenlong.com.tw/products/9786263332645" target="_blank">你就是不寫測試才會沒時間:Kuma 的單元測試實戰 -- Java篇</a>》。的確,自從Teddy在N年前開始寫第一個自動化單元測試之後,Teddy一直認為測試是開發不可分割的一部分。好的測試可以協助釐清規格、作為驗收條件、找出回歸錯誤,以及支持重構,讓開發人員走得更穩、更快。</p> <p>但是,隨著測試案例越來越多,<strong><font color="#000000">管理與重構這些測試案例就變成另一個頭痛的問題</font></strong>。下周二Teddy舉辦一個網路演講,講題是:<strong><font color="#000000">「你就是寫太多測試才會沒時間」</font></strong>,就是要討論應對這種現象的方法。這個講題這雖然是一句帶有玩笑性質的話,但也代表對於測試看法的一種演進過程:</p> <p><strong><font color="#0000ff">不寫測試沒時間 ---> 寫了測試有時間---> 累積太多欠管理的測試又變得沒時間 ---> 下階段是什麼?</font></strong></p> <p>針對這個主題,今天談一個單純一點的情況:如何簡單驗證待測程式沒有做它不該做的事情?</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>從範例看問題</strong></font></p> <p>軟體測試有一個基本原則:<strong><font color="#000000">「除了要驗證待測程式做了該做的事情,也要驗證它沒有做不該做的事。」</font></strong>舉個例子,圖1中的User物件,呼叫它的changeEmail方法設定新的email,在第101行中驗證email是否被正確設定。這種測試很常見,但嚴格講起來這個測試並不完整。除了驗證email有被正確修改以外,還需要確保User物件的其他欄位<strong><font color="#000000">沒有被改變</font></strong>。因為難保changeEmail的實作,除了改變email以外,會不會不小心動到User物件的其他欄位,例如把nickname清空。</p> <p>但是,如果每一個測試案例都去驗證<strong><font color="#000000">不應該被改動的欄位真的沒有被異動</font></strong>,將會增加很多測試工作,如圖2所示。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ0tSzG4Cm08R_gmpo4k_jllIervNfbko9c_j1ODVIloPSTgKJ_LH5W-CczRHJJSCVOcnbjex2Acn8vrppPglhYWfkCG3aMkKiSyNVPZ9_ZM8suucWijkK1X0KjPe7jnn8FMRI3hO-ozYo_1udPXfDlvucE1WEY-zkZ-5KGGBN5tmlKB2y6z27lQwWWyh7/s1272/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%886.02.24.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1026" data-original-width="1272" height="484" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ0tSzG4Cm08R_gmpo4k_jllIervNfbko9c_j1ODVIloPSTgKJ_LH5W-CczRHJJSCVOcnbjex2Acn8vrppPglhYWfkCG3aMkKiSyNVPZ9_ZM8suucWijkK1X0KjPe7jnn8FMRI3hO-ozYo_1udPXfDlvucE1WEY-zkZ-5KGGBN5tmlKB2y6z27lQwWWyh7/w600-h484/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%886.02.24.jpg" width="600" /></a></div><p>▲圖2:第104~109行驗證User除了email以外的其他欄位維持原狀 </p><p align="center">***</p> <p>這還只是單元測試而已,如果是Use Case層次的測試案例,例如ChangeEmailUseCase,從Repository讀出User之後,理論上相同的assertion還要再寫一次。除了需要花費而外時間撰寫測試,也造成duplication code,增加日後維護測試案例的成本。</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>解決方案</strong></font></p> <p>先講結論,Teddy使用<a href="http://joel-costigliola.github.io/assertj/" target="_blank">AssertJ</a>這個「Fluent assertions for java」的測試工具來解決這個問題。圖3為Teddy使用<strong><font color="#000000">ezSpec</font></strong>(Teddy自行開發的BDD工具軟體,可以直接用Java寫Given-When-Then,過一陣子會開源)所撰寫的ChangeEmailUseCase測試,第73行透過Repository從資料庫中拿出修改過email的User物件,然後第74行比對email欄位是否被正確修改。</p> <p>接著在第76~78行使用AspectJ的assertThat做為比對物件的方式,呼叫usingRecursiveComparison,然後透過ignoringFieldsMatchingRegexes指定那些欄位不需要比對,最後再呼叫isEqualTo,就可以排除特定欄位之後,比對兩個物件是否相等。</p> <p>圖3的程式範例擷取自ezKanban,由於ezKanban支援樂觀鎖,因此在每一個Aggregate物件身上都有一個version欄位用以作為樂觀鎖使用。因為User物件是一個Aggregate,所以User的email改變之後,version數值加1,因此在第77行比對修改前與修改後的兩個User物件實例是否相等的時候,除了排除email,也要排除version。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOW3IxXcmDNkKlH6gnnujJ0mBQFKUfKANAN96eirZZrwJUGDEzCqHki42HmhFTnxmDroHlGYXlsqdKwFT2yQbp8SxF99Qbk7k7wMrxv8fvQkYWm443gxn9DJd_zc44H_Gk5k0vI93rk9eQaIqdqwep0Tio6piEzqqz-lT5jRpuk4uqx_7G-16Bq9DRpoHG/s1534/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%883.56.51.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1413" data-original-width="1534" height="526" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOW3IxXcmDNkKlH6gnnujJ0mBQFKUfKANAN96eirZZrwJUGDEzCqHki42HmhFTnxmDroHlGYXlsqdKwFT2yQbp8SxF99Qbk7k7wMrxv8fvQkYWm443gxn9DJd_zc44H_Gk5k0vI93rk9eQaIqdqwep0Tio6piEzqqz-lT5jRpuk4uqx_7G-16Bq9DRpoHG/w571-h526/%E6%88%AA%E5%9C%96%202023-06-17%20%E4%B8%8B%E5%8D%883.56.51.jpg" width="571" /></a></div><p>▲圖3:採用ezSpec撰寫的ChangeEmailUseCase測試 </p><p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>透過工具幫忙,就可以用很簡潔的方式去確保物件的狀態。雖然Teddy在範例中使用AssertJ做為比對的工具,但相信不同的語言應該可以找到類似的工具。如果真的找不到怎麼辦?那就自己寫一個啊。</p> <p align="center">***</p> <p>友藏內心獨白:開發人員就是要有Maker精神。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-26196977055206870632023-06-14T22:54:00.003+08:002024-03-11T18:25:43.455+08:00為什麼Teddy沒使用Specification設計模式?<p>June 14 21:46~22:53</p> <p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE0DCOTvYfhQcnB_8uHKKriGnPjVd8tkPbFagI4Oqy7XoECoPvuWU0SPCJLliynDZhnkAO34aR-Qv3MYpsBrH8w3Pz6rPzsR2SdkG4W0WITHSyp4TYKE77x2O4PzNWbvz8_2SH5wHwoqweV5qW_fyIXduFSSKGGLa2z5PtdIZEYoPnUEtQCho8eru3Tci8/s1116/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%889.58.08.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="272" data-original-width="1116" height="98" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgE0DCOTvYfhQcnB_8uHKKriGnPjVd8tkPbFagI4Oqy7XoECoPvuWU0SPCJLliynDZhnkAO34aR-Qv3MYpsBrH8w3Pz6rPzsR2SdkG4W0WITHSyp4TYKE77x2O4PzNWbvz8_2SH5wHwoqweV5qW_fyIXduFSSKGGLa2z5PtdIZEYoPnUEtQCho8eru3Tci8/w400-h98/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%889.58.08.jpg" width="400" /></a></div><br /> ▲圖1:定義Specification介面<p></p> <p><strong> <br /><font color="#008000" size="5">前言</font></strong></p> <p>在6/5~6/7去客戶家上【<a href="https://teddysoft.tw/courses/clean-architecture/">領域驅動設計與簡潔架構入門實作班</a>】的時候,有一位學員問Teddy:</p> <ol> <li>為什麼Teddy建議Entities Layer的物件不要直接操作Repository? </li> <li>為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern? </li> <li>為什麼Teddy沒有用Specification模式?</li> </ol> <p>前兩集談了前兩個問題,今天討論最後一個問題:「為什麼Teddy沒用Specification設計模式」」?</p> <p> </p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>Specification 設計模式</strong></font></p> <p>這一個設計模式是由Eric Evans與Martin Fowler所整理的,可在此下載<a href="https://www.martinfowler.com/apsupp/spec.pdf" target="_blank">介紹該模式的pdf檔案</a>。</p> <p>Specification這個名字很容易讓人聯想到規格或是需求,但它的用作其實是<strong><font color="#000000">Filter(過濾器)</font></strong>或<strong><font color="#000000">Selector(選擇器)</font></strong>。傳統上,開發一般CRUD-Based的系統,開發人員很常直接下SQL操作資料庫去尋找所需的資料。但是在領域驅動設計(DDD)中,強調透過領域模型來表達業務邏輯。「尋找符合條件的領域物件」這件事,本身就是一個業務邏輯,因此在領域模型中應該有相對應的物件來表達這樣的業務邏輯。而Specification設計模式就是為了這樣的應用場景而存在。</p> <p>實作Specification很簡單,它的介面只有一個方法isSatisfiedBy,請參考圖1。isSatisfiedBy接受一個物件(通常是領域物件,例如Board, Workflow, Card這些物件),如果物件的內容滿足concreate specification所指定的條件,則回傳true,代表這個物件「符合規格(選到這個物件了)」。</p> <p align="center">***</p> <p>圖2是以個用來選擇Board是否屬於某個Team的規格,稱為BoardBelongToTeamSpecification。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6kpd-ACsPLQgVCagAciVwFYcApb8LihnCp70U16nuLFPrX4y4OG34XIWKG8Jjx6_HLB2EHn0B14FIyQSizM2lu_r0JDSNkeoGhENWEeKsdAfd3y0pfKn-jyMo4oieIIGpeyBQS2WB18VW4GefQtQZWrCmalv4Fes-FzAQnO0_Kbsr_HQg_B3eqdADn218/s1534/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%8810.22.48.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="705" data-original-width="1534" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6kpd-ACsPLQgVCagAciVwFYcApb8LihnCp70U16nuLFPrX4y4OG34XIWKG8Jjx6_HLB2EHn0B14FIyQSizM2lu_r0JDSNkeoGhENWEeKsdAfd3y0pfKn-jyMo4oieIIGpeyBQS2WB18VW4GefQtQZWrCmalv4Fes-FzAQnO0_Kbsr_HQg_B3eqdADn218/w493-h227/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%8810.22.48.jpg" width="493" /></a></div><br /><p>▲圖2:BoardBelongToTeamSpecification範例程式 </p><p> </p> <p>圖3為BoardBelongToTeamSpecification的使用方法,首先呼叫getBoardLise()產生四個Board。然後產生BoardBelongToTeamSpecification instance,傳入”Team 1”當作查詢條件。接著用BoardBelongToTeamSpecification 當作過濾條件,從這四個Board裡面選出Kanban Board與Board Game。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0FdmEg7HibNBqD4J3UThgzXmi8qj5FdZ_4w_5saz4jsiS0kT9xdCi0UI1ohr9pzKXVWPr41t-lkIyPy3Ix6G70JOiOKfXoB7poZLDVBEiMZwfstrwmgRgZuzcxua_UoNkXJ-Vg-Ig1KYnxykvHBh9j6hTTb4W9TmSJeNRiatrt2v3zDVy-Wh-Ioyfsjy4/s1534/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%8810.24.58.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="517" data-original-width="1534" height="196" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0FdmEg7HibNBqD4J3UThgzXmi8qj5FdZ_4w_5saz4jsiS0kT9xdCi0UI1ohr9pzKXVWPr41t-lkIyPy3Ix6G70JOiOKfXoB7poZLDVBEiMZwfstrwmgRgZuzcxua_UoNkXJ-Vg-Ig1KYnxykvHBh9j6hTTb4W9TmSJeNRiatrt2v3zDVy-Wh-Ioyfsjy4/w581-h196/%E6%88%AA%E5%9C%96%202023-06-14%20%E4%B8%8B%E5%8D%8810.24.58.jpg" width="581" /></a></div><p>▲圖3:BoardBelongToTeamSpecification使用範例 </p><p> </p> <p>在Eric Evans與Martin Fowler所整理原始的文章中,Specificatoin還可以串接,形成更複雜的選擇規範。</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>為什麼沒用Specification?</strong></font></p> <p>由上面例子可以看出來,Specification其實就是一個Predicate。從實作面的角度來看,可以直接用Lambda來完成。Specification之所以存在,還有一個很重要的作用力,就是<strong><font color="#000000">要重複使用這個業務邏輯</font></strong>。你可以用Specification去資料庫中挑選資料,或是驗證領域模型物件是否符合否項業務規格。在這種情況下,如果使用Lambda來實作,就會產生重複程式碼。</p> <p>寫到這裡,還是沒講為什麼在ezKanban中並沒有使用Specification。原因如下:</p> <ul> <li>ezKanban透過撰寫合約的方式來驗證領域物件的正確性,而非使用Specification。</li> <li>如果要透過Specification去資料庫挑選資料,那麼資料必須先從資料庫中載入記憶體或是用某種映射的方式,把傳給isSatisfiedBy()方法的物件身上的每一個欄位,去匹配資料庫中的欄位。因為ezKanban支援State Sourcing與Event Sourcing,無法光用傳統State Sourcing的方式如果採用Specification去資料庫比對資料。因此,ezKanban就沒有使用Specification,而是針對不同儲存方式與不同資料庫,用資料庫相依的方法,撰寫特別的查詢物件。</li> <li>ezKanban套用CQRS,而CQRS和Specification是兩種互相衝突的設計模式。詳細原因請參考Vladimir Khorikov的部落格文章 <<a href="https://enterprisecraftsmanship.com/posts/cqrs-vs-specification-pattern/" target="_blank">CQRS vs Specification pattern</a>>。</li> </ul> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>Teddy當年在學DDD的時候,印象中只有在DDD藍皮書中看到Specification的介紹,在比較接近實作的DDD紅皮書與DDD橘皮書中,沒什麼印象提到Specification。可能是Teddy學藝不精,所以開發ezKanban的時候潛意識中就沒有套用Specification。</p> <p>但Teddy目前沒用也不代表以後不會用,反正這麼多設計模式,多了解一點也沒什麼不好。等到哪天有合適的場合,這些模式會自動跑出來報效國家。</p> <p align="center">***</p> <p>友藏內心獨白:用Collection操作資料還是非常方便滴。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-10687432622723381292023-06-12T20:57:00.002+08:002024-03-11T18:30:23.422+08:00該不該使用Unit of Work和Repository?<p>June 12 15:48~16:20;20:22~22:56</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV1L7g1wq4Rdrlmp8fljGAAo8Vxp9fAojzCZx3JpwE44136-PMgjm7iygJ-1ta5IjFQLAfQ9NkIKOSTmfKUBzeMdF8jeCaXeaD01hcVfxqS_AFSznApTba1vpA80gE-3XsJHrz4fseAuxpCD4xw3wpeToWzlAByqntUSF4ZIgphWf3iBxZV_rOVrkQrzx_/s1534/%E6%88%AA%E5%9C%96%202023-06-12%20%E4%B8%8B%E5%8D%888.35.05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="593" data-original-width="1534" height="155" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV1L7g1wq4Rdrlmp8fljGAAo8Vxp9fAojzCZx3JpwE44136-PMgjm7iygJ-1ta5IjFQLAfQ9NkIKOSTmfKUBzeMdF8jeCaXeaD01hcVfxqS_AFSznApTba1vpA80gE-3XsJHrz4fseAuxpCD4xw3wpeToWzlAByqntUSF4ZIgphWf3iBxZV_rOVrkQrzx_/w400-h155/%E6%88%AA%E5%9C%96%202023-06-12%20%E4%B8%8B%E5%8D%888.35.05.jpg" width="400" /></a></div><br /><p>▲圖1:ezKanban的Repository介面 </p><p><br /></p><p><strong> <font color="#008000" size="5">前言</font></strong></p> <p>昨天提到6/5~6/7到客戶家上【<a href="https://teddysoft.tw/courses/clean-architecture/">領域驅動設計與簡潔架構入門實作班</a>】,有一位學員問Teddy的三個問題:</p> <ol> <li>為什麼Teddy建議Entities Layer的物件不要直接操作Repository? </li> <li>為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern? </li> <li>為什麼Teddy沒有用Specification模式?</li> </ol> <p>昨天談了第一個問題,今天聊聊第二個問題:「在DDD中,UoW和Repository要不要使用」?</p> <p> </p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>Unit of Work(UoW)</strong></font></p> <p>Unit of Work和Repository這兩個設計模式都出自於Martin Fowler所寫的《<a href="https://www.amazon.com/-/zh_TW/Martin-Fowler/dp/0321127420/ref=sr_1_1?keywords=patterns+of+enterprise+application+architecture&qid=1686556431&s=books&sprefix=patterns+of+enter%2Cstripbooks-intl-ship%2C262&sr=1-1" target="_blank">Patterns of Enterprise Application Architecture</a>》。Unit of Work顧名思義就是<strong><font color="#000000">工作單元</font></strong>,<strong><font color="#000000"></font></strong>什麼叫做工作單元?就是把一連串的工作步驟,視為「一個單元」、「一整包完整的大步驟」。一個工作單元隱含一個<strong><font color="#000000">交易邊界(transaction boundary)</font></strong>。</p> <p>舉個例子,在ezKanban裡面,有以下幾個使用案例:</p> <ul> <li>CreateBoardUseCase</li> <li>CreateWorkflowUseCase</li> <li>CreateStageUseCase</li> <li>CreateCardUseCase</li> </ul> <p>每一個使用案例,都是一個工作單元,形成一個交易邊界。產生一個Board是一個工作單元,要嘛成功,要嘛失敗。同理,產生Workflow、產生Stage、產生Card,都是一個工作單元。很簡單,對不對!</p> <p>ezKanban團隊使用ezKanban的領域模型開發了看板桌遊,這是另一個Bounded Context。在看板桌遊中,有一個CreateKanbanGameUseCase用來產生新的看板遊戲。這個使用案例的實作,呼叫上述四個使用案例。現在問題來了:「在看板桌遊的Bounded Context中,CreateKanbanGameUseCase是一個工作單元,它的執行要嘛成功要嘛失敗。但是因為CreateKanbanGameUseCase的實作方式是重複使用CreateBoardUseCase、CreateWorkflowUseCase、CreateStageUseCase與CreateCardUseCase,這四個使用案例各自是一個工作單元,要怎麼把它們用另一個更大的工作單元包起來?」</p> <p>Unit of Work設計模式就是要解決這個問題,簡單講,就是<strong><font color="#000000">把transaction manager注入給使用案例</font></strong>,而不是讓使用案例自己去控制。如此一來,最外層的使用案例負責控制transaction的開始與結束,內部的使用案例只是接受這個由最外層使用案例所注入的transaction manager。如此一來,便可以因應不同使用情境(Context)的需要,動態決定工作單元的範圍。</p> <p align="center">***</p> <p> </p> <p><font color="#008000" size="5"><strong>為什麼不要使用Unit of Work?</strong></font></p> <p>如果不管DDD,Unit of Work是一個很棒的設計模式。但是,在DDD中,Aggregate已經形成了一個交易邊界。如果在DDD中需要使用Unit of Work,則代表在某個Context底下,需要把好幾個不同的Aggregate放在同一個交易中。這不就和原本在DDD中「Aggregate形成了交易邊界」互相衝突了。</p> <p>更進一步來看,在DDD中,Aggregate由Repository負責儲存與讀取。而「理論上」一個Repository可以各自採用不同的資料庫來儲存Aggregate。也就是說,如果你願意,可以將Aggregate當成一個微服務來佈署。如果在DDD中使用Unit of Work,則這些被放在同一個Unit of Work的Aggregate,就代表它們要綁在同一個資料庫中(除非使用distributed transaction,但採用這種做法的人很少,因為會造成效能問題),這就造成<strong><font color="#000000">不同的Aggregate透過資料庫產生耦合</font></strong>。因此,Teddy覺得在DDD中,不應該使用Unit of Work。</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>Repository可以用嗎?</strong></font></p> <p>在Martin Fowler的《<a href="https://www.amazon.com/-/zh_TW/Martin-Fowler/dp/0321127420/ref=sr_1_1?keywords=patterns+of+enterprise+application+architecture&qid=1686556431&s=books&sprefix=patterns+of+enter%2Cstripbooks-intl-ship%2C262&sr=1-1" target="_blank">Patterns of Enterprise Application Architecture</a>》書中,Repository代表Collection-Based的儲存體。也就是說,只要從Repository拿出物件,之後對於該物件的修改,會直接反應回Repository,使用者不需要呼叫save方法來儲存該物件。</p> <p>在Vaughn Vernon所寫的《<a href="https://www.amazon.com/-/zh_TW/Vaughn-Vernon/dp/0321834577/" target="_blank">Implementing Domain-Driven Design</a>》,進一步將Repository的實作分成Collection-Based Repository與 Curd-Based Repository。圖1為ezKanban所設計的Repository介面,採用Crud-Based Repository。ezKanban的所有Aggregate所對應的Repository都是採用相同的介面,只有findById, save與delete這三個方法。</p> <p>不管是Collection-Based或是Crud-Based,Teddy主張,只要<strong><font color="#000000">固定Repository介面</font></strong>,將其限制在單一Aggregate的新增、修改、刪除、查詢,這樣子使用Repository並不會有什麼太大的問題。</p> <p>但是,實務上經常可以看到,很多開發人員在Repository身上加了很多<strong><font color="#000000">查詢方法</font></strong>。如此一來,雖著需求演進,Repository的介面越來越肥大。<strong><font color="#000000">你可以說,這種使用Repository的方式,違反了單一責任原則、開放封閉原則,以及介面隔離原則。</font></strong></p> <p>所以,只要固定Repository介面,將其餘查詢方法另外設計(在ezKanban中採用<strong><font color="#000000">Inquiry</font></strong>設計模式來解決這個問題),在DDD中使用Repository是沒有問題的。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>以上,是Teddy近幾年開發ezKanban所累積的經驗。Unit of Work比較簡單,ezKanban壓根就沒使用過它。但是,針對Repository的使用方法ezKanban團隊重構了好幾次。一開始Teddy也是在不同的Concreate Repository中直接新增個別Aggregate所需要的查詢介面。但隨著系統越來越複雜,Repository也變得越來越亂,不容易理解其中的邏輯。後來,套用CQRS之後,把查詢、命令分離,保留最簡單的Repository介面。如此一來,使用Repository就沒有問題了。</p> <p align="center">***</p> <p>友藏內心獨白:不是不好用,是你不會用 XD。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-5996824512906652762023-06-11T20:33:00.002+08:002024-03-11T18:33:26.357+08:00領域模型不要直接依賴Repository<p>June 11 19:23~20:38</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizKp_BvRIXz2IJsKXt-wErNMFmTKw85L0xuOvwmFIwi45jMVlcQyVr096LRkL0Ihfwxb2Wsf8hIshnVV2A3xd0JQeS1Z_ZI1LQbUVgRv0U_6gUbqQ3-MLrOHNo5prwKpE9DTuZgCOaeP-m80UdrlRSHrFJhm_VMHb5pw55fl-GmoRH9GQ5EtMPMwcmat4z/s1534/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%888.02.21.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="541" data-original-width="1534" height="182" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizKp_BvRIXz2IJsKXt-wErNMFmTKw85L0xuOvwmFIwi45jMVlcQyVr096LRkL0Ihfwxb2Wsf8hIshnVV2A3xd0JQeS1Z_ZI1LQbUVgRv0U_6gUbqQ3-MLrOHNo5prwKpE9DTuZgCOaeP-m80UdrlRSHrFJhm_VMHb5pw55fl-GmoRH9GQ5EtMPMwcmat4z/w515-h182/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%888.02.21.jpg" width="515" /></a></div><br /><p>▲圖1:將Repository放到Entities Layer(錯誤示範XD) </p><p> </p> <p><strong> <br /><font color="#008000" size="5">前言</font></strong></p> <p>6/5~6/7到客戶家上【<a href="https://teddysoft.tw/courses/clean-architecture/" target="_blank">領域驅動設計與簡潔架構入門實作班</a>】,有一位學員問了Teddy不少問題,像是:</p> <ol> <li>為什麼Teddy建議Entities Layer的物件不要直接操作Repository?<!--EndFragment--></li> <li>為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern?</li> <li>為什麼Teddy沒有用Specification模式?</li> </ol> <p> <br /> 這些問題,應該是有在下功夫研究DDD的人,才會提出來的問題。針對這三個問題,Teddy分三集來說明,今天先談第一個問題。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">增加領域模型與測試複雜度</font></strong></p> <p align="left">Entities Layer是Clean Architecture存放領域模型(Domain Model)的地方,它應該只表達<strong><font color="#000000">問題領域</font></strong>的業務邏輯,儘可能與外在世界、框架無關。Repository是領域驅動設計(Domain-Driven Design;DDD)中,用來存取聚合(Aggregate)的設計模式。也就是說,Repository隔離了儲存層,讓它的使用者無需知道儲存聚合的實作細節。</p> <p>Entities Layer知道Repository又怎樣?首先,這麼作讓Domain Model依賴資料存取介面,雖然這個依賴透過Repository介面做到依賴反轉,但「資料存取」的概念還是<strong><font color="#000000">洩漏</font></strong>到Domain Model,增加不必要的複雜度。</p> <p>這種不必要的複雜度增加,可以從測試的角度看出來。針對Entities Layer物件的測試,理想上就是傳統軟體測試所說的<strong><font color="#000000">單元測試</font></strong>,而且是可以做到<strong><font color="#000000">「測試隔離」(test in isolation)</font></strong>。這樣子的單元測試,因為與外在世界無關,所以可以跑得很快且可以單獨測試業務邏輯。</p> <p>看到這裡鄉民們可能會想:「我有學過<strong><font color="#000000">Test Double(測試替身)</font></strong>,我可以在測試案例中注入Test Double,這樣就可以寫出與世隔絕的單元測試。」</p> <p align="left">使用Test Double雖然可以讓使用Repository的Entities Layer物件做到隔離測試,但是付出的代價就是<strong><font color="#000000">單元測試變得複雜且可能出現重複程式碼</font></strong>。現在,要測試Entities Layer物件之間,都要在單元測試的<strong><font color="#000000">Arrange</font></strong>階段先設定Repository替身,這會複雜化且重複Arrange區塊的程式碼。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">弱化階層式架構</font></strong></p> <p align="left">請參考圖1,如果將Repository放在Entities Layer,從Clean Architecture的角度來看,為了滿足相依性原則,BoardRepository必須是一個介面,然後在Interface Adapters Layer實作BoardRepositoryImpl。如此一來,雖然滿足相依性原則,卻造成了BoardRepositoryImpl<strong><font color="#000000">跨層依賴</font></strong>於BoardRepository。雖然在<strong><font color="#000000">鬆散式階層架構</font></strong>中允許跨層依賴,但Teddy認為這會弱化了階層式架構的一致性。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">其他人怎麼說</font></strong></p> <p align="left">請參考圖2,在IDDD書中也提到,不要將Repository注入給Aggregate。</p> <p align="left"><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivBSljE2u785mQIku7p8AGpfLk8_rTwU8ZIpUdxrNl4YPy3ji68pkAxpDUHHOHZt7uCW7OOr-nhOyQg5XCLKpUp3I5GrSs3kbGU8Spjd1t9czg1lELAqyUwaZmSAU7AOmlDy1Bn8hCxTKLXazXcGP5iAQR8PdND8R4t1VUNlTQhKBLlJTcQhq2k3P6IRQT/s1534/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%886.59.46.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1305" data-original-width="1534" height="438" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivBSljE2u785mQIku7p8AGpfLk8_rTwU8ZIpUdxrNl4YPy3ji68pkAxpDUHHOHZt7uCW7OOr-nhOyQg5XCLKpUp3I5GrSs3kbGU8Spjd1t9czg1lELAqyUwaZmSAU7AOmlDy1Bn8hCxTKLXazXcGP5iAQR8PdND8R4t1VUNlTQhKBLlJTcQhq2k3P6IRQT/w515-h438/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%886.59.46.jpg" width="515" /></a></div><p align="left">▲圖2:Teddy的FB廢文1 </p><p> </p> <p>如圖3所示,在Unit Testing一書中也提到,在領域模型中直接使用資料庫(相當於Repository)會造成程式碼過度複雜。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiN66TTyhVA864oFR7oPIfezFNNXjWONYSIRbZo1-f_ocNi9Of8uYnq3fdbRoAddnOOzo2te5T8MRW31DrUgWeX-pBKvJREXrVpvQkI-wm-eQgvabnA3yv1bJo0zrWKsiccQiUUh3qDWo-9WDyBzxVxYU74yBqSgkL1mYU2OxvltDfJdtbnxxB1TDv1QzcC/s1534/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%887.02.52.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1123" data-original-width="1534" height="392" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiN66TTyhVA864oFR7oPIfezFNNXjWONYSIRbZo1-f_ocNi9Of8uYnq3fdbRoAddnOOzo2te5T8MRW31DrUgWeX-pBKvJREXrVpvQkI-wm-eQgvabnA3yv1bJo0zrWKsiccQiUUh3qDWo-9WDyBzxVxYU74yBqSgkL1mYU2OxvltDfJdtbnxxB1TDv1QzcC/w535-h392/%E6%88%AA%E5%9C%96%202023-06-11%20%E4%B8%8B%E5%8D%887.02.52.jpg" width="535" /></a></div><p>▲圖3:Teddy的FB廢文2 </p><p align="left"> </p> <p align="center">***</p> <p align="left"><strong><font color="#008000" size="5">結論</font></strong></p> <p align="left">只要程式可以正確動起來,設計沒有絕對的對、錯,但有合適程度的差別。關於這個問題Vladimir Khorikov有一篇很棒的blog: <<a href="https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness/" target="_blank">Domain model purity vs. domain model completeness (DDD Trilemma)</a>>,鄉民們可以參考。</p> <p align="center">***</p> <p align="left">友藏內心獨白:領域模型越乾淨越好。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-69088036691186469232023-03-27T00:00:00.004+08:002024-03-11T18:46:19.894+08:00使用ezSpec落實行為驅動開發與實例化需求(10):撰寫自訂報表<p>March 25 12:44~14:18</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI722gU4ZPRx0nyHrGbjM0IqlY35UBU_w1XIX4TZiNjzlXVQWqBzmGslwg86d2-W_5k3VOngqygcu8Y30-tL6q55buRbI7_yFagJStzfECfvdvpx4sjpo0-oPGctFWeYGUabu0Za2VPiat93XFiqWCGtlznU9uvtBm3pmxBQ8XbdsAuQX7_r-Ne5QZIDsr/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%8812.56.07.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="509" data-original-width="1534" height="169" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhI722gU4ZPRx0nyHrGbjM0IqlY35UBU_w1XIX4TZiNjzlXVQWqBzmGslwg86d2-W_5k3VOngqygcu8Y30-tL6q55buRbI7_yFagJStzfECfvdvpx4sjpo0-oPGctFWeYGUabu0Za2VPiat93XFiqWCGtlznU9uvtBm3pmxBQ8XbdsAuQX7_r-Ne5QZIDsr/w509-h169/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%8812.56.07.jpg" width="509" /></a></div><p>▲圖1:ezSpec實作Visitor設計模式類別圖</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>上一集介紹ezSpec的基本報表功能,這一集介紹如何擴充ezSpec,撰寫使用者自訂報表。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">Visitor設計模式</font></strong></p> <p>為了支援自訂報表,ezSpec讓Gherkin keyword實作Visitor設計模式,請參考圖1。在GoF設計模式中,有兩個角色:</p> <ul> <li><strong><font color="#000000">Element</font></strong>:圖1中的<strong><font color="#000000">SpecificationElement</font></strong>介面,要接受「拜訪者」的物件需要實做Element介面。該介面有一個accept方法,接受visitor作為參數。該方法的實作會呼叫(callback)visitor.visit,讓visitor可以讀取element的資料。</li> <li><strong><font color="#000000">Visitor</font></strong>:圖1中的<strong><font color="#000000">SpecificationElementVisitor</font></strong>介面,想要拜訪各個Element的具體拜訪者(concrete visitor)需要實作該介面,將拜訪者的邏輯寫在visit方法裡面。在ezSpec中,客製化報表就是一個具體拜訪者。</li> </ul> <p> </p> <p>圖2為Feature實作SpecificationElement介面的程式碼,第14行Feature先將自己傳給visitor,接著再將它身上每一個Story傳給visitor。由於Feature是整個Gherkin keyword中最外層的結構,只要拜訪Feature,就能夠拜訪整個Feature file裡面所有的元素,包含Scenario、Scenario、Background與Step。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmKc9miXRUjTWvuHcehL_CoioP2Sh5NhiEeiy95_43YocGLDXwu8V4NEeNOxBjpw-Dd8N_2OJKyyNlt0OvJbrTfxT_RAJM37xPMxl9Z68d0-V440ahwyQhmDK5lyJ4vKi1LRWqkSRWBW_C7l07kXtf_KUA0hWpzMcp8wJuQo3-dN2pCw1UPGdiPHMBLEub/s1510/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.07.13.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="478" data-original-width="1510" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmKc9miXRUjTWvuHcehL_CoioP2Sh5NhiEeiy95_43YocGLDXwu8V4NEeNOxBjpw-Dd8N_2OJKyyNlt0OvJbrTfxT_RAJM37xPMxl9Z68d0-V440ahwyQhmDK5lyJ4vKi1LRWqkSRWBW_C7l07kXtf_KUA0hWpzMcp8wJuQo3-dN2pCw1UPGdiPHMBLEub/w640-h202/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.07.13.jpg" width="640" /></a></div><br /><p>▲圖2:ezSpec的Feature實作SpecificationElement的程式碼</p> <p> </p> <p align="center">***</p> <p> </p> <p><strong><font color="#008000" size="5">實作SpecificationElementVisitor產生報表</font></strong></p> <p>ezSpec內建的文字檔報表,就是使用上述介紹的機制所產生,請參考圖3。第20行到60行的visit方法,就是用來「拜訪」Feature內的所有元素,產生報表的邏輯。</p><p> </p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIDUNF_n5VZ8MBFOXwqiyaTF01X6WOUKHbNtGausjl7-cYhAfv3IS-17aZgl0Hjrc-pYjiaOPmbC0vNhsJWKlGCoaTLnNYRi6BsMVrQqjs_IcujQ_NaAjQZnl78SfTTf7iPjLx1Y097-C3o_T7s6Sdl8zfbUb4CctDGj_8WSyiTQXAPBU9N9o6OhWRTo4A/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.17.13.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1522" data-original-width="1534" height="634" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIDUNF_n5VZ8MBFOXwqiyaTF01X6WOUKHbNtGausjl7-cYhAfv3IS-17aZgl0Hjrc-pYjiaOPmbC0vNhsJWKlGCoaTLnNYRi6BsMVrQqjs_IcujQ_NaAjQZnl78SfTTf7iPjLx1Y097-C3o_T7s6Sdl8zfbUb4CctDGj_8WSyiTQXAPBU9N9o6OhWRTo4A/w640-h634/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.17.13.jpg" width="640" /></a></div><p></p> <p>▲圖3:ezSpec內建產生文字報表的Visitor程式碼</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">客製化報表範例</font></strong></p> <p>開發好ezSpec之後,Teddy就拿它來描述ezKanban的規格。圖3為ezKanban的MoveLane使用案例的規格,用ezSpec的Scenario Outline撰寫。這個功能是將看板系統中,工作流程內部的某一個Lane複製到另一個地方。在描述規格的時候,直接以表格描述複製之前與複製之後Workflow的內容,其執行結果所產生的文字報表如圖4。</p> <p><br /></p><div><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTIBYeuVqTdZtl2ce4g4rC5_uJY1bWcwd2IttPRRw4zBUkr4d-IQvKSzWKigYFLOf8bcHAxdubcca-cca6Cq6wnwWkugcn7sCbFwaA6adxPXJgUDjH1VhcP-Mj1AfW6VXEPL7kRi_bJLEMNWqYGcs5SaSUvEDt8qR9ZsTzTyBkTdpxA9-9_fSiViBPkiyD/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.26.08.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1047" data-original-width="1534" height="436" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTIBYeuVqTdZtl2ce4g4rC5_uJY1bWcwd2IttPRRw4zBUkr4d-IQvKSzWKigYFLOf8bcHAxdubcca-cca6Cq6wnwWkugcn7sCbFwaA6adxPXJgUDjH1VhcP-Mj1AfW6VXEPL7kRi_bJLEMNWqYGcs5SaSUvEDt8qR9ZsTzTyBkTdpxA9-9_fSiViBPkiyD/w640-h436/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.26.08.jpg" width="640" /></a></div></div><p>▲圖4:以ezSpec所撰寫的ezKanban系統之MoveLane使用案例規格 </p><p> </p> <p>圖5的內容不太容易閱讀,因為規格中表格還包含著另一個表格,因此Teddy想:「既然MoveLane使用案例是看板視覺化業務邏輯的其中一個功能,為什麼不用視覺化的方式來表達這個功能?」要怎麼用是視覺化方式產生報表,幫它寫一個Visitor吧,請參考圖5。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhRl4v-oZShjUyV03j6lWT6s7YMoyehz1YK2T79pCIxK20zbJ_QQ8zLAv_3R748NMq8eWkq_7B5hGsdYfPUjIqv3Y_z91Cwec-UECDZ9wS8ayrzEGQeO6wj0d4uwukjRFV1RfhhyiPt_CK-nKnUs7JDAIpcA-f8u5qhlpdhjhUzY9dHaWof2gWJU1bNrRy/s1591/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.30.11.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1591" data-original-width="1079" height="846" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhRl4v-oZShjUyV03j6lWT6s7YMoyehz1YK2T79pCIxK20zbJ_QQ8zLAv_3R748NMq8eWkq_7B5hGsdYfPUjIqv3Y_z91Cwec-UECDZ9wS8ayrzEGQeO6wj0d4uwukjRFV1RfhhyiPt_CK-nKnUs7JDAIpcA-f8u5qhlpdhjhUzY9dHaWof2gWJU1bNrRy/w574-h846/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.30.11.jpg" width="574" /></a></div><p>▲圖5:執行圖3所產生的ezSpec內建文字報表 </p><p> </p> <p>Teddy使用Markdown的Mermaid擴充(外掛)來繪製Workflow,圖6的WorkflowMermaidVisitor與ezSpec內建的PlainTextVisitor類似,差別在於前者針對MoveLane規格產生Markdown報表,後者則是標準的文字報表。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtPuebEuRjVVy2Wqaz0Gp1g_8QZNI_DicWnHY6wnjL0snwtGnNVQxZ-tBiYTJF6wOOpZRLClUJ8igRgiD3ng-nX6dxtTTCo77nMdMWRyLG3QizjE9G73kAT0RCkauwc243gZdgsyFlMLIViQePh0V92daEnF1rWU8piCrpQQfNgy5WB2ghGqVPSImf00x7/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.39.19.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1246" data-original-width="1534" height="489" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtPuebEuRjVVy2Wqaz0Gp1g_8QZNI_DicWnHY6wnjL0snwtGnNVQxZ-tBiYTJF6wOOpZRLClUJ8igRgiD3ng-nX6dxtTTCo77nMdMWRyLG3QizjE9G73kAT0RCkauwc243gZdgsyFlMLIViQePh0V92daEnF1rWU8piCrpQQfNgy5WB2ghGqVPSImf00x7/w602-h489/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.39.19.jpg" width="602" /></a></div><p>▲圖6:撰寫WorkflowMermaidVisitor產生MoveLane使用案例的專屬報表 </p><p> </p> <p>圖7報表的內容與圖4相同,前者以圖形顯示,後者以文字顯示。在MoveLane使用案例的情境中,以圖形顯示比較容易閱讀。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpBR5R57-okTw-w_22yIiZrr0ipYMkFyvV6Dwe5r2SoGCVNGDFq6bhar6IbXqP_K5bqU3CmmhQAL7bxoBw_VPm34jrTNb4uL8octvZGKc88XKbSymy_9t_EqkMdh1BnK99FN30Hn-hr4wEDTL2mW731vCEFlAHgzqtRpJ-kDvu3Vt02n_KQLVY82PrGcO2/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.46.05.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="998" data-original-width="1534" height="416" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpBR5R57-okTw-w_22yIiZrr0ipYMkFyvV6Dwe5r2SoGCVNGDFq6bhar6IbXqP_K5bqU3CmmhQAL7bxoBw_VPm34jrTNb4uL8octvZGKc88XKbSymy_9t_EqkMdh1BnK99FN30Hn-hr4wEDTL2mW731vCEFlAHgzqtRpJ-kDvu3Vt02n_KQLVY82PrGcO2/w640-h416/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%881.46.05.jpg" width="640" /></a></div><p>▲圖7:執行圖3所產生的客製化Markdown報表 </p><p> </p> <p>只要將寫好的Visitor在驗收測試的afterAll()方法中去拜訪Feature,再將結果寫入檔案中,就可以在每次執行驗收測試之後產生新的客製化報表,請參考圖8。</p> <p><br /></p><p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoyQMLp9_nfVPuPPXQXC-9ps877jUCy1_7T4xG3qUV0rOOedLW0USTNAeV0OqnDF8YkGlMHfQBnZ6QEzfiOPioEZAJIEpdYJCKFV6WIXH_sBjgDZCBSCOB97tX5z2NppDui72-QlTvn9xYI0kKthPxfgPd-dZEAd8SSPT2XELcyBrBZP0rvdvbBeiZLpKP/s1534/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%882.03.46.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="455" data-original-width="1534" height="190" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoyQMLp9_nfVPuPPXQXC-9ps877jUCy1_7T4xG3qUV0rOOedLW0USTNAeV0OqnDF8YkGlMHfQBnZ6QEzfiOPioEZAJIEpdYJCKFV6WIXH_sBjgDZCBSCOB97tX5z2NppDui72-QlTvn9xYI0kKthPxfgPd-dZEAd8SSPT2XELcyBrBZP0rvdvbBeiZLpKP/w640-h190/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8B%E5%8D%882.03.46.jpg" width="640" /></a></div>▲圖8:針對MoveLane使用案例驗收測試產生客製化報表<p></p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>寫到這裡剛好第十集,也把ezSpec目前的功能介紹完畢。Teddy預計在2023年七月之後開兩門新課:</p> <ol> <li><strong><font color="#000000">Clean TDD(整潔測試驅動開發)</font></strong></li> <li><strong><font color="#000000">Living Documentation in Agile Development(敏捷開發中的活文件)</font></strong></li> </ol> <p>在課程中(特別是第二門課)會以ezSpec為範例,在開課前Teddy會先開源ezSpec讓有興趣的鄉民們使用,敬請期待。</p> <p align="center">***</p> <p>友藏內心獨白:寫完文件要回頭寫Code。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-23171423395380524312023-03-26T00:00:00.004+08:002024-03-11T18:55:00.097+08:00使用ezSpec落實行為驅動開發與實例化需求(9):內建報表<p>March 25 10:43~11:48</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKOQL2Yq7OoUklWherysbhffmCFvicw4nY_dXjCZBNNEyu7hVbNH00SnDSbh7UTfkI7Rk2Hf-kmkBrBA3-SQMX_0WnpcIw9BbporZ_RyPNaUARk80QfeBexVDWHmaP6lNUIUvnUim_BaXQqjDSUDHrMpbVJ7Bujw1cskHyB19JU8rDJ3YrInlW2DPaiZgQ/s1490/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.47.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1490" data-original-width="1432" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKOQL2Yq7OoUklWherysbhffmCFvicw4nY_dXjCZBNNEyu7hVbNH00SnDSbh7UTfkI7Rk2Hf-kmkBrBA3-SQMX_0WnpcIw9BbporZ_RyPNaUARk80QfeBexVDWHmaP6lNUIUvnUim_BaXQqjDSUDHrMpbVJ7Bujw1cskHyB19JU8rDJ3YrInlW2DPaiZgQ/w385-h400/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.47.18.png" width="385" /></a></div><br /><p>▲等待報表功能全部完成,ezSpec就可以開源了</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>前幾集介紹ezSpec的基本功能以及同步執行功能,今天介紹ezSpec最後一個功能:報表。報表功能是做到<strong><font color="#000000">Living Documentation(活文件)</font></strong>的核心功能之一,先用ezSpec描述系統行為,然後這些可執行規格變成驗收條件,執行結果透過報表整理,產出與系統現狀同步的Living Documentation。</p> <p>目前ezSpec支援產生個別Feature file執行結果的報表,報表格式為txt檔與json檔,整合性報表還在開發中。ezSpec支援Visitor設計模式,開發人員也可以自行撰寫Visitor,產生使用者自訂報表。本集先介紹如何產生內建報表,下一集介紹如何擴充ezSpec,撰寫使用者自訂的報表。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">產生報表</font></strong></p> <p>要讓ezSpec產生報表,首先測試案例(Test Class)要實作EzSpec介面,請參考圖1。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjRmeS-XjzCVfCYFM5zeGWEbLu0ZOtLuAjgTxFAVgU6Bnte7tFT9NgaKYJSrsUzV-C8TWoKnX0JZEia9nnBlmmoMMZXM0-FakmTD947WIFEETfllpgT6KQsvTdYtrkBZpQTjzoB_IvfjaG_pyvAmgNO5FOwXMXvRIzoSqPOxuPOeKTFBJiGVCmKNyRE_vW/s1520/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.01.43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="1520" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhjRmeS-XjzCVfCYFM5zeGWEbLu0ZOtLuAjgTxFAVgU6Bnte7tFT9NgaKYJSrsUzV-C8TWoKnX0JZEia9nnBlmmoMMZXM0-FakmTD947WIFEETfllpgT6KQsvTdYtrkBZpQTjzoB_IvfjaG_pyvAmgNO5FOwXMXvRIzoSqPOxuPOeKTFBJiGVCmKNyRE_vW/w640-h138/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.01.43.png" width="640" /></a></div><p>▲圖1:測試案例實作EzSpec介面</p> <p> </p> <p>EzSpec介面程式碼如圖2,它身上有四個Annotation:</p> <ul> <li><a><strong><font color="#000000">@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class</font></strong></a><strong><font color="#000000">):</font></strong>這是JUnit 5的內建annotation,在JUnit報表中會將test mehtod的名字的底線取代為空白,以方便閱讀。例如,將scenario_using_EzScenario_annotation顯示為scenario using EzScenario annotation。</li> <li><a><strong><font color="#333333">@ExtendWith(EzSpecReportExtension.class</font></strong></a><strong><font color="#333333">):</font></strong><a>@ExtendWith</a>是JUnit 5支援使用者自行擴充功能的annotation,ezSpec的EzSpecReportExtension透過實作JUnit 5 的AfterAllCallback介面,讓JUnit執行每一個Test Clase之後呼叫ezSpec,以便產生報表。</li> <li><strong><font color="#000000">@EzSpecReportFormat({EzSpecReportFormat.Format.json, EzSpecReportFormat.Format.txt})</font></strong>:這是ezSpec自訂的annotation,用來表示產生那些格式的內建報表。目前ezSpec支援txt與json這兩種報表格式</li> <li><a><strong><font color="#000000">@Tag(EzSpecTag.LivingDoc.EzSpec</font></strong></a><strong><font color="#000000">)</font></strong>:這也是JUnit 5內建的annotation,用來分類測試案例。參考圖3,ezSpec內建EzSpecTag介面,裡面包含常用的Tag。將Test Class(在ezSpec的情境中,也就是Feature file)用Tag分類,主要以下兩個目的:</li> <ul> <li>在test suite中作為測案案例的選擇條件,例如只挑選單元測試,或是只挑選整合測試。</li> <li>作為Living Documentation的分類標籤,例如產生Use Case的報表,或是產生在Staging環境測試執行結果的報表。</li> </ul> </ul> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBpG33Fx5lOFeMFNEFnZYi9J3_7m7PZPeYGOyTLpygimjxnLQ1XnhzNRMexI7WCU71D443KZY91PdTfkuvGYX92rjcF5aAWNoDN66wZeYpn0NpxVG3-H9vERB5TTfPSqJbtYT1dL8Q9grjqfN_MvC0ZH0U62Nn4_IWt2f3XxcE9f7Cuqpc9lWg6PHaHdsC/s1520/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.01.43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="326" data-original-width="1520" height="138" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBpG33Fx5lOFeMFNEFnZYi9J3_7m7PZPeYGOyTLpygimjxnLQ1XnhzNRMexI7WCU71D443KZY91PdTfkuvGYX92rjcF5aAWNoDN66wZeYpn0NpxVG3-H9vERB5TTfPSqJbtYT1dL8Q9grjqfN_MvC0ZH0U62Nn4_IWt2f3XxcE9f7Cuqpc9lWg6PHaHdsC/w640-h138/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.01.43.png" width="640" /></a></div><p>▲圖2:EzSpec介面程式碼</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYtwUqRlJh3ciQpH7sLXTYvNfTTW0k0a5do5Hyric9RsGw6mD2xH8AMubX5MuU3eYn13llECvH81UgvKsYJJc69GSX_jcBZYNZ_q2vrlYSilyYGhyUlSxQLHCwJlD3ZEgYd1kYqsnCCC_SFGflIiqrC4M5ScTlfay3_J2AIQHZ5fnAWsLa8uX1wPqCMPq2/s1070/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.16.02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1068" data-original-width="1070" height="551" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYtwUqRlJh3ciQpH7sLXTYvNfTTW0k0a5do5Hyric9RsGw6mD2xH8AMubX5MuU3eYn13llECvH81UgvKsYJJc69GSX_jcBZYNZ_q2vrlYSilyYGhyUlSxQLHCwJlD3ZEgYd1kYqsnCCC_SFGflIiqrC4M5ScTlfay3_J2AIQHZ5fnAWsLa8uX1wPqCMPq2/w553-h551/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.16.02.png" width="553" /></a></div><p>▲圖3:EzSpecTag介面程式碼片段</p> <p> </p> <p>只要測試案例(Test Class)實作EzSpec介面,並依據前幾集介紹的方式撰寫Feature, Story, Scenario, Scenario Outline,執行測試案例之後,ezSpec就會在預設的目錄 target/ezSpec-report 產生報表,請參考圖4。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeFQEzlY_RNSjWpBKN7qTN_Zd4MsRZnXX5rTZtas6jk27UEqNLo4eEFxURYnUHuntKT9TgD_5aa5YGTBTJsHgCX0K8vTOe05yKJ7MUJkj-r8d1HfGqIJIWvB06h7BHng5Yo4WWCotHnNy3IuWSpRfXlfSDrpfBf7Ii0PNKW6CAFgym53ICJOX_ARGgR_Uc/s1110/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.23.35.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="778" data-original-width="1110" height="384" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeFQEzlY_RNSjWpBKN7qTN_Zd4MsRZnXX5rTZtas6jk27UEqNLo4eEFxURYnUHuntKT9TgD_5aa5YGTBTJsHgCX0K8vTOe05yKJ7MUJkj-r8d1HfGqIJIWvB06h7BHng5Yo4WWCotHnNy3IuWSpRfXlfSDrpfBf7Ii0PNKW6CAFgym53ICJOX_ARGgR_Uc/w548-h384/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.23.35.png" width="548" /></a></div><p>▲圖4:ezSpec在預設目錄中產生報表</p> <p> </p> <p>圖5與圖6分別展示ezSpec產生的純文字檔報表與json格式報表。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghIAfE13ExcnWWj18p0ng2RitrwhLiH-dh2Lut6twaTD4Ob31VNh6Ech_6YQHh8165w81qnjD6RkKYYvJuxwGDnKUG_VtvduAzXIyFXMTcRVD1NEyQlrNoqziJYUBwgVghM_MIzdaUezv_kT7YOopiTXrQ8jRJiwEaw1TQW_aLVODkzf48BTHcZSw7Y_hT/s1416/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.25.09.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1416" data-original-width="1146" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghIAfE13ExcnWWj18p0ng2RitrwhLiH-dh2Lut6twaTD4Ob31VNh6Ech_6YQHh8165w81qnjD6RkKYYvJuxwGDnKUG_VtvduAzXIyFXMTcRVD1NEyQlrNoqziJYUBwgVghM_MIzdaUezv_kT7YOopiTXrQ8jRJiwEaw1TQW_aLVODkzf48BTHcZSw7Y_hT/w518-h640/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.25.09.png" width="518" /></a></div><p>▲圖5:ezSpec純文字檔報表範例</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI-d5oEMb6fCSLBbD3adETPwiHsTP68iCE5Fqgf5FpbDHJmPAq4K-tMhyGyxh_uw0g7Nx2ntF3fM9XZw6UQkrnU4QFLxE2hq-7u5mPqbOYWaBsEEdjVzx9T-39BoPZ7PkHiCLSSEnEQa1wYsFECbU-I-lYaXy4bckUgEzeC4zcO7lr5Icvi9_dj0kDo_4C/s2206/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.28.05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2206" data-original-width="2048" height="674" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjI-d5oEMb6fCSLBbD3adETPwiHsTP68iCE5Fqgf5FpbDHJmPAq4K-tMhyGyxh_uw0g7Nx2ntF3fM9XZw6UQkrnU4QFLxE2hq-7u5mPqbOYWaBsEEdjVzx9T-39BoPZ7PkHiCLSSEnEQa1wYsFECbU-I-lYaXy4bckUgEzeC4zcO7lr5Icvi9_dj0kDo_4C/w626-h674/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.28.05.png" width="626" /></a></div><p>▲圖6:ezSpec JSON格式報表範例</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">不要產生報表</font></strong></p> <p>有時候你可能會臨時不要產生某個測試案例的報表,此時可以直接在測試案例上面加上@DisableEzSpecReport。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVnUlwHeBGqIWBgh6QN_ePtzd5cNKrOF9hm8w46hLXwUpCqyac1XLTFmV9S_pA1exhjwrDi4xaioLWufTbGEB-EnxAu2YrKv1UaQsqbvo315qil4nSRWzuv_KE6MUpyFZhcXYCnaG5-mkxhRwFDRKgtTiPnGvz1SGdUQz0i6cqCTWq76McCxdfuOk44sm5/s912/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.20.20.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="122" data-original-width="912" height="73" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgVnUlwHeBGqIWBgh6QN_ePtzd5cNKrOF9hm8w46hLXwUpCqyac1XLTFmV9S_pA1exhjwrDi4xaioLWufTbGEB-EnxAu2YrKv1UaQsqbvo315qil4nSRWzuv_KE6MUpyFZhcXYCnaG5-mkxhRwFDRKgtTiPnGvz1SGdUQz0i6cqCTWq76McCxdfuOk44sm5/w542-h73/%E6%88%AA%E5%9C%96%202023-03-25%20%E4%B8%8A%E5%8D%8811.20.20.png" width="542" /></a></div><p>▲圖7:加上@DisableEzSpecReport就不會產生該測試案例的報表</p> <p> </p> <p>如果想要禁止ezSpec產生所有測試案例的報表,可以設定<strong><font color="#000000">EZREPORT</font></strong>環境變數,把它設為OFF即可。什麼時候會取消報表產生功能?因為產生報表需要時間,也需要寫入檔案,因此會讓測試案例跑得比較慢一點。原本這也不是什麼大問題,但如果你的專案採用類似PiTest這種Mutation Test工具,那麼同一個測試案例會被執行N次,而且在這種情況下,開發人員只需要觀看PiTest產出的報表,並不需要ezSpec的報表。此時,關閉ezSpec產生報表功能,可以加速PiTest執行。</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>報表系統算是ezSpec的最後一哩路,目前ezSpec提供的報表屬於個別驗收測試(Feature file)執行結果的報表,雖然使用者可以透過下一集介紹的方式擴充ezSpec產生使用者自訂報表,但Teddy還是想在ezSpec中提供基本的整合報表功能。等待整合報表功能完成,Teddy就會將ezSpec開源給有興趣的鄉民們使用。</p> <p align="center">***</p> <p>友藏內心獨白:人要衣裝,佛要金裝,報表也是很重要的功能。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-70626263024378359482023-03-25T00:00:00.023+08:002024-03-11T19:02:58.768+08:00使用ezSpec落實行為驅動開發與實例化需求(8):規範平行行為<p>March 24 21:45~23:14</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYorR0s02uOFFlllP5nKiMkzvOqgHgdN2kPUemL21XasQ1v2N6RPB_5JgW7Xx-hE15yiLAdMQeh4vCMKyFcfEqYj_M_0KNwZje88RI_Tj0Pz0HeyDLSBhIJ2ygeg8qDLWfNFhIE7N8LXTQx96y6lHCscAX5rZmzP8yjIfzFKo6rKtagxza_Paz39F1gV_N/s1682/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.15.30.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="972" data-original-width="1682" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYorR0s02uOFFlllP5nKiMkzvOqgHgdN2kPUemL21XasQ1v2N6RPB_5JgW7Xx-hE15yiLAdMQeh4vCMKyFcfEqYj_M_0KNwZje88RI_Tj0Pz0HeyDLSBhIJ2ygeg8qDLWfNFhIE7N8LXTQx96y6lHCscAX5rZmzP8yjIfzFKo6rKtagxza_Paz39F1gV_N/w640-h370/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.15.30.png" width="640" /></a></div><p>▲圖1:電梯的規格範例</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>ezSpec與Gherkin相關的基本功能已經介紹完畢,今天介紹一個Gherkin沒有的功能:「描述平行行為的規格」。</p> <p>Gherkin的Given, When, Then, And, But這些Step依據它們出現在Scenario的先後順序依序執行,對於一些天生就具有平行處理能力的系統,例如在IoT(<a href="https://zh.wikipedia.org/zh-tw/%E7%89%A9%E8%81%94%E7%BD%91" target="_blank">Internet of Things</a>)系統中,多個sensor或device彼此之間都是獨立且平行執行。在這種情況下,用Gherkin就無法表達這些平行執行的行為。</p> <p>三月初的時候實驗室一組研究IoT的學生跟Teddy介紹他們用python開發的工具—<strong><font color="#000000">concurrentSpec</font></strong>,它擴充Gherkin語意讓開發人員撰寫並執行同步行為規格。原本Teddy開發ezSpec並沒有計畫要支援描述平行處理行為,聽完學生的介紹,覺得加上這個功能可以讓描述行為的語意變得更完整,因此花了點時間在ezSpec中支援這個功能。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">平行運作行為的範例</font></strong></p> <p>請參考圖1,該例子節錄自 <Specifying Internet of Things Behaviors in Behavior-Driven Development: Concurrency Enhancement and Tool Support> 這篇論文,論文作者是實驗室IoT小組的成員以及Teddy的指導教授鄭老師。論文中提到一個描述電梯規格的例子,這個例子參考自 Jackson所寫的兩本書:《Software Requirements & Specifications: A Lexicon of Practice, Principles and Prejudices》與《Problem Frames: Analysing and Structuring Software Development Problems》,圖1的中文敘述是請ChatGPT幫忙翻譯。</p> <p>這個例子如果用標準的Gherkin執行,假設第29行執行失敗(無法打開緊急指示燈),測試案例就會停在第29,後面的assertion就不會被驗證。在電梯的例子中,這種行為很顯然是不正確的。因為就算29行與30行都失敗,只要第28行成功(電梯有停在最近的樓層),第39行就應該被檢查(電梯門要在五秒內打開)。</p> <p>有些測試框架可以讓使用者設定:「就算某一個assertion失敗,測試案例還是持續執行」。但就算是採用這種方式來執行圖1的例子,最後結果還是可能錯誤。例如第28行失敗但第31行成功,這表示:「電梯沒有成功停在最近的樓層,但是電梯門最後卻打開了。」這顯然不是使用者所期待的行為。</p> <p>怎麼用Gherkin描述同步行為?concurrentSpec提出一個很簡單的擴充方式:「在不增加Gherkin keyword的前提之下,將Given, When, Then當作<strong><font color="#000000">同步執行群組</font></strong>的起頭,它們之後的And與But將會與它們同步執行。整個同步群族執行完畢之後,才會開始執行下一個同步群組。」換句話說,Given, When, Then彼此之間是循序執行,但它們之後若接著And或But,這些And/But將與它們平行執行。</p> <p>此外,還可以指定每一個Step如果執行失敗,之後的Step是否要繼續執行。</p> <p align="center">***</p> <p>用ezSpec描述圖1行為的程式如圖2所示,Teddy故意讓「打開緊急指示燈」與「取消該叫車請求」發生錯誤(839行與842行),但是這兩個Step如果發生錯誤,下一個Given/When/Then依然會繼續執行(因為指定<strong><font color="#000000">ContinuousAfterFailure</font></strong>參數)。請注意,圖2中的Step執行順序如下,Given, When, Then, Then是循序執行,第一個Then與後面兩個And為平行執行。</p> <ul> <li>Given</li> <li>When</li> <li>Then, And, And (834、838、841行):三個Step平行執行</li> <li>Then</li> </ul> <p>圖2中有一個小細節要注意,就是要讓Scenario以平行的方式執行,必須呼要<font color="#333333"><strong>ExecuteConcurrently()</strong></font>。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI1AGY3rjXNJz3WErfe2961yWCkqyCN9MY4Wsf1wQTfT92N9IO2YRSI4XfB3aLVTohuARzZfDxNccmEHM5JBdyl29kp8DRRqH8wUNoBt9V_gApshqNXWdJsoib8K854RFsr8FPdZHGnBvhYmN3dqEV-6AA7Xwkg8cIambL_JSuL0TikvZpkoq0RBOWD8fi/s2538/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.37.12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1322" data-original-width="2538" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiI1AGY3rjXNJz3WErfe2961yWCkqyCN9MY4Wsf1wQTfT92N9IO2YRSI4XfB3aLVTohuARzZfDxNccmEHM5JBdyl29kp8DRRqH8wUNoBt9V_gApshqNXWdJsoib8K854RFsr8FPdZHGnBvhYmN3dqEV-6AA7Xwkg8cIambL_JSuL0TikvZpkoq0RBOWD8fi/w640-h334/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.37.12.png" width="640" /></a></div><p>▲圖2:用ezSpec描述電梯規格範例</p> <p> </p> <p>圖3顯示圖2執行結果,可以看到兩個And執行失敗並沒有影響後續Then的執行。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd3aAR0dK6xLxVoAzoEmdrVRsXVbR8cA2KlKVOOd5DFFcbNKnGt7kWn6nfcd-hMIaaCPMvgzJILRERRM4qEOyYDCgq0OlZxCKkHD5KspX0PE4DLRXnq0miayTrkFncZvrqHrseGXKw70FKa-2nhHiKozuB9fVy-CEUSfCzOz3Y3KCoFAjdaM_hx1C3NQpu/s1872/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.43.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="850" data-original-width="1872" height="290" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd3aAR0dK6xLxVoAzoEmdrVRsXVbR8cA2KlKVOOd5DFFcbNKnGt7kWn6nfcd-hMIaaCPMvgzJILRERRM4qEOyYDCgq0OlZxCKkHD5KspX0PE4DLRXnq0miayTrkFncZvrqHrseGXKw70FKa-2nhHiKozuB9fVy-CEUSfCzOz3Y3KCoFAjdaM_hx1C3NQpu/w640-h290/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.43.36.png" width="640" /></a></div><p>▲圖3:圖2執行結果報表</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">電梯門打不開</font></strong></p> <p>Teddy修改一下電梯的Scenario,故意讓電梯沒有停在最近樓層,請參考圖4。</p> <p><br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEindiq1BIhlT7YxZGeXtrUexLByphjyqOeMNsbjE-rv4eOa7LhAjdNdFvF7Nrt-R13FdrHzE21sXTFCEBslhcWDVl6519rUpBK8kocm7noZgT1u5pTfYece_Wa7AE2cV4ERH62XTSMEH0BV3JiGXNzb9eVrYwQLKFnGr8STUt0dVJ8a_VdCrfC_dmiHYuKE/s1754/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.57.11.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="252" data-original-width="1754" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEindiq1BIhlT7YxZGeXtrUexLByphjyqOeMNsbjE-rv4eOa7LhAjdNdFvF7Nrt-R13FdrHzE21sXTFCEBslhcWDVl6519rUpBK8kocm7noZgT1u5pTfYece_Wa7AE2cV4ERH62XTSMEH0BV3JiGXNzb9eVrYwQLKFnGr8STUt0dVJ8a_VdCrfC_dmiHYuKE/w640-h92/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.57.11.png" width="640" /></a></div><br /><p><br /></p><p><br /></p><p>▲圖4:模擬電梯沒有停在最近的樓層。</p> <p> </p> <p>修改後的Scenario執行結果如圖5,可以看到因為Then沒有加上ContinuousAfterFailure參數,所以整個同步執行群組執行完畢之後,就不會繼續執行下一個Step。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhER_-CNg-xFvwLqDuD_wAjnqy1rWeW2RazlVNqCIu7LA15Rb-ISiQSh_Sw6hq858Jo25ZOopGQdVoISwan9iJiPLllsTgdDP4bZ9UK24DRqFCRKtT3wQZIx7W5k_5oJ-e1V3hcQcUF0ZmQdFtSIA-GcPoEp92D5zEeoQMQScYssg3d1aFyNxjuu16emrDP/s2672/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.56.31.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1364" data-original-width="2672" height="326" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhER_-CNg-xFvwLqDuD_wAjnqy1rWeW2RazlVNqCIu7LA15Rb-ISiQSh_Sw6hq858Jo25ZOopGQdVoISwan9iJiPLllsTgdDP4bZ9UK24DRqFCRKtT3wQZIx7W5k_5oJ-e1V3hcQcUF0ZmQdFtSIA-GcPoEp92D5zEeoQMQScYssg3d1aFyNxjuu16emrDP/w640-h326/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8810.56.31.png" width="640" /></a></div><p>▲圖5:圖4執行結果報表</p> <p align="center">***</p> <p>看到這裡鄉民們可能會想:「不管電梯有沒有停在最近樓層,我就是要執行最後一個步驟的檢查啊!」也可以,修改一下Then,幫它加上ContinuousAfterFailure,請參考圖6。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirNFTF-HJqvWozVELn5a4tfIwGW2KjpYDeMQtxrOdlDkph5K5vAGuNU-0P6g5NiyCD44LUPggLvtTUPouVHyfuJagGS8T9Hhl5c8CPa3HSPyk5ozDBhCpVEm3OgIy9JRgo8NW48l4KNiS85PGi8I2ZDJxNapSyUeMmhR6W_p8O04Oq21sSlBn7YC7O0ru9/s2212/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8811.02.53.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="224" data-original-width="2212" height="64" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirNFTF-HJqvWozVELn5a4tfIwGW2KjpYDeMQtxrOdlDkph5K5vAGuNU-0P6g5NiyCD44LUPggLvtTUPouVHyfuJagGS8T9Hhl5c8CPa3HSPyk5ozDBhCpVEm3OgIy9JRgo8NW48l4KNiS85PGi8I2ZDJxNapSyUeMmhR6W_p8O04Oq21sSlBn7YC7O0ru9/w640-h64/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8811.02.53.png" width="640" /></a></div><p>▲圖6:加上ContinuousAfterFailure參數,即使電梯未停妥也繼續執行後續驗證步驟</p> <p> </p> <p>修改後的Scenario執行結果如圖7,如果系統行為真的如此,恭喜你,找到一個bug。什麼bug?電梯未停靠最近樓層,但電梯門卻打開了。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFacUhTawN2_YRo4bYv7Xe5YOM-D7Oao-GW9MQjJi5fDL9tBrVTzxbYXjnN_qa71UWzI5w-sEcKdhycqhSwVPbY-zqgjHxCdjKmuuo7A4nPk623YaV9Y7JO7l0pese7ktD9kZVtQ8-3-QjjNzGdof9RLVzoUqT6WQ0sYchrL_PnVjPC0aDj1uL7Ps7H5nb/s1918/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8811.04.12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1056" data-original-width="1918" height="352" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFacUhTawN2_YRo4bYv7Xe5YOM-D7Oao-GW9MQjJi5fDL9tBrVTzxbYXjnN_qa71UWzI5w-sEcKdhycqhSwVPbY-zqgjHxCdjKmuuo7A4nPk623YaV9Y7JO7l0pese7ktD9kZVtQ8-3-QjjNzGdof9RLVzoUqT6WQ0sYchrL_PnVjPC0aDj1uL7Ps7H5nb/w640-h352/%E6%88%AA%E5%9C%96%202023-03-24%20%E4%B8%8B%E5%8D%8811.04.12.png" width="640" /></a></div><p>▲圖7:圖6執行結果報表</p> <p align="center">***</p> <p> </p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>如果鄉民們的系統與IoT系統類似,有著很明顯的平行行為,那麼標準的Gherkin語意就無法描述這些行為。借用concurrentSpec所擴充的Gherkin語意,可以在不增加Gherkin keyword的前提之下,使用ezSpec描述平行系統的行為。</p> <p>ezSpec指定行為的功能差不多介紹完畢,下一集介紹ezSpec產生報表的功能。</p> <p align="center">***</p> <p>友藏內心獨白:快到山頂了。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-20531972711815516672023-03-24T00:00:00.006+08:002024-03-11T19:07:59.558+08:00使用ezSpec落實行為驅動開發與實例化需求(7):將共用步驟寫在Background<p>March 23 21:08~22:20</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIpdzjuvZVETHIkmcb-OQxevxG6YYKcPSmZIU3vq9mggh2fRFuILz4J01LFuuoBTP5HIERZ1u0kQj0UqRaFge5DKwjZSS1Lg2USxuT6A2pBW6DmzBkPpiV4aDLqTpiZQFW6UBmehx-MqEjrAe2tyn68GJFJO8Mv3-bOBJgk3IBUFp7BTx9KGuJT7Otf4eU/s2564/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%8810.19.07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2564" data-original-width="1914" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIpdzjuvZVETHIkmcb-OQxevxG6YYKcPSmZIU3vq9mggh2fRFuILz4J01LFuuoBTP5HIERZ1u0kQj0UqRaFge5DKwjZSS1Lg2USxuT6A2pBW6DmzBkPpiV4aDLqTpiZQFW6UBmehx-MqEjrAe2tyn68GJFJO8Mv3-bOBJgk3IBUFp7BTx9KGuJT7Otf4eU/w478-h640/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%8810.19.07.png" width="478" /></a></div><br /><p>▲第七集了,快到山頂了沒XD</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>前幾集介紹如何撰寫Scenario與Scenario Outline,基本上已經可以處理絕大部分的問題。今天介紹Background,讓鄉民們可以共用不同Scenario之間相同的步驟。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">Background也是一種Scenario</font></strong></p> <p>ezSpec將Background也視為一種Scenario,但它身上通常只有Given與And,用來準備不同Scenario中共用狀態的內容。請參考圖1,首先使用第42行<strong><font color="#000000">newBackground()</font></strong>開啟一個新的Background。與Scenario和Scenario Outline相同,Background一定要隸屬於某一個Story。</p> <p>新增完Background之後,就可以和寫Scenario一樣,撰寫Step。圖1第44行將backgroundSideEffect這個data member的值設為100,然後在47行將UserId設為UUID。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlFngGjuQELeuWP6pMmfSsDxx30A06kBhZDTzjisrL5iSRzL87mXpv1YKUdAY1BiKJHipK5OfF7SOc5khjh5TfoAJ-Twi6-r9m1vaF-xKVowknYUOg49I_pIITp_DOh_tdffgvmIxCBdQgD5B0Mo8peT-GSTaxeGE0Xozm_GK9hNyv8WRxjdtbAmmHmhy-/s2052/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.17.17.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="554" data-original-width="2052" height="172" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlFngGjuQELeuWP6pMmfSsDxx30A06kBhZDTzjisrL5iSRzL87mXpv1YKUdAY1BiKJHipK5OfF7SOc5khjh5TfoAJ-Twi6-r9m1vaF-xKVowknYUOg49I_pIITp_DOh_tdffgvmIxCBdQgD5B0Mo8peT-GSTaxeGE0Xozm_GK9hNyv8WRxjdtbAmmHmhy-/w640-h172/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.17.17.png" width="640" /></a></div><p>▲圖1:ezSpec的Background範例</p> <p align="center">***</p> <p> </p> <p>圖2的Scenario因為可以直接使用到Background的Given與And,所以它就不需要再撰寫一次Given與And,所以它的第一個Step直接就是第61行的When。在第63行中檢查backgroundSideEffect的值使否為100,如果是,就表示Background有先輩執行,接著再執行這個Scenario。第66行則是讀取Background所新增的UserId。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgod2IVNzIs6Ae7J9EjBBxiynWScPuCmu2J-ndPr33ZyPyCVU3UuQWafQZCmPaPCqkA8_ONQRxUdeEZd3Hsx4PeeTQNzcL1Odbi_-vdz-8mqATH1hCGrmRt7NhRaxDjxY-mS_sHkcPts_SAC3eX3WqwhhnW7WZ0bHFDdOKDMKudRjlSW9EVVaxXEi7Q_jqc/s1974/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.47.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="548" data-original-width="1974" height="178" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgod2IVNzIs6Ae7J9EjBBxiynWScPuCmu2J-ndPr33ZyPyCVU3UuQWafQZCmPaPCqkA8_ONQRxUdeEZd3Hsx4PeeTQNzcL1Odbi_-vdz-8mqATH1hCGrmRt7NhRaxDjxY-mS_sHkcPts_SAC3eX3WqwhhnW7WZ0bHFDdOKDMKudRjlSW9EVVaxXEi7Q_jqc/w640-h178/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.47.18.png" width="640" /></a></div><p>▲圖2:在ezSpec的Scenario中讀取Background所設定的資料範例</p> <p> </p> <p>圖3為執行圖2所產生的報表,第9到13為Background的內容,第15~18行為圖2的內容。由這個報表可以很清楚的看出來,雖然Background可以達到重用Step的優點,但使用它的Scenario閱讀起來會變得有點……斷頭…..的感覺。Given不見了,直接從When開始。也就是說,使用者要從重用性與可讀性之間做出取捨。當然也可以修改報表產生程式,重複將Background的Step複製到每一個使用它的Scenario,以提升一點可讀性。</p><div><br /></div><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi37yJuNjgFi7CB-0EQFbRa6EvmsFXpgQoByH3msv_hxE1GoRoQ1qcG9TayOaiR1FpFCt5xy_P_liwEe-OGpLlq9b9WIUv6okc2plHaGXic-PeWOgb7p5NoFXmz4-2CvAydlOPix7ElEuv_DiqZ1nmrpURR04HzQemKNn-HEPnaiwTEBRUtHiMLs6WvTE3/s1810/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.55.14.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="802" data-original-width="1810" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgi37yJuNjgFi7CB-0EQFbRa6EvmsFXpgQoByH3msv_hxE1GoRoQ1qcG9TayOaiR1FpFCt5xy_P_liwEe-OGpLlq9b9WIUv6okc2plHaGXic-PeWOgb7p5NoFXmz4-2CvAydlOPix7ElEuv_DiqZ1nmrpURR04HzQemKNn-HEPnaiwTEBRUtHiMLs6WvTE3/w640-h284/%E6%88%AA%E5%9C%96%202023-03-23%20%E4%B8%8B%E5%8D%889.55.14.png" width="640" /></a></div><div><p>▲圖3:執行使用Background的Scenario所產生的報表</p> <p align="center">***</p> <p> </p> <p><strong><font color="#008000" size="5">結論</font></strong></p> <p>寫到這裡,ezSpec的基本功能算是介紹完畢。在Gherkin 6中新增一個<strong><font color="#000000">Rule</font></strong>關鍵字,用來將Scenario分類到某一個業務規則。Teddy覺得這個關鍵字可以用JUnit 5內建的@Tag annotation達到類似效果,所以暫時沒有在ezSpec中直接支援。</p> <p>ezSpec除了基本的Gherkin語法以外,也參考了concurrentSpec,支援描述併行行為的功能,下一集介紹這個功能。</p> <p align="center">***</p> <p>友藏內心獨白:共用經常會降低可讀性。</p></div>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-1385442824544991732023-03-23T00:00:00.004+08:002024-03-11T19:16:33.091+08:00使用ezSpec落實行為驅動開發與實例化需求(6):Scenario Outline<p>March 21 18:06~19:30</p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiet1yDWae_xj31r_vTe1tS4GBcveGvjzUfJdhj8l1bMJ4YiafRltPtDHLAXEVgSMTdtxZ3kMg9jO3K6V0CvUE6ZS3bEyiKho0zJNPFTVZRw9h3ndtvUQ3DIgaaMlkrvB1dcr4jErE-J-tWKABJZfIN8OPtJXh2d2i-vfqCUI1hpA06Ukd8WYdLN1k0CeUU/s3072/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.48.52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1266" data-original-width="3072" height="264" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiet1yDWae_xj31r_vTe1tS4GBcveGvjzUfJdhj8l1bMJ4YiafRltPtDHLAXEVgSMTdtxZ3kMg9jO3K6V0CvUE6ZS3bEyiKho0zJNPFTVZRw9h3ndtvUQ3DIgaaMlkrvB1dcr4jErE-J-tWKABJZfIN8OPtJXh2d2i-vfqCUI1hpA06Ukd8WYdLN1k0CeUU/w640-h264/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.48.52.png" width="640" /></a></div><p>▲一個Test Method,N個綠燈</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>上一集介紹在ezSpec中四種撰寫Scenario Outline的方式(請參考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec5scenario-outline.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(5):撰寫Scenario Outline的四種方法</a>>),所使用的Examples是比較簡單的單一表格。這一集介紹當Scenario Outline的Examples比較複雜的時候,該如何表達這些Examples。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">JUnit 5的ArgumentsProvider</font></strong></p> <p>在說明如何表達複雜的Examples之前,先介紹兩個基本的介面/類別:ArgumentsProvider與Junit5Examples。</p> <p>JUnit 5 的@ParameterizedTest原本就支援好幾種提供測試資料的方式,上一集使用最簡單的@CsvSource,這一集要用ArgumentsProvider來提供測試資料。參考圖1,ArgumentsProvider是一個介面,只有一個provideArguments method,回傳Stream<? extends Arguments>。</p> <p>Arguments也是一個介面,參考圖2,它身上有一個get mehtod,回傳 Object 陣列,這就是存放參數的地方。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT0Mj0rkoQrvqbXnwVPd8xLa3Tkel2T23_zSwhCQ5vewwG_WLi4rsdewtIb3J36muus0Ei9ymSDxePBCXF4eNPPs8dQPpSDDsw0piVFz4pjyLQ_Rfkj5nrgWQqIU2er-tCk3hmVNcw_tmDrIZRFL_JhT3sVhrIhGcEaJlWVpqa2m24EN_5dkuTMAyWA9x4/s1666/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%886.54.02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="210" data-original-width="1666" height="80" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT0Mj0rkoQrvqbXnwVPd8xLa3Tkel2T23_zSwhCQ5vewwG_WLi4rsdewtIb3J36muus0Ei9ymSDxePBCXF4eNPPs8dQPpSDDsw0piVFz4pjyLQ_Rfkj5nrgWQqIU2er-tCk3hmVNcw_tmDrIZRFL_JhT3sVhrIhGcEaJlWVpqa2m24EN_5dkuTMAyWA9x4/w640-h80/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%886.54.02.png" width="640" /></a></div><p>▲圖1:JUnit 5的ArgumentsProvider 介面</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJdhb4xdjcE2Wyl-XN3yDW5l57Hv4qtgti3KcO8IThXS-tJ1uhpiZshwZVJxtgeVV4F8NHmyKzv8WUv361o9dkC7B4uCZm2Dnxkv5AkdY3N9F_b-vLGVh2kcN_-RVgaNZG8HBT9yKntvw-5gHoSMkzBrH2N-B-4pHay6Y5GxCaSgXU25djHK29XyEW6123/s1394/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%886.55.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="546" data-original-width="1394" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJdhb4xdjcE2Wyl-XN3yDW5l57Hv4qtgti3KcO8IThXS-tJ1uhpiZshwZVJxtgeVV4F8NHmyKzv8WUv361o9dkC7B4uCZm2Dnxkv5AkdY3N9F_b-vLGVh2kcN_-RVgaNZG8HBT9yKntvw-5gHoSMkzBrH2N-B-4pHay6Y5GxCaSgXU25djHK29XyEW6123/w640-h250/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%886.55.48.png" width="640" /></a></div><p>▲圖2:JUnit 5的Arguments介面</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">ezSpec的Junit5Examples</font></strong></p> <p>ArgumentsProvider是JUnit 5 設計給的@ParameterizedTest使用的介面,ezSpec的Scenario Outline接受的參數是Examples,兩者介面不同。因此Teddy設計一個Junit5Examples抽象類別,透過它可以轉換ezSpec的Examples以及 JUnit 5的ArgumentsProvider,如圖3所示。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAUi0svLlEZaA6qYtqWgvaEIJafmwuLh3a2moVmfqWpfVAyP0xpcOhdIYsd_Ii4uZQ8ZnCmhz8Z2Gt6ryuAZZ3NzuyiCsThXuV9EO-yuG5oCG-6THQDIgtNSYy1gFXGFmnz1t8Vqojax5MsOnspWZYLHBU1VJ4x8HfPj8px7bLV40LA7NboTviICBAQtc6/s1550/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.02.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="740" data-original-width="1550" height="306" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAUi0svLlEZaA6qYtqWgvaEIJafmwuLh3a2moVmfqWpfVAyP0xpcOhdIYsd_Ii4uZQ8ZnCmhz8Z2Gt6ryuAZZ3NzuyiCsThXuV9EO-yuG5oCG-6THQDIgtNSYy1gFXGFmnz1t8Vqojax5MsOnspWZYLHBU1VJ4x8HfPj8px7bLV40LA7NboTviICBAQtc6/w640-h306/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.02.37.png" width="640" /></a></div><p>▲圖3:ezSpec的Junit5Examples抽象類別</p> <p align="center">***</p> <p> </p> <p><strong><font color="#008000" size="5">RenameWorkflow Use Case的Scenario Outline</font></strong></p> <p>參考圖4,假設你要幫ezKanban的RenameWorkflow使用案例定規格,你想到兩個主要的Scenarios:</p> <ul> <li>Scenario 1:新的名稱與舊的名稱不同,執行RenameWorkflow使用案例之後會產生一個領域事件。</li> <li>Scenario 2:新的名稱與舊的名稱相同,執行RenameWorkflow使用案例之後不會產生領域事件。</li> </ul> <p>針對這兩個Scenario,你一共找出五種不同的測試資料,例如把Workflow的名字從dev改成deV、從dev改成DEV,或是從dev改成dev。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYRw8GBMO2sHnjlGtRIvlTwUwgCIu9DiOALqXGvYccNNZJlJQVtxsShy94KIvONqU9BS687RUwGT6_lXKKeCjV_YQHUlk-55I09sWBVtfAvc1KO6FtSpC6N3GW6H2LxBPXyAW5eFutwBqJvDXHWe4-RoZDzsTnW_gtwhe8hVHZ5IMLRTSinCyYKBlDM3Us/s2262/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.06.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2262" data-original-width="1374" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYRw8GBMO2sHnjlGtRIvlTwUwgCIu9DiOALqXGvYccNNZJlJQVtxsShy94KIvONqU9BS687RUwGT6_lXKKeCjV_YQHUlk-55I09sWBVtfAvc1KO6FtSpC6N3GW6H2LxBPXyAW5eFutwBqJvDXHWe4-RoZDzsTnW_gtwhe8hVHZ5IMLRTSinCyYKBlDM3Us/w388-h640/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.06.18.png" width="388" /></a></div><p>▲圖4:Rename a workflow 範例</p> <p> </p> <p>圖4的Scenario Outline,要如何用ezSpec來表達?首先定義測試資料,請參考圖5與圖6。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimS_q9jqhQEEjVlobS89n-FUnu1rd1KCETzaw1Ntq-ZK2tT-hpFqUw601A-sHwxOh9MVz9s8OXEf0DNzJuHG87yjHl-UkOsdLlZ0SWDtKo0k99x7te0ShqaUb-T3HEGC9lLoHmawtbytJ503ONjamDNyc2ABzbo97R9jM3cqph84GgZXkTHveF-RwZlsKb/s1674/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.13.52.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="842" data-original-width="1674" height="322" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimS_q9jqhQEEjVlobS89n-FUnu1rd1KCETzaw1Ntq-ZK2tT-hpFqUw601A-sHwxOh9MVz9s8OXEf0DNzJuHG87yjHl-UkOsdLlZ0SWDtKo0k99x7te0ShqaUb-T3HEGC9lLoHmawtbytJ503ONjamDNyc2ABzbo97R9jM3cqph84GgZXkTHveF-RwZlsKb/w640-h322/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.13.52.png" width="640" /></a></div><p>▲圖5:不同Workflow名稱的測試資料(Examples)</p> <p> </p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMVYHwu3OJ69Rs5X_gNUGEZS_w6lpNhYcurnFZD1rzB8jUiTgtYVbPBIbE9y5v7hhF81lhHH_Q2bSP3taLZs6bmwTAi2n98YnNmGE1ZllqXqAUb3EWkl_0QB4M1FY8r0o6GV_lL5yO-3qLVv7K3pMbK3A4X9so9xfcvscqghzyZFyoOCFEeG_IMegM7dhG/s1984/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.14.03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="710" data-original-width="1984" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMVYHwu3OJ69Rs5X_gNUGEZS_w6lpNhYcurnFZD1rzB8jUiTgtYVbPBIbE9y5v7hhF81lhHH_Q2bSP3taLZs6bmwTAi2n98YnNmGE1ZllqXqAUb3EWkl_0QB4M1FY8r0o6GV_lL5yO-3qLVv7K3pMbK3A4X9so9xfcvscqghzyZFyoOCFEeG_IMegM7dhG/w640-h230/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.14.03.png" width="640" /></a></div><p>▲圖6:相同Workflow名稱的測試資料(Examples)</p> <p> </p> <p>準備好Examples之後,就可以在Scenario Outline中使用它們。參考圖7,採用上一集所介紹的方法一來撰寫Scenario Outline,唯一不同處在第61行:</p> <p><strong><font color="#000000">WithExamples( Junit5Examples.get(different_name_examples.class), </font></strong></p> <p><strong><font color="#000000"> Junit5Examples.get(same_name_examples.class))</font></strong></p> <p>直接指定Examples的class name來獲得測試資料。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj30ZmI9r_c5e6nCht6f1ZklgqQ1mRgCrR_jOnnq6bWc06LCwbNCgZ0k1pLcnDUjXk0jfIK3aaYN6ahUkGm1Ut7ijxAN22aabZ85GUOR1vzZax6zuQFVOkHsbi6ipFUNQzddF_i99SUZo5aZzhHq8VqUbazR-W4jx_x11KEQNZpVhBJtB8MG9uoi48cirTy/s2204/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.17.22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="862" data-original-width="2204" height="250" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj30ZmI9r_c5e6nCht6f1ZklgqQ1mRgCrR_jOnnq6bWc06LCwbNCgZ0k1pLcnDUjXk0jfIK3aaYN6ahUkGm1Ut7ijxAN22aabZ85GUOR1vzZax6zuQFVOkHsbi6ipFUNQzddF_i99SUZo5aZzhHq8VqUbazR-W4jx_x11KEQNZpVhBJtB8MG9uoi48cirTy/w640-h250/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.17.22.png" width="640" /></a></div><p>▲圖7:使用Junit5Examples的具體類別當作測試資料來源提供給ezSpec</p> <p> </p> <p>另外,different_name_examples與same_name_examples也可以作為@ParameterizedTest的資料來源,用來執行Scenario Outline的,請參考圖8。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRzzie1EBe4dzV-nupHXSHSLaf3MSNDiS8uSboXW6zZYoc1qPW6Ww2kjVmfYW1hR3Au6hkx8mZ3FNFw44VIZ7HDuVbxfteWY2yRLr9dKyRqSOA6PsDEdhc-nquvGdMZdzP_W_veVk_7ZNrVXCNyKjDVQoqdEHlV4D6EKfj59_de90jKsD4ijujGX1dEpsv/s2380/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.22.25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1106" data-original-width="2380" height="298" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRzzie1EBe4dzV-nupHXSHSLaf3MSNDiS8uSboXW6zZYoc1qPW6Ww2kjVmfYW1hR3Au6hkx8mZ3FNFw44VIZ7HDuVbxfteWY2yRLr9dKyRqSOA6PsDEdhc-nquvGdMZdzP_W_veVk_7ZNrVXCNyKjDVQoqdEHlV4D6EKfj59_de90jKsD4ijujGX1dEpsv/w640-h298/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.22.25.png" width="640" /></a></div><p>▲圖8:使用Junit5Examples的具體類別當作測試資料來源提供給@ParameterizedTest</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">更複雜的測試資料:表格中還有表格</font></strong></p> <p>請參考圖9,這是提供給ezKanban的MoveLane使用案例的驗收測試資料,其中givenWorkflow表示在Given階段初始化的Workflow狀態,expectedSubStageAsRootStage表示移動Lane之後Workflow的狀態。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil_SHNUL2GHlPXD9SUXGu36y6j2-_1Rgp1VZXLSjfdQLECDcwQuF8n0NzO84C4XztKRtHh3N3gEHyAl5932wknOgtqdlUfAwks8ZyHg7tJffKSw7HhUFEbCCQx3P2ReBqX4SuwCtYyMDszVdKTTnI4uD5kgFJacH2-2zS7-dXLzMtw5mL5fplDX1mBWbgF/s2416/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.28.24.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1644" data-original-width="2416" height="436" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil_SHNUL2GHlPXD9SUXGu36y6j2-_1Rgp1VZXLSjfdQLECDcwQuF8n0NzO84C4XztKRtHh3N3gEHyAl5932wknOgtqdlUfAwks8ZyHg7tJffKSw7HhUFEbCCQx3P2ReBqX4SuwCtYyMDszVdKTTnI4uD5kgFJacH2-2zS7-dXLzMtw5mL5fplDX1mBWbgF/w640-h436/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.28.24.png" width="640" /></a></div><p>▲圖9:Examples包含表格的範例</p> <p> </p> <p>MoveLane的Scenario Outline如圖10所示,可以看到在89行指定了<given_workflow>參數(圖9中的givenWorkflow表格),以及在123行指定了<expected_workflow>參數(圖9中的expectedSubStageAsRootStage表格)。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzH1-OUYRQwbAgsQirAUA5cvr3Xfrn8JTjHi9uJI65igstuUCOxLtmYV14lJ56DDMoL7nNbz8fB4EH83-TOmiDz_rAFAhpZDUvF6dxp5zDd9jZeluXxmQjMPvh4ldjDLFDDO2M9JW0hyphenhyphenVSEZ5jVeHBiEF19U6AnxeyEPHZ5734HEIc5BE_4bJg5hWp7XJR/s2252/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.33.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1376" data-original-width="2252" height="392" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzH1-OUYRQwbAgsQirAUA5cvr3Xfrn8JTjHi9uJI65igstuUCOxLtmYV14lJ56DDMoL7nNbz8fB4EH83-TOmiDz_rAFAhpZDUvF6dxp5zDd9jZeluXxmQjMPvh4ldjDLFDDO2M9JW0hyphenhyphenVSEZ5jVeHBiEF19U6AnxeyEPHZ5734HEIc5BE_4bJg5hWp7XJR/w640-h392/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.33.36.png" width="640" /></a></div><p>▲圖10:MoveLane Scenario Outline範例</p> <p> </p> <p>圖10的執行結果如圖11所示,可以看到第22是最外層的Examples表格,其內部的given_workflow與expected_workflow這兩個column各是另一個表格。可以透過ezSpec很簡單的設計複雜的驗收測試資料,使用上也很簡單。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivapRlNJz33jF-D8se5kob2YAdbR_0OBXYZ4YkhZvm0GDuauqDfwRb0qGKZ-f-cXaRRndMoj6N_Nel0B0uNiSWlmBYw4COifqo1tbMmGu3D_mx0kdRUXfACrvdnEyfZZaOF3LgBVZsQ70Jaf50I2gab6jLFQ32CKk2bcV8r8AQdwewDtyI_xxqGV4JNHSH/s2366/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.38.56.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1968" data-original-width="2366" height="532" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivapRlNJz33jF-D8se5kob2YAdbR_0OBXYZ4YkhZvm0GDuauqDfwRb0qGKZ-f-cXaRRndMoj6N_Nel0B0uNiSWlmBYw4COifqo1tbMmGu3D_mx0kdRUXfACrvdnEyfZZaOF3LgBVZsQ70Jaf50I2gab6jLFQ32CKk2bcV8r8AQdwewDtyI_xxqGV4JNHSH/w640-h532/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%887.38.56.png" width="640" /></a></div><p>▲圖11:MoveLane Scenario Outline執行結果報表(只顯示部分)</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結語</font></strong></p> <p>透過ezSpec的Junit5Examples可以設計同時讓ezSpec與JUnit 5的ParameterizedTest所使用的驗收測試資料,每一種測試資料獨立定義在一個Junit5Examples類別中,容易管理與撰寫。</p> <p>寫到這裡,常用的ezSpec功能已經介紹完畢。下一集介紹Background,可以用來將相同的Given寫在一起,以利重複使用與減少重複程式碼。</p> <p align="center">***</p> <p>友藏內心獨白:Table內的Table,還是Table。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-29554491953486440862023-03-22T00:00:00.001+08:002024-03-11T19:22:57.260+08:00使用ezSpec落實行為驅動開發與實例化需求(5):撰寫Scenario Outline的四種方法<p>March 21 09:47~12:28</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxulR2aFJlpI69DimMrlWpVYUD9QxoCx1MKAyP2xSzdYmyhglurKhKc_wuM4tWphtcOBQi8924shyphenhyphenhAt_GG7XhITTpVXsLJGhgBH9E21UFjrLfr3RIXbA6fb8pGKuWBqWgVgHv4xZb3S0kwDd3cV6Onv4Y1T6sjZlVL0R7dXIAXR1zfi0mtELHdYqq6pfX/s1348/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8810.43.41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="486" data-original-width="1348" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxulR2aFJlpI69DimMrlWpVYUD9QxoCx1MKAyP2xSzdYmyhglurKhKc_wuM4tWphtcOBQi8924shyphenhyphenhAt_GG7XhITTpVXsLJGhgBH9E21UFjrLfr3RIXbA6fb8pGKuWBqWgVgHv4xZb3S0kwDd3cV6Onv4Y1T6sjZlVL0R7dXIAXR1zfi0mtELHdYqq6pfX/w640-h230/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8810.43.41.png" width="640" /></a></div><p>▲圖1:Scenario Outline範例</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>前兩集介紹在ezSpec中撰寫Scenario的方式(請參考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec3scenario.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(3):撰寫Scenario與傳遞簡單參數</a>> 與 <<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec4scenario.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(4):在Scenario中使用表格</a>>)。當你開始採用BDD(行為驅動開發)或SBE(實例化需求)開發系統,針對同一個Feature或Story中的多個Scenario,很可能發生執行步驟相同或相似,只有輸入的資料以及在When中所用來檢查的expected result不同的情況。此時可考慮將這些Scenario改寫成Scenario Outline,用以簡化feature file的撰寫。</p> <p>ezSpec支援四種不同的方式撰寫與執行Scenario Outline,以下逐一介紹。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">方法一:用For Each執行Scenario Outline</font></strong></p> <p>Scenario翻譯成<strong><font color="#000000">劇情</font></strong>或<strong><font color="#000000">場景</font></strong>,Scenario Outline則是<strong><font color="#000000">劇情大綱</font></strong>或<strong><font color="#000000">場景大綱</font></strong>。簡單來說,Scenario Outline就是餵一組資料給Scenario,讓這個Scenario執行好幾次。用傳統測試的術語,這種測試稱為<strong><font color="#000000">參數化測試案例</font></strong>或<strong><font color="#000000">資料驅動測試案例</font></strong>。 </p> <p>圖1是一個簡單的Scenario Outline,其目的為依據商品未稅金額計算含稅總價,一共有四個例子(四筆測試資料),分別涵蓋四捨五入、可開立零元發票以及兩個邊界條件。為了撰寫這四個例子,最無腦的方式是把它們寫成四個Scenario。但是這樣一來這四個Scenario除了測試資料不同以外,其他的執行步驟都一樣,也就是說寫成四個Scenario會產生很多重複程式碼,此時改用Scenario Outline就可以解決這個問題。</p> <p>為了儘量與JUnit 5整合,ezSpec提供好幾種撰寫Scenario Outline的方式。首先看到圖2:</p> <ul> <li>第74行:執行 <strong><font color="#000000">newScenarioOutline()</font></strong> 在Story身上產生一個新的Scenario Outline。</li> <li>第75行:用 <strong><font color="#000000">WithExamples(examples)</font></strong> 將指定該Scenario Outline所使用的Examples(也就是測試資料)。</li> <li>第76行:呼叫 <strong><font color="#000000">RuntimeScenarios()</font></strong> 獲得List<RunimeScenario>。Scenario Outline會依據所輸入的Examples資料筆數,產生N個RunimeScenario。</li> <li>第77行:使用 <strong><font color="#000000">forEach(example -> {} )</font></strong> 的方式來指定每一次執行RunimeScenario的Step。</li> </ul> <p>之後的寫法就跟一般的Scenario一樣,唯一的差別在於讀取Examples參數的方式。在標準的Scenario中,透過env.row() 可以讀取表格中的資料。在Scenario Outline的情況下,RunimeScenario每次只會看到一筆Examples的資料,此時要用<strong><font color="#000000">env.getInput().get(column_name)</font></strong> 的方式來讀取Examples的資料,如圖2的82~85行所示。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFJPOYYOPoJ9mFqBAq7v5oWgrBftNREE0QtjaGtk8yTowYJTYZi69OVhA1WY6FHqbQwIdIpoJtfTML5Cz5ID9YyZJwI_n8P19Wdpf_0k1VbLCLtpM9-ArR7Mbgl9xF0ch5F9OcGM1ozB54KqWo06TNcO782RmQZjRuatjZJOG65YvacQPMbB-qKUSYM_t/s1780/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8810.55.59.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1100" data-original-width="1780" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDFJPOYYOPoJ9mFqBAq7v5oWgrBftNREE0QtjaGtk8yTowYJTYZi69OVhA1WY6FHqbQwIdIpoJtfTML5Cz5ID9YyZJwI_n8P19Wdpf_0k1VbLCLtpM9-ArR7Mbgl9xF0ch5F9OcGM1ozB54KqWo06TNcO782RmQZjRuatjZJOG65YvacQPMbB-qKUSYM_t/w640-h396/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8810.55.59.png" width="640" /></a></div><p>▲圖2:ezSpec的Scenario Outline範例,採用forEach執行</p> <p> </p> <p>由於圖2的Scenario Outline只是一般標準的test method,因此在JUnit的報表中,只會看到一筆執行結果,如圖3。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTUo8DO0F8zDPMGqalyWrJfNi0PZSekBE4rnbkQvzLAZeeeyko_8azY8vs1PCxPkZ9AaOZ4qcCiqQE5xjDK72QHS6jxi8GMOD6LeS_LcK0q6FyihEc-i8_o2nCtBerR3FuhyFYJjSOpMO7lVb3To8cDLsf5C5VHNDVaw1tVYJX6iKQQ-smfLeD-KfO_h_3/s758/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.08.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="158" data-original-width="758" height="134" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTUo8DO0F8zDPMGqalyWrJfNi0PZSekBE4rnbkQvzLAZeeeyko_8azY8vs1PCxPkZ9AaOZ4qcCiqQE5xjDK72QHS6jxi8GMOD6LeS_LcK0q6FyihEc-i8_o2nCtBerR3FuhyFYJjSOpMO7lVb3To8cDLsf5C5VHNDVaw1tVYJX6iKQQ-smfLeD-KfO_h_3/w640-h134/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.08.48.png" width="640" /></a></div><p>▲圖3:圖2執行結果的JUnit報表</p> <p> </p> <p>雖然JUnit報表中只會有一筆資料,但ezSpec產生的報表會包含Scenario Outline每次的執行結果,請參考圖4。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsbFh0Wyb1X8DShS-ostpyWCrPS27Fa3V_QhCUhacNgVUTwJFkvENwAGSWf5PP4B1_IH8xioD546BwI1ZYbuYl1F-exOF2kEIuatCrDWgUa6nfL5vbDydxrfAFomjnH0Sm7zvUghRBS9QrqK9EM5Bn2jaTIXI_g6ok2s90_W0YdQGPY_RvrxkAMZ7wJ6sF/s1338/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.10.25.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1338" data-original-width="1236" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsbFh0Wyb1X8DShS-ostpyWCrPS27Fa3V_QhCUhacNgVUTwJFkvENwAGSWf5PP4B1_IH8xioD546BwI1ZYbuYl1F-exOF2kEIuatCrDWgUa6nfL5vbDydxrfAFomjnH0Sm7zvUghRBS9QrqK9EM5Bn2jaTIXI_g6ok2s90_W0YdQGPY_RvrxkAMZ7wJ6sF/w592-h640/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.10.25.png" width="592" /></a></div><p>▲圖4:圖2執行結果的ezSpect報表</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">方法二:</font></strong><strong><font color="#008000" size="5">用@ParameterizedTest執行Scenario Outline</font></strong></p> <p>如果開發人員希望在JUnit的報表中看到Scenario Outline每一次執行的結果,可以改用JUnit 5的<strong><font color="#000000">@ParameterizedTest</font></strong>來執行Scenario Outline,請參考圖5。JUnit 5的ParameterizedTest直接將每次執行的測試資料透過test method的arguments接收資料,ParameterizedTest支援多種指定測試資料(相當於圖1的Examples)的方法,圖5所示為透過@CsvSource的方式指定測試資料。</p> <p>使用ezSpec透過ParameterizedTest撰寫Scenario Outline的方式與圖2類似,除了將原本的@Test換成@ParameterizedTest,以及使用@ParameterizedTest所規定的設定測試資料方式以外,主要有以下兩點不同之處:</p> <ul> <li><strong><font color="#000000">不需要自行使用for each</font></strong>:因為JUnit 5的ParameterizedTest會自動執行該test method好幾次,因此開發人員就不需要如同圖2中透過RuntimeScenarios().forEach的方式來執行RuntimeScenarios。</li> <li><strong><font color="#000000">用WithParameterizedExamples取代圖2的WithExamples</font></strong>:由於ParameterizedTest的測試資料不包含表格的表頭,因此需要透過WithParameterizedExamples將測試資料的表頭告知ezSpec,如此一來在描述每一個Step的時候,若使用到 <tax_excluded> 等變數時,ezSpec才知道要如何將這些變數替換成測試資料中實際的數值。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhkgUFt5fHKv4KzKU-nmW0_29y6vMKdOhAbSODI-BIew6VbUu4FUvIKExQMCyLFb3g45TezXAUWvUVocGjqzXCjnE9JAX7W6QY1vBF0GaArQFeXlHm3WY0hACnCtPRgkW3Zg3juB2C7GqrwxK8aZGJi7cwKOoQjXIlztu1RtYhzVu4LDff-YQwcSi27-8u/s2390/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.33.23.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1164" data-original-width="2390" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhkgUFt5fHKv4KzKU-nmW0_29y6vMKdOhAbSODI-BIew6VbUu4FUvIKExQMCyLFb3g45TezXAUWvUVocGjqzXCjnE9JAX7W6QY1vBF0GaArQFeXlHm3WY0hACnCtPRgkW3Zg3juB2C7GqrwxK8aZGJi7cwKOoQjXIlztu1RtYhzVu4LDff-YQwcSi27-8u/w640-h312/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.33.23.png" width="640" /></a></div><p>▲圖5:使用@ParameterizedTest執行ezSpec的Scenario Outline</p> <p> </p> <p>使用ParameterizedTest有一個好處,就是JUnit的報表可以顯示每次Scenario Outline的執行結果,如圖6所示。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0oNJ94X9GPnpKU_m8dOxkDw207ijPUiRDCTR197CL8m8XLdv6v7Owccx5PF7xZPkiiIUupMUdTtKltVPR08lHKhfVJIEP845rewrBqUBbej32EoGKdcrEyQLq_aV5shz8CDzxMVvvhK6W_O9c56TkdvFlWT8EkCa4yEeJCUbLP7ewURFf2XepGKGRAlsm/s1402/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.29.41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="334" data-original-width="1402" height="152" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0oNJ94X9GPnpKU_m8dOxkDw207ijPUiRDCTR197CL8m8XLdv6v7Owccx5PF7xZPkiiIUupMUdTtKltVPR08lHKhfVJIEP845rewrBqUBbej32EoGKdcrEyQLq_aV5shz8CDzxMVvvhK6W_O9c56TkdvFlWT8EkCa4yEeJCUbLP7ewURFf2XepGKGRAlsm/w640-h152/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.29.41.png" width="640" /></a></div><p>▲圖6:圖5執行結果的JUnit報表</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">方法三:</font></strong><strong><font color="#008000" size="5">用@EzScenarioOutline執行Scenario Outline</font></strong></p> <p>開發人員也可以使用ezSpec的@EzScenarioOutline來執行Scenario Outline,如圖7所示。在這種情況下就不需要使用RuntimeScenarios().forEach的方式來執行RuntimeScenarios,ScenarioOutline的內容除了要呼叫WithExamples以外,基本上與Scenario沒什麼差別。主要差異有以下三點:</p> <ul> <li><a>@EzScenarioOutline</a>要自行執行執行次數,以圖7的例子,因為測試資料有四筆,因此指定<a>@EzScenarioOutline</a>(4)</li> <li>test method需要接受一個型別為<strong><font color="#000000">RepetitionInfo</font></strong>的參數,如圖7第119行。</li> <li>要改用<strong><font color="#000000">Execute(repetitionInfo.getCurrentRepetition())</font></strong>來執行RuntimeScenarios,如圖7第138行。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0q9rheilfT5N-4TKbNrwSbxFEvlU-Umps3AupoBpT3b-7V9POTIKG6jnou9-pVMntk8op7K2NJKDX2YcqukLdhTNb2kwBcX14fm3iKYrOqGyHSwfArHCF-CVhHNHA_jiBIZKarh9T7SY6a-9WR4_gydy_eWyY2GGjTzebfmYvfjT4b9K1ZMlYRlDLnkbE/s1826/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.55.04.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="978" data-original-width="1826" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0q9rheilfT5N-4TKbNrwSbxFEvlU-Umps3AupoBpT3b-7V9POTIKG6jnou9-pVMntk8op7K2NJKDX2YcqukLdhTNb2kwBcX14fm3iKYrOqGyHSwfArHCF-CVhHNHA_jiBIZKarh9T7SY6a-9WR4_gydy_eWyY2GGjTzebfmYvfjT4b9K1ZMlYRlDLnkbE/w640-h342/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%8811.55.04.png" width="640" /></a></div><p>▲圖7:使用@ParameterizedTest執行ezSpec的Scenario Outline</p> <p> </p> <p> </p> <p>使用@EzScenarioOutline可以達到和@ParameterizedTest類似的效果,在JUnit的報表中顯示每次Scenario Outline的執行結果(但無法顯示文字說明),如圖8所示。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBteZpcXseduWba_lLmXRpZjmIt4hV8Syli0cn0z_uV5F81Qek6KuAS2vrTe5CURmfFrJiboVZ-mmVG2ERGTR7HWIpRR8-qPsGJBhJ0dhPeupt4d3hD6UubMajbCuiNzsDbV7ZQ7GGIHdoxqj38H-4d8cM5LzRYk9b5t7Zdla6Vx85MObMOa6paUKqTIxC/s1074/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.03.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="314" data-original-width="1074" height="188" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBteZpcXseduWba_lLmXRpZjmIt4hV8Syli0cn0z_uV5F81Qek6KuAS2vrTe5CURmfFrJiboVZ-mmVG2ERGTR7HWIpRR8-qPsGJBhJ0dhPeupt4d3hD6UubMajbCuiNzsDbV7ZQ7GGIHdoxqj38H-4d8cM5LzRYk9b5t7Zdla6Vx85MObMOa6paUKqTIxC/w640-h188/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.03.48.png" width="640" /></a></div><p>▲圖8:圖7執行結果的JUnit報表</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">方法四:</font></strong><strong><font color="#008000" size="5">用@DynamicScenario執行Scenario Outline</font></strong></p> <p>最後一種執行Scenario Outline的方式,使是用ezSpec的@DynamicScenario,請參考圖9。@DynamicScenario的撰寫方式和@EzScenarioOutline大致相同,不同點有:</p> <ul> <li>test method須回傳<strong><font color="#000000">DynamicNode</font></strong>,如圖9第141行。</li> <li>用<strong><font color="#000000">DynamicExecute()</font></strong>取代Execute()<strong><font color="#000000"></font></strong>,如圖9第160行。</li> <li>需<strong><font color="#000000">return</font></strong>整個DynamicExecute執行結果,如圖9第149行。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4kJRYkEvCs8_LaxgNe0j87ZDeFceEqtUAeD_Vz6RbsVYZLxGaDYc6FRWaIPluGYCkVH4Lcy_DoqP9gfSP8mfADb2iMBkuWMSrSoBaF8thjjoTBqHlfgyQxCkPz76v48szTfvEy9KHGeBEB2bqewM9PTi9AfT49ggJf-TZZwtU2_GoOHAnXm7JcXFOh8wD/s1744/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.09.18.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="982" data-original-width="1744" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg4kJRYkEvCs8_LaxgNe0j87ZDeFceEqtUAeD_Vz6RbsVYZLxGaDYc6FRWaIPluGYCkVH4Lcy_DoqP9gfSP8mfADb2iMBkuWMSrSoBaF8thjjoTBqHlfgyQxCkPz76v48szTfvEy9KHGeBEB2bqewM9PTi9AfT49ggJf-TZZwtU2_GoOHAnXm7JcXFOh8wD/w640-h360/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.09.18.png" width="640" /></a></div><p>▲圖9:使用@DynamicScenario執行ezSpec的Scenario Outline</p> <p><br /></p><p>使用@DynamicScenario在JUnit的報表中可以顯示最詳細的資料,除了每次Scenario Outline的執行結果,還包含每一個Step的內容與執行結果,如圖10所示。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU2iPX8t-ogW_jlvDw7DRt9yopW6nAJvI4RTo21U2ghzE6KbkFegD94HxUpBbRl665JyXfU-4DGYYnnvpLw0GwkBu63LvSnYjiGILhQP7dwCQmW7OnBQRkF29h4hH9ZZcH2x7fJBGuQvXwOMuMZQrX_P74KtskJ98QDF5d_KCuQqhf2O28mO9QyefXxnqM/s1610/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.17.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="516" data-original-width="1610" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgU2iPX8t-ogW_jlvDw7DRt9yopW6nAJvI4RTo21U2ghzE6KbkFegD94HxUpBbRl665JyXfU-4DGYYnnvpLw0GwkBu63LvSnYjiGILhQP7dwCQmW7OnBQRkF29h4hH9ZZcH2x7fJBGuQvXwOMuMZQrX_P74KtskJ98QDF5d_KCuQqhf2O28mO9QyefXxnqM/w640-h206/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8B%E5%8D%8812.17.54.png" width="640" /></a></div><p>▲圖10:圖9執行結果的JUnit報表</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結語</font></strong></p> <p>本集介紹四種不同撰寫與執行Scenario Outline的方法,除了撰寫方式略有不同,以及JUnit產生的報表有所差異以外,其執行結果ezSpec所產生的報表基本上都是相同的。開發人員可以選擇自己認為比較喜歡的與法表達方式來撰寫Scenario Outline。</p> <p>在本集中Scenario Outline的Examples是一個簡單的表格,下一集將介紹如何在Scenario Outline中指定表格中含有表格的測試資料。</p> <p align="center">***</p> <p>友藏內心獨白:太多選擇也是一種煩惱。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-91970895323927163642023-03-21T06:46:00.003+08:002024-03-11T20:19:42.938+08:00使用ezSpec落實行為驅動開發與實例化需求(4):在Scenario中使用表格<p>March 21 05:40~6:45</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsm1mVKzPLRyKwEvR7ORh1o8FJygbnDQVX9qiJQho-gFwz77UuUft8jgYyivA0ZxdviPhquv6IuLzOYJoT-c5a1g3IZXzhet4dTWmX18PqaFFo61vrCf0aMhYc7OEs8qgn8gFzni0DDN5ObTBa0Ox84fLwz8qaMNNMi-Ha0Ik04wifv4lmUqTcKRbMWVjF/s2112/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%885.49.00.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1236" data-original-width="2112" height="374" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsm1mVKzPLRyKwEvR7ORh1o8FJygbnDQVX9qiJQho-gFwz77UuUft8jgYyivA0ZxdviPhquv6IuLzOYJoT-c5a1g3IZXzhet4dTWmX18PqaFFo61vrCf0aMhYc7OEs8qgn8gFzni0DDN5ObTBa0Ox84fLwz8qaMNNMi-Ha0Ik04wifv4lmUqTcKRbMWVjF/w640-h374/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%885.49.00.png" width="640" /></a></div><p>▲圖1:使用ezSpec在Scenario中讀取單一表格資料範例,表格內容取自《BDD in Action》一書</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>上一集介紹如何使用ezSpec撰寫Scenario、從Step描述文字內容中讀取參數、以及在不同的Step Definition之間傳遞資料的方法(請參考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec3scenario.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(3):撰寫Scenario與傳遞簡單參數</a>>),今天介紹如何在Scenario中讀取表格資料。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">讀取單一表格</font></strong></p> <p>請參考圖1的85~87行,ezSpec支援Gherkin的表格,以 <strong><font color="#000000">|</font></strong> 符號分隔不同的column。通常表格的第一列為表頭,之後每一列為表格內容。ezSpec支援兩種讀取表格的方式:</p> <ul> <li><strong><font color="#000000">row(int).get(column_name)</font></strong>:第一種讀取表格方式請參考圖1的88~90行,env.row(0)拿到表格中第一個row,也就是 <font color="#0000ff"><strong>| Jill | 100,000 | 800 |</strong></font> 這一組資料。接著可以用get(column_name)拿到這個row中不同column的資料,例如 env.row(0).get("owner")拿到Jill。</li> <li><strong><font color="#000000">row(first_column_data_in_each_row).get(column_name)</font></strong>:圖1的93~95行展示第二種讀取表格方式,用每一個row第一個column的資料當作key,使用env.row(first_column_data_in_each_row)的方式定位到所要讀取的該筆row,然後再用get(column_name)拿到這個row中不同column的資料。例如env.row("Jow").get("points")拿到50,000。</li> </ul> <p>使用表格可以一次指定多筆測試資料,提高Scenario的可讀性。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">讀取多個表格</font></strong></p> <p>你也可以在一個Scenario中使用多個表格,例如把一個表格當作Given裡面的輸入資料,另一個表格放在Then裡面當作expected result,請參考圖2。</p> <p>讀取多個表格資料的方式與讀取單一表格相同,但有一點需要注意:<strong><font color="#000000">「在ezSpec中,一個Scenario同一時間最多只會有一個Active Table。」</font></strong>在圖1中由於該Scenario只定義了一個表格,因此一但表格定義之後,在接下來的Step Definition(Given-When-Then-And)中都可以讀到該表格的資料。在圖2中,第一個表格定義在Given,第二個表格定義在Then。因此Given以及When會讀到第一個表格的資料,而Then則是讀到第二個表格的資料,請參考圖2第171~172行(後面定義的表格會蓋掉上一個定義的表格)。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwvHw2p630LaTfdglxOPHzKTDxLH6gdCwvggT7c_0khSb9Cudk2fRpC9ivRuJvhxHGMJDCkm9vu6ebdZs1kdsLtcQP-ef2K5Uas7T0SZ7GrCsBxkTOcpRey3yUNt5VwgP_2lI-f_gy8YziZaarxVyXcBTMeSPLZQX5bXiNSw3ju9NmyzBlsPXoj3UyFVll/s1912/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%886.15.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1106" data-original-width="1912" height="370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwvHw2p630LaTfdglxOPHzKTDxLH6gdCwvggT7c_0khSb9Cudk2fRpC9ivRuJvhxHGMJDCkm9vu6ebdZs1kdsLtcQP-ef2K5Uas7T0SZ7GrCsBxkTOcpRey3yUNt5VwgP_2lI-f_gy8YziZaarxVyXcBTMeSPLZQX5bXiNSw3ju9NmyzBlsPXoj3UyFVll/w640-h370/%E6%88%AA%E5%9C%96%202023-03-21%20%E4%B8%8A%E5%8D%886.15.54.png" width="640" /></a></div><p>▲圖2:使用ezSpec在Scenario中讀取多個表格資料範例,表格內容取自《BDD in Action》一書</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">結語</font></strong></p> <p>這兩集介紹在ezSpec中撰寫Scenario的方式,有使用過Cucumber、SpecFlow或JBehave的鄉民,應該可以很明顯發現兩者的不同之處。因為ezSpec是一種Internal DSL,使用「類Gherkin語法」以Java程式語言採用Specification by Example的方式,用例子(Scenario)來表達規格。</p> <p>ezSpec的Step Definition以Lambda實作,和其相對應的Step直接綁定,不需要因為Step的文字內容改變而重新產生一個新的Step Definition,可以讓開發人員專心在撰寫Scenario上面,少掉很多工具所帶來的干擾。</p> <p>採用BDD或Specification by Example的開發方式,經常會產生「執行步驟相同但驗證資料不同的Scenario」,此時就可以用下一集介紹Scenario Outline來簡化這些相似的Scenario。</p> <p align="center">***</p> <p>友藏內心獨白:一表值千言。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-68361549141799857292023-03-20T00:31:00.002+08:002024-03-11T20:26:41.543+08:00使用ezSpec落實行為驅動開發與實例化需求(3):撰寫Scenario與傳遞簡單參數<p>March 18 20:27~22:12;March 20 00:00~00:30</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCVKPXH1ubHnr7qayGV-3lYb8TBgO19EUCTyV2Ids_gE6gjUWkkfsbbQ6Vl9xXIKFx20uQAxcBPVPgXy4Id5eWn3ZGyjypc0nconpmTa33Dvdx-5THXDM9PcMtz_UkuETRzwL8zLW65t5RppP29tMiqwVnE9IsTbdyq1UTXmay_fTWVrhCjptk2p5w-9ow/s3190/%E6%88%AA%E5%9C%96%202023-03-20%20%E4%B8%8A%E5%8D%8812.20.34.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2130" data-original-width="3190" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCVKPXH1ubHnr7qayGV-3lYb8TBgO19EUCTyV2Ids_gE6gjUWkkfsbbQ6Vl9xXIKFx20uQAxcBPVPgXy4Id5eWn3ZGyjypc0nconpmTa33Dvdx-5THXDM9PcMtz_UkuETRzwL8zLW65t5RppP29tMiqwVnE9IsTbdyq1UTXmay_fTWVrhCjptk2p5w-9ow/w640-h428/%E6%88%AA%E5%9C%96%202023-03-20%20%E4%B8%8A%E5%8D%8812.20.34.png" width="640" /></a></div><br /><p><br /></p> <p> </p><p><strong></strong></p> <strong><font color="#008000" size="5">前言</font></strong><p></p> <p>介紹完ezSpec的領域模型(請考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec1.html">使用ezSpec落實行為驅動開發與實例化需求(1):領域模型介紹</a>>)與Feature和Story(請考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec2featurestory.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(2):Feature與Story</a>>),終於要進入主題,今天介紹如何用ezSpec撰寫Scenario。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">撰寫Scenario</font></strong></p> <p>假設你要開發一隻開發票的程式,可以從未稅金額計算含稅總價與稅金,你幫這隻程式寫的第一個Scenario如圖1所示:「當一台電腦的未稅金額為20000,營業稅為5%,當你買了這台電腦,你應該支付含稅金額21000的總價,其中1000元為營業稅。」</p> <p>產生Scenario的方式很簡單,透過feature.withStory()找出事先建好的Story(請考<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec2featurestory.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(2):Feature與Story</a>>),然後呼叫Story身上的newScenario就可以產生一個新的Scenario。在產生Scenario的時候你可以指定這個Scenario的名字,如果未指定則ezSpec會自動抓取test method的名字當作Scenario的名字。</p> <p>產生Scenario之後,可以透過它的Given、When、Then、And、But(統稱為Step)等method來撰寫Scenario的實際內容。每一個Step接受兩個參數:</p> <ul> <li><strong><font color="#000000">字串</font></strong>:用來描述Step內容的文字敘述。</li> <li><strong><font color="#000000">Lambda</font></strong>:實際實行Step的程式,ezSpec使用Lambda來取代Cucumber的Step Definition。</li> </ul> <p>由於本系列文章的主要目的是介紹ezSpec的使用方式,並不是要介紹透過TDD/BDD/SBE來開發軟體,所以接下來Teddy會說明撰寫Scenario的時候如何指定參數、讀取參數,以及傳遞參數的方式。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMdK3WOhWTWQFAUMxDluDSRy0jQfwcq0SIOlTBPAhmAYvdNzh-nkZLmSz8ocrjYZGKWwg9Z3NCit0dhkrNieTRmjZKLSyl1tojJ7ZJkhDw-kIGs1p-S4baXPWjbhaKCrOAstC-FlT2S72Rbx2vmepSBWM5UiqbYd8O8RIbCFyMmA-BownXKDj6UlFk21jO/s2138/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.33.45.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1050" data-original-width="2138" height="314" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiMdK3WOhWTWQFAUMxDluDSRy0jQfwcq0SIOlTBPAhmAYvdNzh-nkZLmSz8ocrjYZGKWwg9Z3NCit0dhkrNieTRmjZKLSyl1tojJ7ZJkhDw-kIGs1p-S4baXPWjbhaKCrOAstC-FlT2S72Rbx2vmepSBWM5UiqbYd8O8RIbCFyMmA-BownXKDj6UlFk21jO/w640-h314/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.33.45.png" width="640" /></a></div><p>▲圖1:ezSpec的Scenario範例</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>指定與讀取無名參數</strong></font></p> <p>在圖1的範例中,一共有四個參數:</p> <ul> <li>第61行的未稅金額<strong><font color="#000000">20,000</font></strong></li> <li>第64行的營業稅<strong><font color="#000000">5%</font></strong></li> <li>第69行的含稅總價<strong><font color="#000000">21,000</font></strong></li> <li>第72行的營業稅<strong><font color="#000000">1,000</font></strong></li> </ul> <p>有兩個方法可以在Step的Lambda中讀取這些參數,第一種方法是在這些參數前面加上<strong><font color="#000000">$</font></strong>符號,Step的Lambda可以傳入一個<strong><font color="#000000">ScenarioEnvironment</font></strong>的物件當成參數,可以透過ScenarioEnvironment在Lambda中讀取$開頭的字串,請參考圖2中的env參數。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJFkPFFM_rv3fi3t-yvYIJUoJk3MsiBDsNCpxE4BSTQNQxHwi8aYUPR8QOEdn84Hrfz1_oyyZFFm2SnnPhMJqaU7liQiRl5CkIGoOTwndnuPimYedRmaAUqWB1IMYTIcXVx8aj22DhqIPcya0IoGQiRxjCgQuZewi82xHW7dCFjRfq5wXmdJjDkXtr6E9a/s2144/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.39.50.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1064" data-original-width="2144" height="318" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJFkPFFM_rv3fi3t-yvYIJUoJk3MsiBDsNCpxE4BSTQNQxHwi8aYUPR8QOEdn84Hrfz1_oyyZFFm2SnnPhMJqaU7liQiRl5CkIGoOTwndnuPimYedRmaAUqWB1IMYTIcXVx8aj22DhqIPcya0IoGQiRxjCgQuZewi82xHW7dCFjRfq5wXmdJjDkXtr6E9a/w640-h318/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.39.50.png" width="640" /></a></div><p>▲圖2:ezSpec指定與讀取無名參數的Scenario範例</p> <p> </p> <p>圖2示範三種讀取參數的函數:</p> <ul> <li>第62行evn.<strong><font color="#000000">getArg</font></strong>:回傳<strong><font color="#000000">String</font></strong>型別的參數</li> <li>第65行evn.<strong><font color="#000000">getArgd</font></strong>:回傳<strong><font color="#000000">double</font></strong>型別的參數</li> <li>第70行evn.<strong><font color="#000000">getArgi</font></strong>:回傳<font color="#000000"><strong>int</strong></font>型別的參數</li> </ul> <p><strong><font color="#000000">透過$方式所指定的參數,只有value,沒有key,因此在讀取時需透過index方式來讀取資料。</font></strong>在圖2中每一個Step剛好都只有一個參數,因此透過env.getArg(0)就可以拿到這些參數。</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>指定與讀取有名參數</strong></font></p> <p>既然有無名參數,就有有名參數,請參考圖3:</p> <ul> <li>指定有名參數:參考圖3的61行可用 <strong><font color="#000000">${tax_included=20,000}</font></strong>,或是用第64<strong><font color="#000000">${vat_rate:5%}</font></strong>來指定參數的名稱(可用=或:)。</li> <li>讀取有名參數:參考圖3第62、65、70、73,讀取方式和圖2類似,但此時使用字串的key來讀取參數內容。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYPdGEtD8fsX4DHO7bDnGIjU6YjUBvgofmJxqylAtO7SsVbhCESFnXUd8-A8z_0fVyfrqMGsNyVE5W5AwDC2Laf3KZBGZVK8YJlbKjvOS6rX7XBL6MQBalLkPGv1KiU-jYDrQ8ZjGHGtV_OboG_bXATl8QktdOlcH-DQnz5ecVk5faLlSV9gHhx72Azeu7/s2430/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.51.54.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1048" data-original-width="2430" height="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYPdGEtD8fsX4DHO7bDnGIjU6YjUBvgofmJxqylAtO7SsVbhCESFnXUd8-A8z_0fVyfrqMGsNyVE5W5AwDC2Laf3KZBGZVK8YJlbKjvOS6rX7XBL6MQBalLkPGv1KiU-jYDrQ8ZjGHGtV_OboG_bXATl8QktdOlcH-DQnz5ecVk5faLlSV9gHhx72Azeu7/w640-h276/%E6%88%AA%E5%9C%96%202023-03-18%20%E4%B8%8B%E5%8D%889.51.54.png" width="640" /></a></div><p>▲圖3:ezSpec指定與讀取有名參數的Scenario範例</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>讀取其他Step的參數</strong></font></p> <p>有時候你想要在Lambda中讀取其他Step所定義的有名參數,這時候就要用<strong><font color="#000000">getHistoricalArg</font></strong>函數來取得,請參考圖4第74行:<strong><font color="#000000">env.getHistoricalArg("tax_included")</font></strong> 讀到第61行所定義的tax_included參數。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6gnsYv5r9IkBwU5mLkQiMjKitaanY1dMVaFC4_hooC3wa-ZcaroWEC3j01sGKY7Wyj9DM-K6zmYxNXam96TUBIk0_Ii_EGojsJ3LwhVJrzKWkCjsVcSY8dS7tZxIdsh3gOeORWuo8zVcOSUC6rWsZM257Mmks97xKn4_Ljw80SUpKcEtDzQnSiYCnXO06/s1938/%E6%88%AA%E5%9C%96%202023-03-19%20%E4%B8%8B%E5%8D%8811.59.36.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="766" data-original-width="1938" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6gnsYv5r9IkBwU5mLkQiMjKitaanY1dMVaFC4_hooC3wa-ZcaroWEC3j01sGKY7Wyj9DM-K6zmYxNXam96TUBIk0_Ii_EGojsJ3LwhVJrzKWkCjsVcSY8dS7tZxIdsh3gOeORWuo8zVcOSUC6rWsZM257Mmks97xKn4_Ljw80SUpKcEtDzQnSiYCnXO06/w640-h252/%E6%88%AA%E5%9C%96%202023-03-19%20%E4%B8%8B%E5%8D%8811.59.36.png" width="640" /></a></div><p>▲圖4:ezSpec讀取其他Step的有名參數範例</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>在不同的Step中傳遞資料</strong></font></p> <p>在撰寫Scenario的時候經常需要在不同的Step之間傳遞資料,例如在67行的When當中你用hasBought變數代表成功購買到電腦,你想在Then的Lambda中驗證hasBought的內容。請參考圖5,此時可以使用ScenarioEnvironment的put(key, value)將要傳遞的資料放到ScenarioEnvironment(第69行),然後在另一個Lambda中用get(key, Class<T>)將資料讀出(第73行)。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy9nYkR4lblPW0sisMju1uD0SBAIGjMrd7XEToS0m_CNiYNfjjTDpknysi-i4bldD_k_-zWnIZkLCEBIDkM_9n5RblK1JRd6PcgytmcisgC9C1hYYYp7gdOhBeSsIc8mFqw4dxwINndYzkdtxw-fPs7I-paj04LupCSvFztuWuxMGf29wiu17dyCl0ehfZ/s1922/%E6%88%AA%E5%9C%96%202023-03-20%20%E4%B8%8A%E5%8D%8812.09.10.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="936" data-original-width="1922" height="312" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy9nYkR4lblPW0sisMju1uD0SBAIGjMrd7XEToS0m_CNiYNfjjTDpknysi-i4bldD_k_-zWnIZkLCEBIDkM_9n5RblK1JRd6PcgytmcisgC9C1hYYYp7gdOhBeSsIc8mFqw4dxwINndYzkdtxw-fPs7I-paj04LupCSvFztuWuxMGf29wiu17dyCl0ehfZ/w640-h312/%E6%88%AA%E5%9C%96%202023-03-20%20%E4%B8%8A%E5%8D%8812.09.10.png" width="640" /></a></div><p>▲圖5:ezSpec在不同的Step中傳遞資料</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>結語</strong></font></p> <p>今天介紹ezSpec撰寫Scenario與指定和讀取參數的方式,Scenario也可以接受一個Table的資料當作參數,Teddy將在下一集介紹這個功能。</p> <p align="center">***</p> <p align="left">友藏內心獨白:用Lambda撰寫Step Definition就不用處理煩人的正規表示式了。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-84949023283149063142023-03-15T20:20:00.002+08:002024-03-11T20:29:52.624+08:00使用ezSpec落實行為驅動開發與實例化需求(2):Feature與Story<p>March 15 08:57~11:00;20:02~08:19</p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_SLYfCx1AMUJxm7iJBSqPvrVML4RtYmbsYvEK1BLi17jn0p_upPhu46I56UtrflFKNuFOwei0GMwmAbJF2dHOljnezWKgfKcHhA7akLNrUmP7tH1TQmb9Dm6UGF5wt-AUxRwvPFpJIUaJ3Mmsjl9JHmOFJgxqHofFtJEluaH68hhU_nnk1EccvHKu4E9U/s1442/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.18.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="678" data-original-width="1442" height="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_SLYfCx1AMUJxm7iJBSqPvrVML4RtYmbsYvEK1BLi17jn0p_upPhu46I56UtrflFKNuFOwei0GMwmAbJF2dHOljnezWKgfKcHhA7akLNrUmP7tH1TQmb9Dm6UGF5wt-AUxRwvPFpJIUaJ3Mmsjl9JHmOFJgxqHofFtJEluaH68hhU_nnk1EccvHKu4E9U/w640-h300/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.18.39.png" width="640" /></a></div><p>▲用ezSpec寫ezSpec的使用說明文件</p><p><br /></p> <p><strong></strong></p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>上一集<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec1.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(1):領域模型介紹</a>>提到Feature是Gherkin用來描述需求的最大單位,今天介紹在ezSpec中如何撰寫Feature。</p> <p>在介紹撰寫Feature之前,先說明<strong><font color="#000000">External DSL(外部領域特定語言)</font></strong>與<strong><font color="#000000">Internal DSL(內部領域特定語言)</font></strong>這兩個名詞。使用Cucumber、SpecFlow或Cucumber-JVM這類工具的鄉民,將Feature寫在feature file裡面,再交由工具產生Step Definition,然後撰寫Step Definition以達到驗收測試自動化的目的。Gherkin是一種用來描述需求或規格的<strong><font color="#000000">DSL(Domain-Specific Language,領域特定語言)</font></strong>,Cucumber系列工具的作法Teddy將其稱為<strong><font color="#000000">External DSL</font></strong>,因為用來描述需求的語言(也就是Gherkin),和開發用的程式語言(Ruby、C#或Java)並不相同,因此Gherkin成為「外部領域特定語言」。</p> <p>External DSL的好處與缺點Teddy在<<a href="https://teddy-chen-tw.blogspot.com/2021/12/blog-post.html">無痛將驗收測試文件寫在測試案例中</a>>與<<a href="https://teddy-chen-tw.blogspot.com/2023/03/ezspec1.html" target="_blank">使用ezSpec落實行為驅動開發與實例化需求(1):領域模型介紹</a>>提過,有興趣的鄉民可參考。<font color="#000000"><strong>ezSpec是一種<font color="#000000">Internal DSL</font></strong></font>,使用Java語言模擬Gherkin,換句話說用來描述需求或規格的DSL與開發者使用的程式語言是一致的。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">撰寫第一個Feature</font></strong></p> <p>ezSpec除了採用Internal DSL的方式開發,它也與JUnit 5結合,撰寫Feature檔就和傳統寫一個Test Class是一樣的方式。請參考圖1第8行,FeatureSpec是一個一般的Java Class,名稱可以隨便取,你也可以把它叫做FeatureTest或任何你喜歡的名字。</p> <p>FeatureSpec實作<strong><font color="#000000">ezSpec interface</font></strong>,這麼做可以讓ezSpec自動產生報表。如果你只需要在IDE裡面觀看JUnit所產生的報表,也可以不用實作這個介面。</p> <p>在第9行宣告一個static Feature feature 物件,用來代表一個feature file。請注意,如果你要讓ezSpec自動產生報表,這個static Feature的變數名稱一定要取名為 feature,否則系統無法自動產生報表。接著第11~21行使用JUnit 5標準的@BeforeAll annotation來定義feature的內容。第13行的Feature.New()產生一個新的Feature物件,它接受兩個參數:</p> <ul> <li><strong><font color="#000000">Name</font></strong>:Feature的名稱,用來代表一個交付給使用者的功能或功能組。</li> <li><strong><font color="#000000">Description</font></strong>:Feature的詳細內容描述,除了進一步說明Feature的內容,也可以拿來定義這個Feature會用到的詞彙,這些詞彙可以成為<strong><font color="#000000">通用語言(ubiquitous language)</font></strong>的一部分。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqCnqprPMJ1zRT1WPHEmJ9Gdqjki5p8etph8OVjBFQe5DCXLEz5nG6yG5gzXAj4CQeOrcIpFeul2R5bhBJ4twN813w3cUrywdODvoBVMpaJS_w7vSkX5Gu6bJAAS2HdXQiGstVw-khjfHOBPsBtNS3fZZTJI5YvaSDf4ASKCIPKHGJ9TILtP6FvM-QeQL9/s1814/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8A%E5%8D%889.51.05.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1476" data-original-width="1814" height="520" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqCnqprPMJ1zRT1WPHEmJ9Gdqjki5p8etph8OVjBFQe5DCXLEz5nG6yG5gzXAj4CQeOrcIpFeul2R5bhBJ4twN813w3cUrywdODvoBVMpaJS_w7vSkX5Gu6bJAAS2HdXQiGstVw-khjfHOBPsBtNS3fZZTJI5YvaSDf4ASKCIPKHGJ9TILtP6FvM-QeQL9/w640-h520/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8A%E5%8D%889.51.05.png" width="640" /></a></div><p>▲圖1:ezSpec 的Feature使用範例</p> <p> </p> <p>定義好feature之後,可以直接寫一個test method來執行看看,驗證feature的內容,請參考第24~40行,這個test method單純驗證feature的內容是否正確,其產生的報表如圖2。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFMXlogEjajeyeTWRS14qnxALy7ThyphenhyphencCAazkoiE26fYs5IN9It5P6sa0Q3PUnqwn__THKRfobSLkSxIDRu0W5ozDvqn5YmqE0VNWKtcnyUyDoepmr2v5drZSALZ4y-ZC10PkSfuGH2WOGPykYWKocbvm0e72v0MUKWICKAqK6cc_-YUIhuaSScnlDras53/s1534/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8A%E5%8D%8810.26.41.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="328" data-original-width="1534" height="136" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjFMXlogEjajeyeTWRS14qnxALy7ThyphenhyphencCAazkoiE26fYs5IN9It5P6sa0Q3PUnqwn__THKRfobSLkSxIDRu0W5ozDvqn5YmqE0VNWKtcnyUyDoepmr2v5drZSALZ4y-ZC10PkSfuGH2WOGPykYWKocbvm0e72v0MUKWICKAqK6cc_-YUIhuaSScnlDras53/w640-h136/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8A%E5%8D%8810.26.41.png" width="640" /></a></div><p>▲圖2:ezSpec產生的Feature報表範例</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">Story</font></strong></p> <p>ezSpec 的Feature物件本身只是一個「容器」,實際上的需求內容是寫在Scenario裡面。在Feature與Scenario之間,ezSpec還支援Story。一個Feature需要包含至少一個Story,Scenario則是附屬於某一個Story。</p> <p>請參考圖3,第16行呼叫feature.newStory新增一個Story,它和Feature一樣有Name和Description欄位,其用途也類似。此外,Story還有一個Index欄位,可以指定一個編號給它,之後可以使用這個編號來讀取Story。</p> <p>基本上Story就是一個小Feature,如果你覺得不需要Story來管理更小的功能,在撰寫Feature的時候,只需要幫每一個Feature寫一個Story,然後由該Story來產生所有的Scenario。</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiKfsxYeM2Y62jnH0Q65BfdNkE4qwruBrq1FLk5HyaHkI_nWXip0luhgCseQazFqFDzZ_0LIEwFidnD4Y3ERjgpDKs_FM6CdwEHMvoc2AKFyDQpWPIWCsvyEQ3fR4KGthWnlcYe-UYIW6manFbJpUqvckmvjxi4q3ek9sVdRA_jWXKK-MD8PBaT8VHILtf/s1866/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.11.42.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1866" data-original-width="1766" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiKfsxYeM2Y62jnH0Q65BfdNkE4qwruBrq1FLk5HyaHkI_nWXip0luhgCseQazFqFDzZ_0LIEwFidnD4Y3ERjgpDKs_FM6CdwEHMvoc2AKFyDQpWPIWCsvyEQ3fR4KGthWnlcYe-UYIW6manFbJpUqvckmvjxi4q3ek9sVdRA_jWXKK-MD8PBaT8VHILtf/w606-h640/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.11.42.png" width="606" /></a></div><p>▲圖3:ezSpec 的Story使用範例</p> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvoonHyQAk6_2X0zxZAUOzeteU2p6wrGltR6O0wjk-wX5WRaLGV3awzCNYqA9FL2I2_jGDEB1S9jVoGEZr2Wg8Ov6ifMRwmKPpbbPM3M1_FNNr00jWCj-MYKNTk4hU7wU3oQlLq0jBARCD9g0krrkqaXZwQLDw8QhU-NDFnAaVZTxh4EWtCD8BYK1FU0z9/s976/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.05.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="324" data-original-width="976" height="212" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvoonHyQAk6_2X0zxZAUOzeteU2p6wrGltR6O0wjk-wX5WRaLGV3awzCNYqA9FL2I2_jGDEB1S9jVoGEZr2Wg8Ov6ifMRwmKPpbbPM3M1_FNNr00jWCj-MYKNTk4hU7wU3oQlLq0jBARCD9g0krrkqaXZwQLDw8QhU-NDFnAaVZTxh4EWtCD8BYK1FU0z9/w640-h212/%E6%88%AA%E5%9C%96%202023-03-15%20%E4%B8%8B%E5%8D%888.05.46.png" width="640" /></a></div><p>▲圖4:ezSpec 產生的Story報表 (未包含內部的Scenario、Scenario Outline與Background)</p> <p> </p> <p>新增Story之後,可以呼叫feature.withStory拿到屬於該feature的story,然後再透過它的newScenario產生Scenario。下一集將介紹如何使用Scenario與Given-Then-Then撰寫需求範例。</p> <p align="center">***</p> <p>友藏內心獨白:運動前先暖身。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-7018546231842080332023-03-14T23:24:00.002+08:002024-03-11T20:33:14.011+08:00使用ezSpec落實行為驅動開發與實例化需求(1):領域模型介紹<p>March 14 18:10~19:02;20:28~23:23</p> <p><img height="379" src="https://lh3.googleusercontent.com/pw/AMWts8BDnWkPjqwnIEpqH9a6fls4AOmJ9ZTFyH0K3fApA7-yundTUyDG5iA5ANub9BdMIxwzTQDVvlcRHE3eg3xyXr-Ln2yNqlP_PdQ2uDgXKz2mBse0v5VJBaOa125pWAxalpBqBpSWn2v2MTSkOmno3fTtuQ=w640-h379?authuser=0" width="640" /></p> <p>▲圖1:ezSpec領域模型</p> <p> </p> <p><font color="#008000" size="5"><strong>前言</strong></font></p> <p><strong><font color="#000000">行為驅動開發(Behavior-Driven Development;BDD)</font></strong>與<strong><font color="#000000">實例化需求(Specification by Example)</font></strong>是兩種主流的測試驅動開發方法。藉由開發人員與領域專家或利害關係人一起探討需求(協同建模),以及代表需求的<strong><font color="#000000">具體範例</font></strong>,並將其表達在自動化測試案例中,以達到<strong><font color="#000000">活文件(Living Documentation)</font></strong>的效果。</p> <p> </p> <p>Teddy在開發ezKanban的過程中,在2021年順手作了ezSpec—它是用Java撰寫的Internal DSL,可以簡化使用Cucumber或JBehave等工具的麻煩(請參考<<a href="https://teddy-chen-tw.blogspot.com/2021/12/blog-post.html" target="_blank">無痛將驗收測試文件寫在測試案例中</a>>)。Teddy當初開發ezSpec是想要做Living Documentation的研究,後來ezKanban的事情太忙,做好ezSpec之後只有在ezKanban中使用它,一直沒有時間進一步把它做到完整的支援Living Documentation。這陣子因故又把ezSpec拿出來「積極開發」,等開發到一段落準備把它開源。在開源之前先寫幾篇文章來介紹ezSpec,日後也可當作ezSpec的使用文件。</p> <p align="center">***</p> <p><font color="#008000" size="5"><strong>ezSpec的領域模型與使用範例</strong></font></p> <p>圖1是ezSpec的領域模型,基本上ezSpec參考Gherkin語法所設計,差別在於ezSpec可以描述Story以及同步執行步驟。這些細節之後再詳細說明,先介紹領域模型各個物件的意義:</p> <ul> <li><strong><font color="#000000">Feature</font></strong>:功能,代表可以提供使用者價值的單位。關於Feature的「粒度大小」有不同的用法,有人將「整個功能模組」當成一個Feature,然後再透過Story或Scenario切成比較小的功能。例如,把「結帳」當成一個Feature,裡面包含現金結帳、信用卡結帳、xxx Pay等。也有人把Feature當成一個單獨功能使用,粒度大小相當於傳統的使用案例(Use Case)。</li> <li><strong><font color="#000000">Story</font></strong>:故事,就是敏捷社群經常使用的User Story。Gherkin/Cucumber本身並沒有支援Story,但JBehave有。有些人主張Story只是開發過程中用來「切割需求」的一種任務分派的單位,最後交付給使用者的是Feature。既然Gherkin是用來描述需求的一種語法,不需要使用Story去紀錄開發過程的「臨時性產品」。但許多傳統敏捷社群的人可能習慣用Story來做為需求溝通的最小單位,所以認為還是有Story比較好。ezSpec支援Story,一個Feature可以有一到多個Story。</li> <li><strong><font color="#000000">Scenario</font></strong>:劇情,基本上一個Scenario可以想像成Specification by Example裡面的Example,透過舉例來溝通需求。一個Feature或Story具體來說到底要做什麼?用<strong><font color="#000000">舉例</font></strong>的方式來表達,比較具體明白,也可以減少誤會。幫一個Feature或Story列舉出幾個具代表性的Scenario之後,團隊如果覺得已經可以瞭解這個功能的內容,這些例子就成為這個功能的驗收條件。如果可以將這些例子(也就是Scenario)變成程式自動執行,它們就成為自動化驗收測試。圖2為ezSpec的Scenario範例,由於ezSpec是一個Internal DSL(Domain Specific Language),使用ezSpec描述規格時就跟寫程式是類似的,直接把Gherkin的Given-Then-Then內容寫在lambda裡面。</li> </ul> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgalHLBlRd-7P4-Ga7wl4dyY8G4KA-RAy4pXW-TY4c7StcNXEs6FjYM-E81x8FhcA2ocEhaUaV70ycipl4u6vlLbWRhbA9zpZ5ir5heyxGLlrXykbMQU_j-uh_RVPl9OYzwAlPqIwKBsibV3UN9CtNNkTI7cBg_3tRQ9BWqct25FWN5bhvLP12gskJGcx6x/s2742/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.44.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1322" data-original-width="2742" height="308" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgalHLBlRd-7P4-Ga7wl4dyY8G4KA-RAy4pXW-TY4c7StcNXEs6FjYM-E81x8FhcA2ocEhaUaV70ycipl4u6vlLbWRhbA9zpZ5ir5heyxGLlrXykbMQU_j-uh_RVPl9OYzwAlPqIwKBsibV3UN9CtNNkTI7cBg_3tRQ9BWqct25FWN5bhvLP12gskJGcx6x/w640-h308/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.44.37.png" width="640" /></a></div><p>▲圖2:ezSpec Scenario範例,pending()表示該Step尚未實作</p> <p> </p> <p>圖3為ezSpec執行圖2的Scenario所產生的報表。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLPBO6N6cx30djl2Oy5SxoRiLHdG-oBjcz8dXOa_pyx7EyUTzX4fka5-Sycp__l2Py1B38GWxzCxoUqUsK7za60jJu8BfwDG1Cd_ezuKO_rxNASESakZGbJyRJsCMckPvFqNBGNGOb46EgBJIPVixXcHy5qxJoap9OWcz4Zy3q0FvwvtuUid6JB7oHYJf3/s2250/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.50.00.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1382" data-original-width="2250" height="394" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLPBO6N6cx30djl2Oy5SxoRiLHdG-oBjcz8dXOa_pyx7EyUTzX4fka5-Sycp__l2Py1B38GWxzCxoUqUsK7za60jJu8BfwDG1Cd_ezuKO_rxNASESakZGbJyRJsCMckPvFqNBGNGOb46EgBJIPVixXcHy5qxJoap9OWcz4Zy3q0FvwvtuUid6JB7oHYJf3/w640-h394/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.50.00.png" width="640" /></a></div><p>▲圖3:ezSpec產生的Scenario執行結果報表</p> <p align="center">***</p> <ul> <li><strong><font color="#000000">Scenario Outline</font></strong>:劇情大綱,幫一個Feature或Story舉了幾個例子之後,如果發現這些例子的執行步驟都相同,只有輸入的資料與執行結果不同,就可以將這些個別的Scenario整理成一個Scenario Outline。Scenario Outline基本上就是傳統軟體測試中的<strong><font color="#000000">「參數化測試」</font></strong>或是<strong><font color="#000000">「資料驅動測試」</font></strong>,在Gherkin中,提供不同資料的方式是指定一組<strong><font color="#000000">Examples</font></strong>。<strong><font color="#000000"></font></strong>請參考圖4的ezSpec ScenarioOutline範例,範例表格資料內容參考自<a href="https://reurl.cc/qkrL9y" title="https://reurl.cc/qkrL9y">https://reurl.cc/qkrL9y</a>。</li> </ul> <p> </p> <div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc2dr5CDjN6L-3UiYiBQPVcoQzTZuUCYGOs1K4k9HWJw0UHExTkW-e9Uj1brwBXREDgr2QaHx1_wBzlSPh3HIRbjxTnU68OqzK6fLExM_YMKpCwMnDSqGqIy_ouFHvZ5zsIuqfyO1RnfyTPGFIOvTkdd4idgzDe57mXNbPR1GB6ybmRa1z4nAYcMkaxUss/s2520/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.20.49.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1680" data-original-width="2520" height="426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc2dr5CDjN6L-3UiYiBQPVcoQzTZuUCYGOs1K4k9HWJw0UHExTkW-e9Uj1brwBXREDgr2QaHx1_wBzlSPh3HIRbjxTnU68OqzK6fLExM_YMKpCwMnDSqGqIy_ouFHvZ5zsIuqfyO1RnfyTPGFIOvTkdd4idgzDe57mXNbPR1GB6ybmRa1z4nAYcMkaxUss/w640-h426/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.20.49.png" width="640" /></a></div><p>▲圖4:ezSpec ScenarioOutline範例</p> <p> </p> <p>圖4第160~177行是指定Examples的地方,兩組Examples一共以五筆測試資料。第179~187行新增一個Scenario Outline,整個Scenario Outline寫在JUnit 5 的test method內,執行結果與一個一般的測試案例相同,可以在JUnit報表中看到,如圖5。</p> <p><img height="199" src="https://lh3.googleusercontent.com/pw/AMWts8BOMZ0AkoYnb4codypnJsBmWoFmH8nrHLu_wyLfG5burTtEL8J0tRp72hNE5hDlJrpnRBeri-xHpEWqGC749JqXYVMCBDPtw0GwRi9kOZnYbXtr2icprI7PfguTYhR50k41MmmlX_cwmv3gTYlXS8ZPiw=w640-h199?authuser=0" width="640" /></p> <p>▲圖5:Scenario Outline執行結果</p> <p> </p> <p>ezSpec可以將整個Feature的執行結果產生文字報表,圖6為上述Scenario Outline執行結果所產生的報表,可以看到每一輪執行的輸入資料,以及每一個步驟(Step,請參考後面說明)的執行結果。</p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdtbqM_s-ojAJrrxDEeyJoPWlCKI0pT2XV3dIWNwZUcwYgY8qPuQSFJqNlrU5eNovhyWBqz8krHUgRMqPW3mzKEcRDL40rUJXLx2r5-u_v6NhoZSnsVJBiVOVhvcWTK14gA221TieMJ-1xRF3Xkl7zz2-qYf5gVCgsSIa7egRVx88L1HasxsUiXVFctS-D/s1836/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.24.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1836" data-original-width="1372" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdtbqM_s-ojAJrrxDEeyJoPWlCKI0pT2XV3dIWNwZUcwYgY8qPuQSFJqNlrU5eNovhyWBqz8krHUgRMqPW3mzKEcRDL40rUJXLx2r5-u_v6NhoZSnsVJBiVOVhvcWTK14gA221TieMJ-1xRF3Xkl7zz2-qYf5gVCgsSIa7egRVx88L1HasxsUiXVFctS-D/w478-h640/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.24.48.png" width="478" /></a></div><p>▲圖6:ezSpec產生的Scenario Outline執行結果文字報表</p> <p> </p> <p align="center">***</p> <p> </p> <ul> <li><strong><font color="#000000">Runtime Scenario</font></strong>:Scenario在ezSpec是一個抽象類別,在執行期間,每一個Scenario,以及Scenario Outline展開之後每次執行都會產生一個Runtime Scenario物件,用來記錄輸入資料與執行結果。</li> <li><font color="#000000"><strong>Background<font color="#000000"></font>:</strong></font><font color="#666666">背景,請參考圖7,類似JUnit的@BeforeEach,可以將多個Scenario的共同步驟定義在Background中,然後再重複使用。Background雖然達到減少重複描述步驟的好處,但是會讓Scenario變得比較不容易閱讀,使用時要稍作取捨。</font></li> </ul> <p><font color="#666666"></font></p> <p><br /></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbE5i3cXkY2Xdi4AvY0bGwCHkWhUG7sz-BLtJOz2jGoQtqXB9BHO3Uwph8R4-qQnDpxDqxJytS6sFB8zr8xkRZJvTKfwBmmvmUa3qtI_LOlYuQICfQLeLBOl3Kul-9f27FuEN7Rz-7oFwrFrTfNPhIRKyEiM8a1E-sfcWMrT9M7S5cU3JYHqy0_mmDeerK/s2656/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.39.37.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="666" data-original-width="2656" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgbE5i3cXkY2Xdi4AvY0bGwCHkWhUG7sz-BLtJOz2jGoQtqXB9BHO3Uwph8R4-qQnDpxDqxJytS6sFB8zr8xkRZJvTKfwBmmvmUa3qtI_LOlYuQICfQLeLBOl3Kul-9f27FuEN7Rz-7oFwrFrTfNPhIRKyEiM8a1E-sfcWMrT9M7S5cU3JYHqy0_mmDeerK/w640-h160/%E6%88%AA%E5%9C%96%202023-03-14%20%E4%B8%8B%E5%8D%8810.39.37.png" width="640" /></a></div><p>▲圖7:ezSpec Background範例</p><p><br /></p> <p><font color="#666666"></font></p> <ul> <li><strong><font color="#000000">Step</font></strong>:步驟,一個Scenario可以包含多個Step。Given、When、Then、And、But這些都屬於Step的一種類型。</li> <li><strong><font color="#000000">Given</font></strong>:用來描述執行功能的前置條件。</li> <li><strong><font color="#000000">When</font></strong>:用來描述執行功能,也就是傳統測試所說的「執行待測程式」。</li> <li><strong><font color="#000000">Then</font></strong>:用來驗證結果功能執行後的結果。</li> <li><strong><font color="#000000">And, But</font></strong>:在Given、When、Then之後可以加上And與But作為接續步驟。</li> <li><strong><font color="#000000">Concurrent Group</font></strong>:Gherkin的Step只支援循序執行,也就是你無法用Gherkin描述同步行為的規格。Teddy的指導教授鄭老師指導一組研究IoT的學生,用Python開發了一個稱為concurrentSpec的工具,可以使用原本Gherkin的語法來描述IoT系統地同步行為規格。原本ezSpec也只支援循序的Step,最近一個月才參考concurrentSpec,加上描述同步行為的能力。</li> </ul> <p align="center">***</p> <p align="left"> </p> <p><font color="#008000" size="5"><strong>結語</strong></font></p> <p>傳統使用Cucumber、SpecFlow或是JBehave,都是先用Gherkin撰寫純文字的feature file(或是 story file),再透過工具產生Step Definition Method/Function,然後開發人員負責實作這些Step Definition Method,把規格變成可執行文件。</p> <p>這種方式,原本是希望領域專家或利害關係人可以直接用Gherkin採用舉例的方式描述需求,再交由開發人員將其轉成自動化驗收測試。概念很好,但這種方式有兩個很大的問題,首先領域專家或利害關係人大該都不會用Gherkin直接幫你寫規格,他們願意口頭跟你溝通、討論,而且是<strong><font color="#000000">持續地</font></strong>溝通、討論,就已經很了不起了。其次,<strong><font color="#000000">Step Definition Method很難寫,也不好閱讀與維護。</font></strong>因為要用到很多正規表示式將feature file所描述的內容傳給程式,要先學會不同工具對於Step Definition的繁瑣撰寫規範,實際使用時很容易出錯,讓開發人員無法專心在描述規格與範例上面。既然到頭來feature file與Step Definition都要開發人員自己寫,為什麼不用「開發人員友善」的方法與工具,來做BDD/SBE呢?</p> <p>改用Internal DSL的方式,直接拿掉煩人的Step Definition與正規表示式,整個BDD/SBE/TDD流程變得很順暢。用Internal DSL的方式並不是Teddy發明的,有人用Ruby做過,只是Teddy一直到2021年在《<a href="https://www.amazon.com/-/zh_TW/Cyrille-Martraire/dp/0134689321/ref=sr_1_1?keywords=Living+Documentation&s=books&sr=1-1" target="_blank">Living Documentation: Continuous Knowledge Sharing by Design</a>》書中看到一個類似的例子之後,才引發自己也用Java開發類似工具的念頭。</p> <p>Teddy自己使用的結果表明,真的是方便很多。這一集先到這邊,之後再詳細逐一介紹ezSpec的每一項功能。</p> <p align="center">***</p> <p>友藏內心獨白:軟體還沒Open,文件先Open。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com0tag:blogger.com,1999:blog-1298974142445162186.post-36987382766374528922023-02-07T22:14:00.011+08:002023-02-07T22:33:03.823+08:00與ChatGPT聊天(1)<p>February 07 20:57~22:15</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8BhAZQ9EgGYI2ToW8UudIMVdyMJUtRdCu0Ll5V6TS2HLwjY-WiZyf2HrGs8rAuFIT8e6WnFOwo_M_XI32et5KK78FIaGvMke85fKeKkCIK8_xtXpjYoXuBYzDumBHS7dvAyM1Ccpf_toeHUU3eBtfUCVw=w1502-h882-no?authuser=0" width="538" height="316" /></p> <p>▲ChatGPT官網首頁畫面</p> <p> </p> <p><strong><font color="#008000" size="5">前言</font></strong></p> <p>ChatGPT自從去年11月推出以來引起很大的關注,原本Teddy對於這方面的議題是沒有涉略,但上周五在會議中聽鄭老師(Teddy的指導教授)分享他使用ChatGPT的經驗,引起Teddy對ChatGPT的興趣。</p> <p>Teddy完全不懂AI,對於ChatGPT背後的運作原理也不了解。單純以軟體開發人員的身分,從軟體設計與學習的角度,分享Teddy使用ChatGPT的經驗。</p> <p align="center">***</p> <p align="justify"><font color="#008000" size="5"><strong>幫我寫合約</strong></font></p> <p>最近這陣子Teddy與ezKanban團隊在研究以<strong><font color="#000000">驗收測試</font></strong>加上<strong><font color="#000000">Design By Contract(DBC)</font></strong>取代傳統<strong><font color="#000000">驗收測試</font></strong>加上<strong><font color="#000000">單元測試</font></strong>來確保軟體品質的方式,於是Teddy就想考考ChatGPT對於寫合約的能力。</p> <p> </p> <p>Teddy請ChatGPT針對Stack class產生push method的合約,結果如下。如果不懂DBC的人,也無法判斷產出結果是否正確(廢話XD)。如果略懂DBC的人,可能會覺得ChatGPT產生的合約,已經與一般教科書中的參考答案相去不遠,頂多少一個stack not full的precondition。</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8DTNxfIydKs5vd363Wogd5CjusJZGHH8uFhzdOEeZvbGLEMujmeAAoDzNsEJfq1hDiHk186D6aGTI3ot9s5Dh587iOYnwa_I4-t3p7n_C53Cjx5bd-9-itP6Am-Fh2iOT4-RcT1j_RHkx6-J8SUGp0dbQ=w965-h2003-no?authuser=0" width="771" height="1603" /></p> <p> </p> <p align="center">***</p> <p> </p> <p>但Teddy一眼就看出來,上面的程式少了一個很重要的postcondition,於是Teddy繼續追問:<strong><font color="#000000">「postconditions好像不完整」</font></strong>。沒想到ChatGPT自己就補上了一條新的postcondition: </p> <p><strong><font color="#0000ff">@post For all i in [0, list.size() - 2], list.get(i).equals(old(list.get(i)))</font></strong></p> <p>這一條postcondition想表達「除了stack top元素以外,stack內容在push之前與之後的其它元素要相等」,換句話說push只可以將新元素放到stack頂端,不可動到原本stack內部的其它元素。。</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8BUpPWabf7zKfyDTzspwntVVmF6uBmwmxn9eRkg3fdCdStf9aEqG7CPtWEBvQsD7JgQ0GWPzbKFHb44wyKb-lq9b5LqPPhiDUTcdmnbb57RhTDOcN0cubaFN3kubFvbwo6mh3iuD5aT6jjslTIZnEy3WA=w897-h2003-no?authuser=0" width="776" height="1733" /></p> <p align="center"> </p> <p align="center">***</p> <p> </p> <p>看到這裡,鄉民們覺得ChatGPT是不是很厲害?但請仔細看它產生的程式碼,其實有bug。什麼bug,Teddy再給它一個提示:<strong><font color="#000000">「list.get(i).equals(list.get(i)) 不能確定現有元素沒有被改變,你必須儲存 old list」</font></strong>。</p> <p>這一次,ChatGPT產生的程式「看起來」就算完整了。</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8AlSv5NHNb8yQRdyeeTgEYDWQH1m5QLev2GOPcSe0-OVDoOjIQdYygBWA-hnp4tM5wax4j8XzJZNRG7xIAnUW26NdXprfH7qztg_kSBcx_4qBR4Atch5hjSK45ZY_NpnC3IAkSuASUhOUovCTjOk6u_AA=w881-h2003-no?authuser=0" width="787" height="1791" /></p> <p> </p> <p>試到這裡Teddy覺得很奇怪:<strong><font color="#000000">很明顯地ChatGPT是知道push contract的「完整答案」,但為什麼第一次只給了一個常見但不完整的答案?</font></strong>是為了節省運算資源嗎?還是有什麼其他原因,就不得而知</p> <p> </p> <p align="center">***</p> <p><strong><font color="#008000" size="5">老師在講你有沒有在聽?</font></strong></p> <p>正當Teddy以為ChatGPT已經「學會」如何回答這個問題,於是Teddy重新再問一次,而且強調「The generated contracts should be as accurate and correct as possible. 」,提醒它不要忘了第三條postcondition。</p> <p>但是,ChatGPT依然故我,還是回答只有兩條合約的答案。</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8D1f046Y3BLoNGmYrKhsdkh-UnIaw4OivCwg-yEs97t5mRngJh655xaDaU4DTATK1wa9TSMkz7PsdIBWyFH0WdhxLCd1XTxqgR0K0Fz74j0L7mLF6hSyOAM2oZYQl149sWtM-ZTZBvX1NfrgDQdXB3jVw=w851-h2003-no?authuser=0" width="771" height="1819" /></p> <p> </p> <p align="center">***</p> <p> </p> <p>Teddy還是好心提問:「Is the postcondition correct?」但這次ChatGPT居然回答Yes。之前Teddy問:「postconditions 好像不完整」,ChatGPT就補上第三條合約,但問它「postcondition是否正確?」,可能它覺得這兩條postcondition是正確的,所以回就答Yes。</p> <p><img src="https://lh3.googleusercontent.com/pw/AMWts8BccMGTisxTbUAjg0Na6cjJn09nSyMaV10RmGUJzgvJUfE26iu6xOFzF86HKx_mOXsepIchLMuWFCF7pzMqiOjbG7xte7_sUdYwWPPCw0N8p4BXvb-slKJ8YkhKYlNRgfo08QiaPO6LiOsrbkxl2BoQvA=w1250-h628-no?authuser=0" width="788" height="396" /></p> <p> </p> <p>對話至此,這個問題Teddy的懶得繼續問下去了。</p> <p align="center">***</p> <p><strong><font color="#008000" size="5">感想</font></strong></p> <p>如果你對於詢問ChatGPT的問題心中已有答案,可以分辨ChatGPT產生結果的正確性,那麼它不失為一種快速代替人類產出文字的好工具。但是如果你對於問題的答案沒個準,那麼盲目相信ChatGPT的產出結果是很危險的。因為它才剛剛問世,對於它產生的結果的「信心指數」還是個謎。如果你在教科書上學到一個知識,基本上信心指數很高,你可以基於這樣的知識繼續累積其他更大的知識體系,但現階段ChatGPT應該還是達不到這種等級的信心要求。</p> <p>從Teddy的角度來看,經過與ChatGPT對話之後獲得正確答案,自然期望相同問題再問一次,就可以得到上次對話的結果,但ChatGPT卻沒有滿足這個期待。這也是另一個令人失望的地方,因為原本想把它「訓練」成自己的小幫手,但是訓練過後它又忘了,那就無法將使用者的個人知識累積在ChatGPT上面。</p> <p>Teddy覺得平均而言,ChatGPT對於問題的理解能力與專業知識量應該已經狂勝個別的人類。只要提升ChatGPT產出結果的正確性,還有就是請它做到「知之為知之,不知為不知」,不要不懂裝懂,這樣子可用性就更大,更值得依賴它用來解決特定的小問題。</p> <p align="center">***</p> <p> </p> <p>友藏內心獨白:受過專業的廢話訓練。</p>Teddy Chenhttp://www.blogger.com/profile/02066842119056439711noreply@blogger.com1