l

2023年9月23日 星期六

練好基本功

September 19 21:00~21:35

 

今天跟ezKanban團隊mobbing,Teddy說:「我這兩天跟別人講理,你們有沒有注意到一件事情,就是平常mobbing我會一直問你們定義。我知道定義,所以我不怕跟別人講理。」

定義,很重要,也很無聊。想走捷徑的人,只看到別人光鮮亮麗的表皮,看不到別人蹲馬步時的付出。抄襲了皮,外表漂漂亮亮,骨子裏還是Teddy的指導教授常說的那句話:「只有薄薄的一層,一刮就掉」

這不是,Teddy才說一句話,一個定義,一刮就掉。

Teddy在北科上課會告訴學生:「Teddy和學生之間是平行關係,不是上下關係,Teddy只是在課堂上扮演老師的角色而已。上課Teddy會問你們很多問題,你們對我說的話如果有疑問,也可以直接說。如果我說錯了,最好可以打臉我。」

Teddy帶的學生,應該都覺得Teddy很嚴格,甚至嚴厲。Teddy常跟他們說:「實驗室很單純,外面世界很黑暗,壞人很多。你不把專業能力練好,出去很容易被壞人唬得一愣一愣的。」我好歹也是在業界工作的人,先讓你們體驗一點人性險惡,以後出校門才有抵抗力。

壞人的專業能力不會比你好,但他強在「夠壞」。這一點,我們永遠比不上,也不想比上。

***


Teddy在外面開課,都是先寫部落格文章,然後再整理成上課教材。Teddy可以說,收費課程的教材內容,絕大部分我都放在部落格上。Teddy做事不會留一手,知道什麼寫什麼,並提供參考資料。我巴不得鄉民們追朔源頭,多讀書。

沒錢上課的學員,只要肯學,把「搞笑談軟工」部落格文章讀完、讀懂,Teddy相信在軟工這一塊,絕對可以排進台灣軟體工程師的前10%。付費上課的學員,是Teddy的衣食父母,沒有他們,泰迪軟體就經營不下去。

這就是Teddy照顧小白、普通人、大神、有錢人、沒錢人的方法。

所以,Teddy不主動參加社群活動,把時間留下來專心讀書、寫作、寫程式,還有這幾個月開始新的嘗試:錄製YouTube影片。以前的敏捷高峰會和這幾年的DDD TW年會,都是主辦單位看得起Teddy,主動邀請參加。不邀請了,人家不需要了,也就不參加了。

台灣的社群活動,主辦單位很辛苦,也有很負責的講師。但是也有一群人,Teddy沒有實際測量過,但希望是一小群人,到處刷存在感、打知名度。這種人,空殼子又想出名收割韭菜,先搞小團體,放一些稻草人,再互相取暖。


這也是一種Pattern,叫什麼、什麼神來著?

***

2023年9月22日 星期五

改行寫網路小說算了(25)

September 20 07:39~09:34;11:16~11:48

  

▲畫面來源:電影「九品芝麻官」

 

上課

開場旁白:不知道是不是因為教改的緣故,這些年學生的造句能力越來愈差,今天國文課小明老師來幫同學做個造句練習。

***

小明老師:請同學依據以下句型,在空白處填入你覺得最合適的文字,老師會依據同學的作答內容來評分,選出第一名給予表揚。

我當然也是讀了_______的文章加入_______,也有拜過碼頭,但我沒錢上_______的課,所以得到了屬於我自己的見解(大多數和_______的見解是一樣的,但少數是不同的)。

我跟_______一樣的見解是_______,不同的見解是_______

***

水豚君同學造句練習

小明老師出完題目之後,班上最聰明的班長水豚君同學第一個跳出來作答。

水豚君:老師,我想好了,以下是我的造句。

我當然也是讀了愛因斯坦的文章加入物理系,也有拜過碼頭,但我沒錢上愛因斯坦的課,所以得到了屬於我自己的見解(大多數和愛因斯坦的見解是一樣的,但少數是不同的)。

我跟愛因斯坦一樣的見解是E = mc²,不同的見解是物理的「理」就是品質

***

稻草人同學造句練習

水豚君話音剛落,副班長稻草人同學接著說:

我當然也是讀了Kent Beck的文章加入TDD陣營,也有拜過碼頭,但我沒錢上Kent Back的課,所以得到了屬於我自己的見解(大多數和Kent Beck的見解是一樣的,但少數是不同的)。

我跟Kent Beck一樣的見解是先寫測試再寫程式,不同的見解是TDD的「Development」就是品質

***

蔣孝華同學造句練習

第三位作答的是班上的開心果,蔣孝華同學。

蔣孝華:老師,各位同學,我的造句和前兩位同學不同,比較生活化且呼應時事。

我當然也是讀了唐綺陽的文章加入星座趨勢分析,也有拜過碼頭,但我沒錢上唐綺陽的課,所以得到了屬於我自己的見解(大多數和唐綺陽的見解是一樣的,但少數是不同的)。

我跟唐綺陽一樣的見解是本周天蠍座的朋友水星逆行,須提防小人,不同的見解是星座趨勢分析的「分析」就是品質

***

王同學造句練習

我當然也是讀了_______的文章加入_______,也有拜過碼頭,但我沒錢上_______的課,所以得到了屬於我自己的見解(大多數和_______的見解是一樣的,但少數是不同的)。

我跟_______一樣的見解是_______,不同的見解是_______

***

小明老師:王同學。

小明老師:王同學、王同學,老師在叫你怎麼不回應?

王同學:(抬頭望著老師)

小明老師:這個練習,你為什麼不作答?

王同學:老師…..這個句型…..我……不會寫(啜泣聲)

小明老師:王同學,沒關係。來,老師秀秀,不哭、不哭。

小明老師:這樣吧,老師看水豚君同學造句表現最好,一開頭就搬出舉世聞名的諾貝爾獎得主愛因斯坦大神當例子(豹子頭:先嚇嚇他),整個氣勢、高度與企圖心就是不同。而且水豚君同學居然可以在碼的多重宇宙,只花不到10秒,就同步發現愛因斯坦花了一輩子才想出來的E = mc²公式,堪為全班表率、台灣之光。

小明老師:這樣子,老師請水豚君同學下課後來教你,好不好?

王同學:….好….好……..喲……謝..謝..老.師…..

***

下課

小明老師:好,各位同學,今天課就上到這邊,等一下中午吃營養午餐,今天的主食是韭菜水餃,請值日生稻草人同學跟蔣孝華同學到廚房領取。對了,記得要多拿一點,不要讓集體肚子餓著了。

小明老師:下課。

水豚君同學:起立、立正、敬禮。

全體同學:謝謝老師。

***

友藏內心獨白:找水豚君來教王同學,這個小明老師,是GTO嗎?。

2023年9月20日 星期三

鐵帽子王事件的Teddy觀點

September 20 12:30~14:43 

▲先看個貓照片消消火 XD

 

緣起

Teddy明明是一位被鄉民認證過的文人,這幾天卻搞得好像變成武將一樣。Teddy就再講一個文人與水球潘的故事,扳回點文人的形象。

幾年前,水球潘加Teddy FB好友。認識Teddy的鄉民應該知道,Teddy基本上不加沒在真實世界見過面的人為好友,但Teddy加了水球潘。為什麼?因為看到Teddy的指導教授鄭老師,也是水球潘的好友。

Teddy看了一下水球潘公開在FB的基本資料,事隔多年如果記憶有錯請更正,他是銘傳大學畢業,當時正在唸台大資工碩士

Teddy知道鄭老師認識台大資工系的老師,於是自己腦補:「會不會是教授們彼此有交流合作,因此當時還是學生身份的水球潘,才會認識鄭老師?」

Teddy也不會為了這麼小不拉機的事跑去跟鄭老師求證,於是就加了水球潘好友。

這中間,如果沒記錯,Teddy應該沒有跟他有什麼互動。一直到疫情期間的某一天水球潘分享一個Clean Architecture範例。Teddy也在教Clean Architecture,因此下載來參考看看。這是一個很小的專案,Teddy覺得水球潘的Repository設計有可以改善之處,於是留言告訴他。

當時Teddy對於水球潘的反應就覺得有點奇怪,說不上來,有一點他不太歡迎Teddy給他的建議的感覺。

***

第二次接觸

第二次是水球潘在「搞笑談軟工臉書社團」跟Teddy討論Value Object,Teddy覺得水球潘對於DDD Building Block的一些基本認知,從Teddy的角度來看,是錯的。一開始Teddy很有耐心地跟他討論,但後來對於他那種幾乎可以說是詭辯的態度,真的受不了,放棄。

認識Teddy的人應該看得出來,Teddy對水球潘的耐心,已經超過對一般鄉民的耐心。為何?還不是看在鄭老師的面子上,自己腦補說,總是老師認識的學生,就包容一些。

接下來就是今年8/11~13,Teddy在上「Design Patterns這樣學就會了–入門實作班」,有學員跑來問Teddy是否認識水球潘?他用跟Teddy一樣的講法也在教Design Patterns。

 

 

 

這事件,因為「鐵帽子王」事件(水球潘在搞笑談軟工FB社群送給Teddy的幾頂帽子)發生後,Teddy才在FB公開。如果Teddy在意,在八月上課當下就馬上在FB發文了酸水球潘,不會忍到前幾天。Teddy當時笑笑地告訴學員「我知道這個人,他有加入搞笑談軟工社團,我跟他不熟,印象中是很認真(我知道他有弄社群活動)的人,也很有主見。之前他跟我在FB討論過事情,但雙方看法不同。」

當時問Teddy問題學員,好像有參加水球潘的活動,如果有看到,可以出來佐證。

當時Teddy對於水球潘,一句不好聽的話都沒說,甚至還小小小的誇他。

為什麼?還不是看在鄭老師的面子上。

***

現況

再來就是這幾天發生的事。上禮拜日下午,因為禮拜六剛參加完DDD TW 2023年會,想說在YouTube找看看有沒有更新什麼影片上去,於是下了DDD關鍵字,就看到水球潘的影片。點開一看,一開頭解釋DDD的Design,Teddy覺得基本觀念就錯了。以下是Teddy當時的貼文:

閒來無聊在YouTube找DDD的影片,看到這個影片中說到:DDD的「Design的意思其實就是品質。」
Domain-Driven Design的「Design」不是在講品質,而是「決定邊界」,這是基本中的基本。

我只聽到這裡,後面的內容我就沒聽了。

 

 

水球潘一開始非常強勢,覺得Teddy是「文人相輕」故意找他麻煩。但他卻後來又自己承認,他的說法應該稍加修正。一般人遇到這種情況,就說聲:「噯呀,口誤」,這不就沒事了。

後續水球潘的回應,就是給Teddy扣帽子、潑髒水。更有趣的是,水球潘還自爆內幕:



既然他都自爆他向Teddy「致敬」的內幕,那就不好意思,水球潘已經用完鄭老師面子點數

***

內心話

這中間還穿插一些,從Teddy的角度來看,屬於幼幼班行為的事情,就不提了。

事情演變至此,問題已經不是原本討論關於「Design」定義。Teddy早上去復健,吊脖子的時候在想,以Teddy的身分,在台灣軟工界,不敢說是大佬,好歹也是小佬:

Teddy寫文章、經營泰迪軟體、寫書、辦C. C. Agile社群活動(曾經XD)、經營臉書社群,都已經十年以上,還有最近釋出DDD、DBC與BDD/SBE開源軟體、錄製YouTube頻道等。另外,Teddy的學歷,再怎麼不濟,好歹也是個本土博士(北科大資工)以及北科大兼任助理教授。

Teddy這樣的人,水球潘都敢跑到Teddy經營的「搞笑談軟工社團」來鬧。如果Teddy這次又包容水球潘,日後一般軟體工程師對於水球潘的分享如果有任何疑問,誰還敢發言啊?

看到有些鄉民的思維,有時Teddy都會懷疑:這是在台灣還是在中國?喔,你放在網路上公開廣告的教材內容,不准任何人公開提出疑問?那你不會不要放廣告膩,這樣就沒人會批評了啊!

***

若干年前神收割了一波小白韭菜,很抱歉當時Teddy實力不夠,也不懂神的技術,幫不上忙。但Teddy自己偷偷覺得,應該有小小阻擋了一下神的勢力蔓延到Agile這邊。賣東西卻不允許別人討論產品內容,態度還這麼囂張,這是什麼樣的生意可以這樣?想想這些轉職者與技術小白,真的是挺可憐的,想要努力變好,還要被騙。有種海龜寶寶破殼而出,努力爬向大海的途中,還有一堆海鳥等在旁邊要吃他們的即視感。

***

友藏內心獨白:我不入地獄,誰入地獄。

改行寫網路小說算了(24)

September 19 16:10~18:50

 

炸神誕生

約莫2500年前,羅剎國的土地上出現了一位傳說中的神級人物。其出生地、父母、生日與姓名均不詳。因成年後的職業為農夫,且養馬,江湖人稱馬農

馬農雖為農夫,但並不耕作,專以收割別人土地上長出的韭菜為生。因為騎馬,跑得快,上面坐個老太太 眾多受害農夫想抓馬農久已,可惜連他的馬尾燈都看不到,反倒吃了滿嘴灰。於是農夫們集體討論想出一個辦法,準備很多鞭炮,當馬農出現時,用鞭炮炸他身體,把他轟下馬。

某日,馬農出現在羅剎國的霄霄村,準備再次收割別人農田中的韭菜。就在此時,一位正好在田邊樹陰下大便的農民發現馬農。農夫顧不了WIP,稍微施力剪斷大便,立馬讓WIP歸零。農夫緊接著來到工作間拿出準備以久的鞭炮丟向馬農,並大喊:「炸他身體、炸他身體」。無奈,馬農騎術優良,能打勝仗,一轉眼就消失在樹林中,隱姓埋名,從此江湖中再也無人見過馬農。此後,農夫們改稱馬農為炸身---炸他身體的簡稱。炸身、炸身,農夫們希望喊久之後,下次再見到馬農,可以炸他身體,一炮斃命。

若干年後,炸身的傳奇故事,傳來傳去,不知怎麼地居然被神格化。炸身被尊稱為炸神,持續保有一群忠實信徒,不離不棄。

***

主角小刀

本故事主角,小刀,是村民所認證的三字經知識權威,以三字經教學為生。小刀平日閉門在家讀經,鮮少涉足農田,自然不知曉炸神這號人物。小刀第一次看到神的名字,是在「小刀讀經班」的課堂上。

消失多年的神,再次下凡化身為一位普通書生。神在課堂上的表現,並沒有特別引人注意。上課當時,小刀有眼無珠,還不知道眼前這位平凡書生,就是大名鼎鼎的炸神(奏樂,噹噹)!

在課堂上的經文討論活動中,神總是特立獨行。不是默默不語,自顧自的下著他帶來的一盤大旗(當下流行的一種虛擬挖人礦桌遊),就是大言不慚地發表一些驚世駭俗的言論。難道是神覺得小刀的三字經講解的不到位,所以故意搗蛋引發關注嗎?好像也不是這樣,因為神還報名了一次夜間讀經活動,應該是對小刀的三字經有信心才是。神在課堂上的表現實在是太平凡了,完全沒有神該有的樣子,小刀一直以為神就是個普通書生,還好心告訴神一些關於讀經的建議。

直到多年後,才有高人在夢中告訴小刀,當年那位在課堂上不起眼的書生,其實就是神,大名鼎鼎的炸神!

***

所有的大神都有一個共同特點,就是具備一擊必殺的神邏輯。神用神的視野「發現」平常人老早就已經知道的東西,並將其宣稱為神發明。例如,神再次發現了火,可以煮飯;水,可以喝;屎,可以施……….肥。多麼了不起的的發明啊!一般人類,是無法用人邏輯與神溝通。因為,神,活在神界!

神邏輯還可以不必遵守人間一切物理定律。

神說智慧,就有了智障

 

不可以質疑你的神!

***

有人說,神的口才很好,擁有大批信眾。每逢初一、十五,信徒提供大量韭菜供養神,讓神過著優渥的生活。關於神的好口才這個傳聞,小刀無法證實。因為在短短兩次聆聽神諭的機會中,化身為書生的神,是那麼的低調。涉世未深的小刀,著實體察不出神的嘴砲神功之威力。

有了信徒的供養,神此後不用再騎馬割韭菜。神賣掉了馬,出門改搭屋駁,一種有蓋子的轎子,負責在霄霄村內短期通勤之用。

又過了幾年,神再度轉趨低調,眾多信徒們已有多年沒有神的消息。

***

神蹟再現

此刻,神再次降臨。本番下凡,雖然神的性別不明、長相不同,但能力依舊。

神在下凡時大喊一聲:

萬能的天神,請賜與我葛雷堡神奇的力量!

神說獨創,就有了模仿

可憐,凡間

小白盛開的田裡

韭菜塗炭

***

後人寫詩一首以茲紀念:

智慧曰智障

獨創模仿

施展神邏輯

引燃自爆彈

***

友藏內心獨白:橫批--集體智障。

2023年9月19日 星期二

創業初期的陳年往事

September 19 05:01~06:17

 

▲就好像吃飯睡覺一樣,還不是你做什麼他也做什麼

 

緣起

上周六Teddy參加了DDD TW 2023年會活動,禮拜天下午閒來無事在YouTube上找DDD的影片來看。滑到某個影片,沒想到因此發生了一連串讓人哭笑不得的事,細節Teddy就不重複描述,今天想聊一件陳年往事。

***

永遠的泰迪之友

十年前泰迪軟體剛成立沒多久,有一位YA先生主動找Teddy聊天。YA先生早年也經常出現在「搞笑談軟工部路格」文章,當時他在新創公司上班,遇到一些問題。因為敏捷開發是當時泰迪軟體的主要業務,YA先生想了解導入敏捷開發能不能對他的工作有所幫助。

YA先生是一位非常熱心的人,幾年前他去了德國工作,是Teddy心目中永遠的泰迪之友。因為YA先生是泰迪軟體成立之後第一位找Teddy「諮詢」的客戶,Teddy當時也是剛創業的菜鳥一隻,雙方約在北科大對面的伯朗咖啡聊聊天。Teddy並沒有跟YA先生收費,只是讓他請了一杯飲料。可能是因為這樣,YA先生覺得過意不去,好心找Teddy去跟他們公司的新創顧問談一談,看看對Teddy的創業有沒有什麼幫助。

***

Teddy記得對方是一位長相帥氣的年輕ABC(以下就以ABC稱呼這位先生),對話的時候英文、中文交錯使用,好像在美國某創投公司待過。由於年代久遠,細節Teddy已經記不清了。ABC聊到創投的一些生態,他說以前他們常常看很多新創的案子,因此衍生出一些快速篩選案子的原則。就好比大公司HR每天可能收到幾百份履歷,總不可能每份都逐一閱讀,因此用學歷、畢業學校作與工作年資為條件,快速過濾掉不符標準的求職者。

ABC:有一種case,就算是business model再棒,我們絕對不會投。

Teddy:為什麼?

ABC:因為非法!

ABC:我們看過一個case,公司計畫從YouTube下載各種影片、音樂,然後再提供服務給第三者。

ABC:侵權、非法的case,就算他們現在活耀的使用者數量再多,我們都不考慮,這是底線。

Teddy原本以為新創公司因為新的業務模型,遊走在法律邊緣是很常見的,不然當年YouTube一開始營運上面不是有很多非法的影片。不知道是這位ABC待過的創投比較不一樣,還是這是一個業界標準,當時Teddy也沒多問。

***

結論

老美對於做生意不可剽竊他人智慧財產這件事,相對而言還是比較嚴肅看待。在美國念書,如果作業被抓到抄襲,是一件很嚴重的事情。

抄襲者為什麼要抄襲別人?因為可以彎道超車,自己不用花很多時間,就可以看起來好棒棒。抄襲的過程中,不只被抄襲的人受害,消費者也受害。

先幫「小白」默哀三秒!

***

友藏內心獨白:用新的謊去圓舊的謊,就容易自爆。

2023年7月10日 星期一

搞笑談軟工YouTube頻道開張

July 10 18:24~18:36

 

▲用講的好像比用打字寫部落格文章還要簡單啊

緣起

6月20日Teddy舉辦「你就是寫太多測試才會沒時間: 在領域驅動設計與事件溯源架構中使用合約式設計」線上活動,並錄影。活動結束後Teddy準備把影片放到YouTube上,原本泰迪軟體就有一個YouTube頻道但是都沒有經營,只放了兩個影片。想一想,這幾年Teddy比較少寫部落格文章(常寫文章打太多字、用太多滑鼠,很容易肩頸痠痛),幹脆試著錄影用講的,看看能不能吸引到另外的客群幫泰迪軟體拉點生意。

於是,就開了搞笑談軟工YouTube頻道。目前打算先來個一年的每日更新,Teddy每天放一個影片,分享一些Teddy覺得對軟體開發人員會感到有趣且有用的知識(常識XD)。 

頻道網址在此:https://www.youtube.com/@TeddyChen,歡迎舊雨新知幫Teddy訂閱、按讚、分享、開啟小鈴噹 XD。

***

友藏內心獨白:影片一刀不剪,因為我也不會剪。

2023年7月4日 星期二

買到還不錯用的筆電收納架

July 04 18:48~19:22


  

▲圖1:把兩台筆電立起來收納

前言

Teddy有兩台筆電,第一台是2018年從Amazon買的LG gram 15,這台筆電非常棒,很輕續航力又強,這的是沒什麼好挑的。唯一的問題是年華老去,速度有點跟不上,去年不知哪根神經不對勁,居然買了Asus UX5401Z。Asus 這台筆電,才14吋但體感重量比起LG gram 15,有種沉甸甸的感覺。如果是買水果沉甸甸也就算了,買筆電沉甸甸真的不優。

不過今天不是要談筆電,而是要談筆電收納。Teddy平常在家中工作,使用的是2019 iMac 27”,筆電只有外出上課時才會用到。這兩台筆電,平時就被Teddy丟在不同的位置,平躺著有點占空間。前天晚上在PChome上面隨意選了「YUNMI 重力感應筆電直立式收納支架」,收到之後沒想到還不錯用。今天介紹這個支架,提供有需要的鄉民做為參考。

***

非廣告


 ▲圖2:PChome的產品介紹畫面

 

502元,價錢還可以。Teddy以前曾經想買一個筆電收納架放Apple Air與MacBook Pro,但後來換了iMac之後就作罷。這次是整理書桌,順便把找個架子收納筆電。看到這個產品,可以收納多台筆電,抱著可能採雷的覺悟,就買了。

 

▼開箱之後就一個塑膠做的架子,長成下面這樣。


  

▼準備放下兩台筆電,還沒放到底部,此時筆電架還沒有夾緊


▼放到底部之後,筆電架就將兩台筆電夾緊。

   

▼空拍圖


***

結論

▼收納之後將筆電放在書桌後方,留一條Type C線讓兩台筆電輪流充電,還行。

 

***

友藏內心獨白:可能因為難得沒採雷所以特別高興。

2023年7月3日 星期一

為什麼開發人員會過度設計?

July 03 23:10~23:45


  

▲圖1:軟體開發的三個圈圈


前言

今天學生問Teddy一個問題:為什麼會產生過度設計(Over Design)?開發人員的時間不是都很寶貴嗎,怎麼會做出「超出目前需要」的設計?

***

原因很多

造成過度設計的原因很多,常見的有:

  1. 需求不清:很多開發人員沒有機會或意願接觸業務人員或領域專家去持續釐清需求,因此在開發系統的時候,只能依據文件或是對於需求模糊的認知來做設計。因此,很可能產生存在於設計中,但卻不存在於需求中的軟體,如圖1的區域6。
  2. 超前佈署:不管需求是否明確,開發人員通常會有一種「預留彈性」的傾向。「雖然客戶現在沒有要求,但是這個地方如果可以這樣再那樣設計,套某某設計模式,以後需求改變就輕鬆多了。」
  3. 改動架構成本很高:這一點和超前佈署有關,但發生在軟體架構層面。因為軟體架構的修改成本很高,所以如果可以在架構設計階段就「留有彈性」,那未來的日子就好過多了。很可惜,此時的彈性絕大部分都是開發人員腦補的結果,不但沒有幫未來鋪路,反倒增加人為的複雜度。
  4. 展現技術能力:有時候,過度設計就僅是開發人員展現自我技術能力的結果。「08學得一身功夫,就該好好施展一番。先來個23個設計模式漱漱口。」
  5. 演化的結果:有時候系統初期過度設計並不明顯,但隨著系統不斷開發與重構,演化的結果導致系統存在一些過於彈性的設計。例如,想要code reuse與去除重複程式碼,就很容易產生過於彈性的設計。

***

結論

過度設計的原因很多,Teddy覺得主要還是對於需求的理解是否到位,以及對於改變的反應能力是否足夠,會造成開發人員有沒有信心只要當下做出「將將好的設計」(Just Enough Design)即可。從敏捷開發的角度來看,採用TDD/BDD/SBE的開發方法,可以讓 (Specification = Test) = Program,在理想狀況下,可以大幅減少過度設計。

***

友藏內心獨白:要做到Lean,並不容易。

2023年6月20日 星期二

你就是寫太多測試才會沒時間(2):自動化測試是金字塔嗎?

June 20 09:55~11:46

  


▲圖1:傳統的自動化測試形成金字塔形狀,單元測試占比最大


前言

傳統軟體自動化測試形成如圖1的金字塔形狀,作為驗證個別軟體元件正確性的單元測試數量做多,整合測試次之,透過使用者介面驗證系統功能的使用者驗收測試或稱為End-To-End測試數量最少。

多年來,很多撰寫自動化測試的開發人員心中大致依循著測試金字塔去規劃與撰寫他們的測試案例。但是,雖著單元測試越來越多,系統功能不斷地演化以及持續重構改善設計,開發人員經常會發現:「靠,剛剛的修改造成N個單元測試失敗。更慘的是,我看不懂這些失敗的單元測試為什麼失敗。」

整合測試因為以黑箱的角度測試「系統功能」,因此相對而言對於軟體修改與重構的抵抗力比較高。但光使用整合測試驗證系統品質存在兩個問題:

  1. 發生錯誤時不易除錯(不容易明確看出錯誤發生在哪個地方)
  2. 執行速度比單元測試要慢很多,導致回饋路徑較長,降低開發人員持續測試的意願

如果可以減少單元測試的數量同時維持單元測試的效果(避免上述兩個問題),自動測試有沒有可能從金字塔變成如圖2所示的菱形


▲圖2:自動化測試有沒有可能是菱形?

 

今天這一集先談第一個問題,下一集再談測試執行速度的問題。

***

用合約取代單元測試

測試只是一種驗證系統行為的方法,在軟體工程中除了測試以外還有一種也算是廣為人知但較少人做的方法:合約式設計(Design by Contract;DBC)也可以規範系統行為。DBC的作法很簡單,模仿真實世界人類的合約,幫軟體元件撰寫合約。軟體合約主要包含:pre-conditions(前置條件)post-conditions(後置條件)以及class invariants(類別不變量)。為了簡化起見之後的範例只討論前兩者,先忽略class invariants。

圖3是ezKanban系統中的Workflow aggregate單元測試,這也是傳統用來驗證程式行為的做法。


▲圖3:Workflow單元測試

 

圖4是幫Workflow的建構函數撰寫合約的程式範例,其中第42~44是pre-conditions,第48~61是post-conditions,第46行是method body。當程式執行的時候,只要pre-conditions和post-conditions都通過,那麼不管method body如何實作,它的行為就被視為具備正確性(correctness)


   ▲圖4:幫Workflow寫合約 

***

看到圖4的範例,鄉民們可能會覺得:「類別的合約就是幫method做輸入參數檢查,然後把平時寫在單元測試裡面的assertions移到production code裡面而已啊。」這樣做雖然不用寫單元測試,但是這些合約也是程式碼,也是要花時間撰寫與維護,這樣有省到時間嗎?

這個問題可以從幾個方面來討論:

  • 不用寫arrange和act:撰寫單元測試有三個步驟,arrangeactassert。寫成合約之後,assert部分還是存在,但少了arrange與act。寫過自動化測試的鄉民們應該很有感,很多時候花在arrange的時間甚至比assert還多。減少arrange與act除了少掉撰寫的時間,也避免了之後需求變更或軟體重構導致需要維護單元測試的時間。
  • 和Production Code生活在一起有助於開發與維護:寫在production code裡面的合約(post-conditions),看起來跟寫在測試裡面的assertions很像,但合約並不是把測試寫在production code,而是把規格寫在production code,這兩者有很大的差別。將規格寫在production code,當規格改變之後,可以直接修改production code(反之亦然),減少context switching。
  • Caller和Callee責任清楚:撰寫合約也可以釐清物件之間的責任,Caller需要滿足pre-conditions,Callee需要滿足post-conditions。當合約被違反時,丟出的例外訊息可以協助找出錯誤,這一點可以達到和單元測試一樣,甚至更好的除錯效果。

***

誰來驗證合約?

看到這裡鄉民們應該有一個疑問:「我可以直接執行單元測試,寫在production code裡面的合約要怎麼執行?」另外,「合約也是程式碼,寫錯了怎麼辦?要不要寫測試來驗證合約?」

合約是在系統執行期間(runtime)被執行與驗證,所以還是要有「人」來執行這些合約。這就是圖2中的整合測試所要負擔的責任:透過整合測試來執行合約

有沒有需要另外寫其他類型的測試來驗證合約?基本上不需要,當執行驗收測試時如果合約失敗,和單元測試執行失敗一樣,可能有兩個原因:production code寫錯或測試寫錯(合約寫錯),然後開發人員就必須介入排除錯誤發生的原因。

***

結論

透過驗收測試驅動合約,可以極大幅度減少單元測試的數量,接下來只要可以加速驗收測試執行速度,實務上就有可能落實Teddy所介紹的這套方法。至於如何加速驗收測試執行,這個問題比較複雜,下集再談。

***

友藏內心獨白:「你就是寫太多測試才會沒時間」都是真的 XD。

2023年6月17日 星期六

你就是寫太多測試才會沒時間(1):證明自己的清白

June 17 16:31~18:24

▲圖1:單元測試驗證修改過的email是否正確 

 

前言

Teddy的朋友Kuma幾個月前寫了一本書:《你就是不寫測試才會沒時間:Kuma 的單元測試實戰 -- Java篇》。的確,自從Teddy在N年前開始寫第一個自動化單元測試之後,Teddy一直認為測試是開發不可分割的一部分。好的測試可以協助釐清規格、作為驗收條件、找出回歸錯誤,以及支持重構,讓開發人員走得更穩、更快。

但是,隨著測試案例越來越多,管理與重構這些測試案例就變成另一個頭痛的問題。下周二Teddy舉辦一個網路演講,講題是:「你就是寫太多測試才會沒時間」,就是要討論應對這種現象的方法。這個講題這雖然是一句帶有玩笑性質的話,但也代表對於測試看法的一種演進過程:

不寫測試沒時間 ---> 寫了測試有時間--->  累積太多欠管理的測試又變得沒時間 ---> 下階段是什麼?

針對這個主題,今天談一個單純一點的情況:如何簡單驗證待測程式沒有做它不該做的事情?

***

從範例看問題

軟體測試有一個基本原則:「除了要驗證待測程式做了該做的事情,也要驗證它沒有做不該做的事。」舉個例子,圖1中的User物件,呼叫它的changeEmail方法設定新的email,在第101行中驗證email是否被正確設定。這種測試很常見,但嚴格講起來這個測試並不完整。除了驗證email有被正確修改以外,還需要確保User物件的其他欄位沒有被改變。因為難保changeEmail的實作,除了改變email以外,會不會不小心動到User物件的其他欄位,例如把nickname清空。

但是,如果每一個測試案例都去驗證不應該被改動的欄位真的沒有被異動,將會增加很多測試工作,如圖2所示。


▲圖2:第104~109行驗證User除了email以外的其他欄位維持原狀 

***

這還只是單元測試而已,如果是Use Case層次的測試案例,例如ChangeEmailUseCase,從Repository讀出User之後,理論上相同的assertion還要再寫一次。除了需要花費而外時間撰寫測試,也造成duplication code,增加日後維護測試案例的成本。

***

解決方案

先講結論,Teddy使用AssertJ這個「Fluent assertions for java」的測試工具來解決這個問題。圖3為Teddy使用ezSpec(Teddy自行開發的BDD工具軟體,可以直接用Java寫Given-When-Then,過一陣子會開源)所撰寫的ChangeEmailUseCase測試,第73行透過Repository從資料庫中拿出修改過email的User物件,然後第74行比對email欄位是否被正確修改。

接著在第76~78行使用AspectJ的assertThat做為比對物件的方式,呼叫usingRecursiveComparison,然後透過ignoringFieldsMatchingRegexes指定那些欄位不需要比對,最後再呼叫isEqualTo,就可以排除特定欄位之後,比對兩個物件是否相等。

圖3的程式範例擷取自ezKanban,由於ezKanban支援樂觀鎖,因此在每一個Aggregate物件身上都有一個version欄位用以作為樂觀鎖使用。因為User物件是一個Aggregate,所以User的email改變之後,version數值加1,因此在第77行比對修改前與修改後的兩個User物件實例是否相等的時候,除了排除email,也要排除version。


▲圖3:採用ezSpec撰寫的ChangeEmailUseCase測試 

***

結論

透過工具幫忙,就可以用很簡潔的方式去確保物件的狀態。雖然Teddy在範例中使用AssertJ做為比對的工具,但相信不同的語言應該可以找到類似的工具。如果真的找不到怎麼辦?那就自己寫一個啊。

***

友藏內心獨白:開發人員就是要有Maker精神。

2023年6月14日 星期三

為什麼Teddy沒使用Specification設計模式?

June 14 21:46~22:53


  ▲圖1:定義Specification介面


前言

在6/5~6/7去客戶家上【領域驅動設計與簡潔架構入門實作班】的時候,有一位學員問Teddy:

  1. 為什麼Teddy建議Entities Layer的物件不要直接操作Repository?
  2. 為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern?
  3. 為什麼Teddy沒有用Specification模式?

前兩集談了前兩個問題,今天討論最後一個問題:「為什麼Teddy沒用Specification設計模式」」?

 

***

Specification 設計模式

這一個設計模式是由Eric Evans與Martin Fowler所整理的,可在此下載介紹該模式的pdf檔案

Specification這個名字很容易讓人聯想到規格或是需求,但它的用作其實是Filter(過濾器)Selector(選擇器)。傳統上,開發一般CRUD-Based的系統,開發人員很常直接下SQL操作資料庫去尋找所需的資料。但是在領域驅動設計(DDD)中,強調透過領域模型來表達業務邏輯。「尋找符合條件的領域物件」這件事,本身就是一個業務邏輯,因此在領域模型中應該有相對應的物件來表達這樣的業務邏輯。而Specification設計模式就是為了這樣的應用場景而存在。

實作Specification很簡單,它的介面只有一個方法isSatisfiedBy,請參考圖1。isSatisfiedBy接受一個物件(通常是領域物件,例如Board, Workflow, Card這些物件),如果物件的內容滿足concreate specification所指定的條件,則回傳true,代表這個物件「符合規格(選到這個物件了)」。

***

圖2是以個用來選擇Board是否屬於某個Team的規格,稱為BoardBelongToTeamSpecification。



▲圖2:BoardBelongToTeamSpecification範例程式 

 

圖3為BoardBelongToTeamSpecification的使用方法,首先呼叫getBoardLise()產生四個Board。然後產生BoardBelongToTeamSpecification instance,傳入”Team 1”當作查詢條件。接著用BoardBelongToTeamSpecification 當作過濾條件,從這四個Board裡面選出Kanban Board與Board Game。


▲圖3:BoardBelongToTeamSpecification使用範例 

 

在Eric Evans與Martin Fowler所整理原始的文章中,Specificatoin還可以串接,形成更複雜的選擇規範。

***

為什麼沒用Specification?

由上面例子可以看出來,Specification其實就是一個Predicate。從實作面的角度來看,可以直接用Lambda來完成。Specification之所以存在,還有一個很重要的作用力,就是要重複使用這個業務邏輯。你可以用Specification去資料庫中挑選資料,或是驗證領域模型物件是否符合否項業務規格。在這種情況下,如果使用Lambda來實作,就會產生重複程式碼。

寫到這裡,還是沒講為什麼在ezKanban中並沒有使用Specification。原因如下:

  • ezKanban透過撰寫合約的方式來驗證領域物件的正確性,而非使用Specification。
  • 如果要透過Specification去資料庫挑選資料,那麼資料必須先從資料庫中載入記憶體或是用某種映射的方式,把傳給isSatisfiedBy()方法的物件身上的每一個欄位,去匹配資料庫中的欄位。因為ezKanban支援State Sourcing與Event Sourcing,無法光用傳統State Sourcing的方式如果採用Specification去資料庫比對資料。因此,ezKanban就沒有使用Specification,而是針對不同儲存方式與不同資料庫,用資料庫相依的方法,撰寫特別的查詢物件。
  • ezKanban套用CQRS,而CQRS和Specification是兩種互相衝突的設計模式。詳細原因請參考Vladimir Khorikov的部落格文章 <CQRS vs Specification pattern>。

***

結論

Teddy當年在學DDD的時候,印象中只有在DDD藍皮書中看到Specification的介紹,在比較接近實作的DDD紅皮書與DDD橘皮書中,沒什麼印象提到Specification。可能是Teddy學藝不精,所以開發ezKanban的時候潛意識中就沒有套用Specification。

但Teddy目前沒用也不代表以後不會用,反正這麼多設計模式,多了解一點也沒什麼不好。等到哪天有合適的場合,這些模式會自動跑出來報效國家。

***

友藏內心獨白:用Collection操作資料還是非常方便滴。

2023年6月12日 星期一

該不該使用Unit of Work和Repository?

June 12 15:48~16:20;20:22~22:56


▲圖1:ezKanban的Repository介面 


前言

昨天提到6/5~6/7到客戶家上【領域驅動設計與簡潔架構入門實作班】,有一位學員問Teddy的三個問題:

  1. 為什麼Teddy建議Entities Layer的物件不要直接操作Repository?
  2. 為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern?
  3. 為什麼Teddy沒有用Specification模式?

昨天談了第一個問題,今天聊聊第二個問題:「在DDD中,UoW和Repository要不要使用」?

 

***

Unit of Work(UoW)

Unit of Work和Repository這兩個設計模式都出自於Martin Fowler所寫的《Patterns of Enterprise Application Architecture》。Unit of Work顧名思義就是工作單元什麼叫做工作單元?就是把一連串的工作步驟,視為「一個單元」、「一整包完整的大步驟」。一個工作單元隱含一個交易邊界(transaction boundary)

舉個例子,在ezKanban裡面,有以下幾個使用案例:

  • CreateBoardUseCase
  • CreateWorkflowUseCase
  • CreateStageUseCase
  • CreateCardUseCase

每一個使用案例,都是一個工作單元,形成一個交易邊界。產生一個Board是一個工作單元,要嘛成功,要嘛失敗。同理,產生Workflow、產生Stage、產生Card,都是一個工作單元。很簡單,對不對!

ezKanban團隊使用ezKanban的領域模型開發了看板桌遊,這是另一個Bounded Context。在看板桌遊中,有一個CreateKanbanGameUseCase用來產生新的看板遊戲。這個使用案例的實作,呼叫上述四個使用案例。現在問題來了:「在看板桌遊的Bounded Context中,CreateKanbanGameUseCase是一個工作單元,它的執行要嘛成功要嘛失敗。但是因為CreateKanbanGameUseCase的實作方式是重複使用CreateBoardUseCase、CreateWorkflowUseCase、CreateStageUseCase與CreateCardUseCase,這四個使用案例各自是一個工作單元,要怎麼把它們用另一個更大的工作單元包起來?」

Unit of Work設計模式就是要解決這個問題,簡單講,就是把transaction manager注入給使用案例,而不是讓使用案例自己去控制。如此一來,最外層的使用案例負責控制transaction的開始與結束,內部的使用案例只是接受這個由最外層使用案例所注入的transaction manager。如此一來,便可以因應不同使用情境(Context)的需要,動態決定工作單元的範圍。

***

 

為什麼不要使用Unit of Work?

如果不管DDD,Unit of Work是一個很棒的設計模式。但是,在DDD中,Aggregate已經形成了一個交易邊界。如果在DDD中需要使用Unit of Work,則代表在某個Context底下,需要把好幾個不同的Aggregate放在同一個交易中。這不就和原本在DDD中「Aggregate形成了交易邊界」互相衝突了。

更進一步來看,在DDD中,Aggregate由Repository負責儲存與讀取。而「理論上」一個Repository可以各自採用不同的資料庫來儲存Aggregate。也就是說,如果你願意,可以將Aggregate當成一個微服務來佈署。如果在DDD中使用Unit of Work,則這些被放在同一個Unit of Work的Aggregate,就代表它們要綁在同一個資料庫中(除非使用distributed transaction,但採用這種做法的人很少,因為會造成效能問題),這就造成不同的Aggregate透過資料庫產生耦合。因此,Teddy覺得在DDD中,不應該使用Unit of Work。

***

Repository可以用嗎?

在Martin Fowler的《Patterns of Enterprise Application Architecture》書中,Repository代表Collection-Based的儲存體。也就是說,只要從Repository拿出物件,之後對於該物件的修改,會直接反應回Repository,使用者不需要呼叫save方法來儲存該物件。

在Vaughn Vernon所寫的《Implementing Domain-Driven Design》,進一步將Repository的實作分成Collection-Based Repository與 Curd-Based Repository。圖1為ezKanban所設計的Repository介面,採用Crud-Based Repository。ezKanban的所有Aggregate所對應的Repository都是採用相同的介面,只有findById, save與delete這三個方法。

不管是Collection-Based或是Crud-Based,Teddy主張,只要固定Repository介面,將其限制在單一Aggregate的新增、修改、刪除、查詢,這樣子使用Repository並不會有什麼太大的問題。

但是,實務上經常可以看到,很多開發人員在Repository身上加了很多查詢方法。如此一來,雖著需求演進,Repository的介面越來越肥大。你可以說,這種使用Repository的方式,違反了單一責任原則、開放封閉原則,以及介面隔離原則。

所以,只要固定Repository介面,將其餘查詢方法另外設計(在ezKanban中採用Inquiry設計模式來解決這個問題),在DDD中使用Repository是沒有問題的。

***

結論

以上,是Teddy近幾年開發ezKanban所累積的經驗。Unit of Work比較簡單,ezKanban壓根就沒使用過它。但是,針對Repository的使用方法ezKanban團隊重構了好幾次。一開始Teddy也是在不同的Concreate Repository中直接新增個別Aggregate所需要的查詢介面。但隨著系統越來越複雜,Repository也變得越來越亂,不容易理解其中的邏輯。後來,套用CQRS之後,把查詢、命令分離,保留最簡單的Repository介面。如此一來,使用Repository就沒有問題了。

***

友藏內心獨白:不是不好用,是你不會用 XD。

2023年6月11日 星期日

領域模型不要直接依賴Repository

June 11 19:23~20:38


▲圖1:將Repository放到Entities Layer(錯誤示範XD) 

 


前言

6/5~6/7到客戶家上【領域驅動設計與簡潔架構入門實作班】,有一位學員問了Teddy不少問題,像是:

  1. 為什麼Teddy建議Entities Layer的物件不要直接操作Repository?
  2. 為什麼有人說UoW(Unit of Work)和Repository在DDD裡面算是Anti-Pattern?
  3. 為什麼Teddy沒有用Specification模式?


這些問題,應該是有在下功夫研究DDD的人,才會提出來的問題。針對這三個問題,Teddy分三集來說明,今天先談第一個問題。

***

增加領域模型與測試複雜度

Entities Layer是Clean Architecture存放領域模型(Domain Model)的地方,它應該只表達問題領域的業務邏輯,儘可能與外在世界、框架無關。Repository是領域驅動設計(Domain-Driven Design;DDD)中,用來存取聚合(Aggregate)的設計模式。也就是說,Repository隔離了儲存層,讓它的使用者無需知道儲存聚合的實作細節。

Entities Layer知道Repository又怎樣?首先,這麼作讓Domain Model依賴資料存取介面,雖然這個依賴透過Repository介面做到依賴反轉,但「資料存取」的概念還是洩漏到Domain Model,增加不必要的複雜度。

這種不必要的複雜度增加,可以從測試的角度看出來。針對Entities Layer物件的測試,理想上就是傳統軟體測試所說的單元測試,而且是可以做到「測試隔離」(test in isolation)。這樣子的單元測試,因為與外在世界無關,所以可以跑得很快且可以單獨測試業務邏輯。

看到這裡鄉民們可能會想:「我有學過Test Double(測試替身),我可以在測試案例中注入Test Double,這樣就可以寫出與世隔絕的單元測試。」

使用Test Double雖然可以讓使用Repository的Entities Layer物件做到隔離測試,但是付出的代價就是單元測試變得複雜且可能出現重複程式碼。現在,要測試Entities Layer物件之間,都要在單元測試的Arrange階段先設定Repository替身,這會複雜化且重複Arrange區塊的程式碼。

***

弱化階層式架構

請參考圖1,如果將Repository放在Entities Layer,從Clean Architecture的角度來看,為了滿足相依性原則,BoardRepository必須是一個介面,然後在Interface Adapters Layer實作BoardRepositoryImpl。如此一來,雖然滿足相依性原則,卻造成了BoardRepositoryImpl跨層依賴於BoardRepository。雖然在鬆散式階層架構中允許跨層依賴,但Teddy認為這會弱化了階層式架構的一致性。

***

其他人怎麼說

請參考圖2,在IDDD書中也提到,不要將Repository注入給Aggregate。


▲圖2:Teddy的FB廢文1 

 

如圖3所示,在Unit Testing一書中也提到,在領域模型中直接使用資料庫(相當於Repository)會造成程式碼過度複雜。


▲圖3:Teddy的FB廢文2 

 

***

結論

只要程式可以正確動起來,設計沒有絕對的對、錯,但有合適程度的差別。關於這個問題Vladimir Khorikov有一篇很棒的blog: <Domain model purity vs. domain model completeness (DDD Trilemma)>,鄉民們可以參考。

***

友藏內心獨白:領域模型越乾淨越好。

2023年3月27日 星期一

使用ezSpec落實行為驅動開發與實例化需求(10):撰寫自訂報表

March 25 12:44~14:18

▲圖1:ezSpec實作Visitor設計模式類別圖

 

前言

上一集介紹ezSpec的基本報表功能,這一集介紹如何擴充ezSpec,撰寫使用者自訂報表。

***

Visitor設計模式

為了支援自訂報表,ezSpec讓Gherkin keyword實作Visitor設計模式,請參考圖1。在GoF設計模式中,有兩個角色:

  • Element:圖1中的SpecificationElement介面,要接受「拜訪者」的物件需要實做Element介面。該介面有一個accept方法,接受visitor作為參數。該方法的實作會呼叫(callback)visitor.visit,讓visitor可以讀取element的資料。
  • Visitor:圖1中的SpecificationElementVisitor介面,想要拜訪各個Element的具體拜訪者(concrete visitor)需要實作該介面,將拜訪者的邏輯寫在visit方法裡面。在ezSpec中,客製化報表就是一個具體拜訪者。

 

圖2為Feature實作SpecificationElement介面的程式碼,第14行Feature先將自己傳給visitor,接著再將它身上每一個Story傳給visitor。由於Feature是整個Gherkin keyword中最外層的結構,只要拜訪Feature,就能夠拜訪整個Feature file裡面所有的元素,包含Scenario、Scenario、Background與Step。



▲圖2:ezSpec的Feature實作SpecificationElement的程式碼

 

***

 

實作SpecificationElementVisitor產生報表

ezSpec內建的文字檔報表,就是使用上述介紹的機制所產生,請參考圖3。第20行到60行的visit方法,就是用來「拜訪」Feature內的所有元素,產生報表的邏輯。

  

▲圖3:ezSpec內建產生文字報表的Visitor程式碼

 

***

客製化報表範例

開發好ezSpec之後,Teddy就拿它來描述ezKanban的規格。圖3為ezKanban的MoveLane使用案例的規格,用ezSpec的Scenario Outline撰寫。這個功能是將看板系統中,工作流程內部的某一個Lane複製到另一個地方。在描述規格的時候,直接以表格描述複製之前與複製之後Workflow的內容,其執行結果所產生的文字報表如圖4。


▲圖4:以ezSpec所撰寫的ezKanban系統之MoveLane使用案例規格 

 

圖5的內容不太容易閱讀,因為規格中表格還包含著另一個表格,因此Teddy想:「既然MoveLane使用案例是看板視覺化業務邏輯的其中一個功能,為什麼不用視覺化的方式來表達這個功能?」要怎麼用是視覺化方式產生報表,幫它寫一個Visitor吧,請參考圖5。


▲圖5:執行圖3所產生的ezSpec內建文字報表 

 

Teddy使用Markdown的Mermaid擴充(外掛)來繪製Workflow,圖6的WorkflowMermaidVisitor與ezSpec內建的PlainTextVisitor類似,差別在於前者針對MoveLane規格產生Markdown報表,後者則是標準的文字報表。


▲圖6:撰寫WorkflowMermaidVisitor產生MoveLane使用案例的專屬報表 

 

圖7報表的內容與圖4相同,前者以圖形顯示,後者以文字顯示。在MoveLane使用案例的情境中,以圖形顯示比較容易閱讀。


▲圖7:執行圖3所產生的客製化Markdown報表 

 

只要將寫好的Visitor在驗收測試的afterAll()方法中去拜訪Feature,再將結果寫入檔案中,就可以在每次執行驗收測試之後產生新的客製化報表,請參考圖8。


▲圖8:針對MoveLane使用案例驗收測試產生客製化報表

***

結論

寫到這裡剛好第十集,也把ezSpec目前的功能介紹完畢。Teddy預計在2023年七月之後開兩門新課:

  1. Clean TDD(整潔測試驅動開發)
  2. Living Documentation in Agile Development(敏捷開發中的活文件)

在課程中(特別是第二門課)會以ezSpec為範例,在開課前Teddy會先開源ezSpec讓有興趣的鄉民們使用,敬請期待。

***

友藏內心獨白:寫完文件要回頭寫Code。

2023年3月26日 星期日

使用ezSpec落實行為驅動開發與實例化需求(9):內建報表

March 25 10:43~11:48


▲等待報表功能全部完成,ezSpec就可以開源了

 

前言

前幾集介紹ezSpec的基本功能以及同步執行功能,今天介紹ezSpec最後一個功能:報表。報表功能是做到Living Documentation(活文件)的核心功能之一,先用ezSpec描述系統行為,然後這些可執行規格變成驗收條件,執行結果透過報表整理,產出與系統現狀同步的Living Documentation。

目前ezSpec支援產生個別Feature file執行結果的報表,報表格式為txt檔與json檔,整合性報表還在開發中。ezSpec支援Visitor設計模式,開發人員也可以自行撰寫Visitor,產生使用者自訂報表。本集先介紹如何產生內建報表,下一集介紹如何擴充ezSpec,撰寫使用者自訂的報表。

***

產生報表

要讓ezSpec產生報表,首先測試案例(Test Class)要實作EzSpec介面,請參考圖1。

 

▲圖1:測試案例實作EzSpec介面

 

EzSpec介面程式碼如圖2,它身上有四個Annotation:

  • @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class):這是JUnit 5的內建annotation,在JUnit報表中會將test mehtod的名字的底線取代為空白,以方便閱讀。例如,將scenario_using_EzScenario_annotation顯示為scenario using EzScenario annotation。
  • @ExtendWith(EzSpecReportExtension.class):@ExtendWith是JUnit 5支援使用者自行擴充功能的annotation,ezSpec的EzSpecReportExtension透過實作JUnit 5 的AfterAllCallback介面,讓JUnit執行每一個Test Clase之後呼叫ezSpec,以便產生報表。
  • @EzSpecReportFormat({EzSpecReportFormat.Format.json, EzSpecReportFormat.Format.txt}):這是ezSpec自訂的annotation,用來表示產生那些格式的內建報表。目前ezSpec支援txt與json這兩種報表格式
  • @Tag(EzSpecTag.LivingDoc.EzSpec):這也是JUnit 5內建的annotation,用來分類測試案例。參考圖3,ezSpec內建EzSpecTag介面,裡面包含常用的Tag。將Test Class(在ezSpec的情境中,也就是Feature file)用Tag分類,主要以下兩個目的:
    • 在test suite中作為測案案例的選擇條件,例如只挑選單元測試,或是只挑選整合測試。
    • 作為Living Documentation的分類標籤,例如產生Use Case的報表,或是產生在Staging環境測試執行結果的報表。


▲圖2:EzSpec介面程式碼

 

▲圖3:EzSpecTag介面程式碼片段

 

只要測試案例(Test Class)實作EzSpec介面,並依據前幾集介紹的方式撰寫Feature, Story, Scenario, Scenario Outline,執行測試案例之後,ezSpec就會在預設的目錄 target/ezSpec-report 產生報表,請參考圖4。

 

▲圖4:ezSpec在預設目錄中產生報表

 

圖5與圖6分別展示ezSpec產生的純文字檔報表與json格式報表。


▲圖5:ezSpec純文字檔報表範例

 

▲圖6:ezSpec JSON格式報表範例

 

***

不要產生報表

有時候你可能會臨時不要產生某個測試案例的報表,此時可以直接在測試案例上面加上@DisableEzSpecReport。

 

▲圖7:加上@DisableEzSpecReport就不會產生該測試案例的報表

 

如果想要禁止ezSpec產生所有測試案例的報表,可以設定EZREPORT環境變數,把它設為OFF即可。什麼時候會取消報表產生功能?因為產生報表需要時間,也需要寫入檔案,因此會讓測試案例跑得比較慢一點。原本這也不是什麼大問題,但如果你的專案採用類似PiTest這種Mutation Test工具,那麼同一個測試案例會被執行N次,而且在這種情況下,開發人員只需要觀看PiTest產出的報表,並不需要ezSpec的報表。此時,關閉ezSpec產生報表功能,可以加速PiTest執行。

 

***

結論

報表系統算是ezSpec的最後一哩路,目前ezSpec提供的報表屬於個別驗收測試(Feature file)執行結果的報表,雖然使用者可以透過下一集介紹的方式擴充ezSpec產生使用者自訂報表,但Teddy還是想在ezSpec中提供基本的整合報表功能。等待整合報表功能完成,Teddy就會將ezSpec開源給有興趣的鄉民們使用。

***

友藏內心獨白:人要衣裝,佛要金裝,報表也是很重要的功能。

2023年3月25日 星期六

使用ezSpec落實行為驅動開發與實例化需求(8):規範平行行為

March 24 21:45~23:14

▲圖1:電梯的規格範例

 

前言

ezSpec與Gherkin相關的基本功能已經介紹完畢,今天介紹一個Gherkin沒有的功能:「描述平行行為的規格」。

Gherkin的Given, When, Then, And, But這些Step依據它們出現在Scenario的先後順序依序執行,對於一些天生就具有平行處理能力的系統,例如在IoT(Internet of Things)系統中,多個sensor或device彼此之間都是獨立且平行執行。在這種情況下,用Gherkin就無法表達這些平行執行的行為。

三月初的時候實驗室一組研究IoT的學生跟Teddy介紹他們用python開發的工具—concurrentSpec,它擴充Gherkin語意讓開發人員撰寫並執行同步行為規格。原本Teddy開發ezSpec並沒有計畫要支援描述平行處理行為,聽完學生的介紹,覺得加上這個功能可以讓描述行為的語意變得更完整,因此花了點時間在ezSpec中支援這個功能。

***

平行運作行為的範例

請參考圖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幫忙翻譯。

這個例子如果用標準的Gherkin執行,假設第29行執行失敗(無法打開緊急指示燈),測試案例就會停在第29,後面的assertion就不會被驗證。在電梯的例子中,這種行為很顯然是不正確的。因為就算29行與30行都失敗,只要第28行成功(電梯有停在最近的樓層),第39行就應該被檢查(電梯門要在五秒內打開)。

有些測試框架可以讓使用者設定:「就算某一個assertion失敗,測試案例還是持續執行」。但就算是採用這種方式來執行圖1的例子,最後結果還是可能錯誤。例如第28行失敗但第31行成功,這表示:「電梯沒有成功停在最近的樓層,但是電梯門最後卻打開了。」這顯然不是使用者所期待的行為。

怎麼用Gherkin描述同步行為?concurrentSpec提出一個很簡單的擴充方式:「在不增加Gherkin keyword的前提之下,將Given, When, Then當作同步執行群組的起頭,它們之後的And與But將會與它們同步執行。整個同步群族執行完畢之後,才會開始執行下一個同步群組。」換句話說,Given, When, Then彼此之間是循序執行,但它們之後若接著And或But,這些And/But將與它們平行執行。

此外,還可以指定每一個Step如果執行失敗,之後的Step是否要繼續執行。

***

用ezSpec描述圖1行為的程式如圖2所示,Teddy故意讓「打開緊急指示燈」與「取消該叫車請求」發生錯誤(839行與842行),但是這兩個Step如果發生錯誤,下一個Given/When/Then依然會繼續執行(因為指定ContinuousAfterFailure參數)。請注意,圖2中的Step執行順序如下,Given, When, Then, Then是循序執行,第一個Then與後面兩個And為平行執行。

  • Given
  • When
  • Then, And, And (834、838、841行):三個Step平行執行
  • Then

圖2中有一個小細節要注意,就是要讓Scenario以平行的方式執行,必須呼要ExecuteConcurrently()


▲圖2:用ezSpec描述電梯規格範例

 

圖3顯示圖2執行結果,可以看到兩個And執行失敗並沒有影響後續Then的執行。


▲圖3:圖2執行結果報表

***

電梯門打不開

Teddy修改一下電梯的Scenario,故意讓電梯沒有停在最近樓層,請參考圖4。





▲圖4:模擬電梯沒有停在最近的樓層。

 

修改後的Scenario執行結果如圖5,可以看到因為Then沒有加上ContinuousAfterFailure參數,所以整個同步執行群組執行完畢之後,就不會繼續執行下一個Step。


▲圖5:圖4執行結果報表

***

看到這裡鄉民們可能會想:「不管電梯有沒有停在最近樓層,我就是要執行最後一個步驟的檢查啊!」也可以,修改一下Then,幫它加上ContinuousAfterFailure,請參考圖6。

 

▲圖6:加上ContinuousAfterFailure參數,即使電梯未停妥也繼續執行後續驗證步驟

 

修改後的Scenario執行結果如圖7,如果系統行為真的如此,恭喜你,找到一個bug。什麼bug?電梯未停靠最近樓層,但電梯門卻打開了。


▲圖7:圖6執行結果報表

***

 

結論

如果鄉民們的系統與IoT系統類似,有著很明顯的平行行為,那麼標準的Gherkin語意就無法描述這些行為。借用concurrentSpec所擴充的Gherkin語意,可以在不增加Gherkin keyword的前提之下,使用ezSpec描述平行系統的行為。

ezSpec指定行為的功能差不多介紹完畢,下一集介紹ezSpec產生報表的功能。

***

友藏內心獨白:快到山頂了。