如果你不喜歡視頻,這裡有一個冗長的版本。
軟體是可以改變的。這就是為什麼它被稱為“軟”件,它的可塑性是比硬體強的。優秀的工程師團隊應該是一個公司一筆驚人的財富,他們編寫一個可以隨著業務發展而不斷增值的系統。
那麼,為什麼我們在這方面做的這麼糟糕呢?你聽過多少完全失敗的專案?或成為「遺產」,必須完全重寫 (重寫通常也會失敗! )
軟體系統是如何「失敗」的呢?難道不能在它正確之前進行修改嗎?這就是我們的承諾!
很多人選擇用 Go 來建立系統,因為它已經做出了許多選擇,人們希望這些選擇能讓它更經得起遺產的考驗。 【相關推薦:Go影片教學】
和我之前Scala 生涯相比我形容它簡直會讓你有上吊的衝動 ,Go 只有25個關鍵字和很多 可以從標準函式庫和一些其他小型函式庫中建立的系統。願景是透過 Go 你可以編寫程式碼並在6個月內回顧它,它仍然有意義。
與大多數替代品相比,測試,基準測試,語義解析和裝載方面的工具是一流的。
很棒的標準函式庫。
嚴謹的回饋迴路讓編譯非常迅速
#Go 有向後相容性承諾。看起來 Go 將來會獲得泛型和其他功能,但設計師們已經承諾,即使你 5 年前寫的 Go 程式碼仍然會建立。我花了幾週的時間將專案從Scala 2.8 升級到 2.10。
即使擁有所有這些優秀的屬性,我們仍然可能會製造出糟糕的系統,因此我們應不論你使用的語言優秀與否,你都應該回顧過去的軟體工程並理解其中所獲得的經驗教訓。
1974年,一位名叫[曼尼·雷曼](https://en.wikipedia.org/wiki/Manny_Lehman...) 的聰明的軟體工程師寫下了雷曼軟體進化定律。
這些定律描述了推動新發展的力量和阻礙進步的力量之間的平衡。
如果我們不希望開發的系統變成遺產,被一遍又一遍的重寫,那麼這些力量是我們需要著重理解的。
在實際生活中被使用的軟體系統都必須不斷的改變,不然就會被大環境淘汰
很明顯,一個系統必須 不斷的改變,要不然就會變的越來越沒用,但是這種情況為什麼常被忽略呢?
##因為很多開發團隊在指定的日期交付一個專案會得到獎金,然後他們會繼續開發下一個專案。如果這個軟體是「幸運的」,至少它會以某種形式移交給另一組人來維護它,但是他們一定不會繼續迭代它。 人們通常關心的是選擇一個框架來幫助他們「快速交付」,而不是專注於系統持久性發展。 即使你是個優秀的軟體工程師,你仍然會因為不了解自己系統的未來需求而成為受害者。隨著業務的變化,你寫的出色的程式碼也會變得不再適用。 雷曼在70年代很成功,因為他給了我們另一個值得深思的規律。隨著系統的發展,除非採取措施減少系統複雜性的增加,否則系統的複雜程度會持續增加他現在要所說的就是:我們不能讓軟體團隊成為純粹的功能工廠,只是透過將越來越多的功能集中到軟體上, 來讓系統能夠繼續長期運作。 隨著我們知識領域的變化,我們
必須 持續管理系統的複雜性。
許多 方面保持軟體的可塑性,例如:
這句話從和而來?重構與編寫程式碼有什麼不同?
我知道我和其他很多人都 認為 我們在進行重構,但我們錯了。
#然而「重建」經常被用在不合適的地方。如果有人討論一個系統在重構時出現了幾天故障,你可以肯定他們不是在重構。
那是什麼呢?
在學校學習數學時,你可能學了因式分解。這裡有一個非常簡單的例子
計算1/2 1/4
#為此,將分母分解,將表達式轉換為
2/4 1/4
你可以把它變成3/4
.
我們可以從中學到一些重要的教訓。當我們 分解表達式 時,我們沒有改變表達式的意義。兩者都等於3/4
,但我們透過將1/2
變成2/4
後,我們的工作變得更容易了;它更適合我們的“領域”。
當你重構程式碼時,你應該在「符合」你目前系統需求的情況下,試著找一個方法來讓你的程式碼更容易理解。關鍵是你不應該改變程式碼原有的行為.
下面這個方法是用特定的 language
問候name
func Hello(name, language string) string { if language == "es" { return "Hola, " + name } if language == "fr" { return "Bonjour, " + name } // 想象一下更多的语言 return "Hello, " + name }
如果有幾十個if
語句會讓人感覺不舒服,而且我們還要重複的使用特定的language
去伴隨著,
問候name
。因此,我們來重構程式碼。
func Hello(name, language string) string { return fmt.Sprintf( "%s, %s", greeting(language), name, ) } var greetings = map[string]string { es: "Hola", fr: "Bonjour", //等等... } func greeting(language string) string { greeting, exists := greetings[language] if exists { return greeting } return "Hello" }
實際上,這個重構的本質並不重要,重要的是我們沒有改變程式碼的行為。
當重構時,你可以做任何你喜歡的事情,添加接口,新類型,函數,方法等等。唯一的規則是你不能改變程式碼的行為。
這非常重要。如果你重構時改變功能,你相當於同時在做 兩 件事。身為軟體工程師,我們應該學習把系統分成不同的檔案/套件/功能/等等,因為我們知道試圖理解一大塊東西是困難的。
我們不要一次想很多事情,因為那會使我們犯錯。我目睹了許多重構工作的失敗,因為開發人員貪多嚼不爛。
當我在數學課上用筆和紙做因式分解時,我必須手動檢查我是否改變了頭腦中表達式的意思。當我們重構程式碼時,尤其是在一個重要的系統上,我們如何知道我們是否改變了功能?
那些選擇不寫測試的人通常依賴手動測試。除非是一個小項目,否則這將是一個巨大的時間消耗,並且從長遠來看不利於系統將來的擴展。
為了安全地重構,您需要單元測試因為它提供了
可以在不擔心功能改變的情況下重構程式碼
有助於開發人員編寫關於系統應該如何運行的文件
#比手動測試更快更可靠的回饋
我們有一個Hello
方法的單元測試是這樣的:
func TestHello(t *testing.T) { got := Hello("Chris", es) want := "Hola, Chris" if got != want { t.Errorf("got %q want %q", got, want) } }
在命令列中,我們可以執行go test
,並且可以立即得到我們的重構工作是否影響了原先程式運行的回饋。實際上,最好學習在編輯器/IDE中執行測試。
你想要得到你程式正在執行的狀態
小的重構
阻礙 了重構。
捫心自問,重構時需要多久更改一次測試?這些年來,我參與了許多測試覆蓋率非常高的項目,但是工程師不願意重構,因為他們認為更改測試是費力的事情。 這與我們的承諾相反!#兩個直角三角形構成一個正方形
我們在正方形周圍寫單元測試以確保兩邊相等然後我們在三角形周圍寫一些測試。我們想確保我們的三角形渲染正確所以我們斷言這些角和是180度,我們做了兩個測試來檢查,等等。測試覆蓋率非常重要,編寫這些測試非常簡單,為什麼不呢?
幾週後,不斷變化的規律衝擊了我們的系統,一個新的開發人員做出了一些改變。她現在認為,如果用兩個長方形來構成正方形會比兩個三角形更好。
兩個長方形組成一個正方形
他嘗試進行這種重構,並從一些失敗的測試中得到一些提示。他真的破壞了程式碼重要的功能嗎?她現在必須深入研究這些三角形的測試,並試圖理解它內部到底發生了什麼事。
正方形由三角形組成實際上並不重要,但是我們的測試錯誤地提高了實作細節的重要性 。
當我聽到人們抱怨單元測試時,通常是因為測試處於錯誤的抽象層級。他們都在測試實作細節,過度的觀察協作者的程式碼並進行許多嘲諷。
我相信這個問題是因為他們對單元測試的誤解,以及對度量標準(測試覆蓋率)的追求。
如果我說的只是測試功能,我們不應該只寫系統/黑盒測試嗎?在驗證關鍵用戶歷程方面,這類測試確實有很多價值,但它們通常編寫成本高,運行速度慢。由於這個原因,它們對 重構 沒有太大幫助,因為回饋循環很慢。此外,與單元測試相比,黑盒測試對解決根本問題並沒有太大幫助。
那什麼 是 正確的抽象層級呢?
暫時忘記測試,最好在您的系統中包含自包含的、解耦的「單元」,以您的領域中的關鍵概念為中心。
我喜歡將這些單元想像成簡單的樂高積木,它們具有一致的 API,我可以將這些 API 與其他積木結合起來建造更大的系統。在這些 API 的內部,可能有許多東西(類型、函數等)協作,使它們能夠按照需要工作。
例如,如果你使用 Go 開發一個銀行系統,你應該會有一個「帳戶」包。它將提供一個不會洩漏實作細節且易於整合的 API。
如果您的單元遵循了這些屬性,那麼你可以針對它們的公共 API 編寫單元測試。 根據定義,這些測試只能測試有用的功能。在有這些單元的基礎下,只要有需要,我們可以自由地實現重構,並且在大多數情況下測試不會成為我們的阻礙。
是的。單元測試是針對我所描述的“單元”的。它們 從不 只針對一個類別/函數/任何東西。
我們已經講過了
重構
#單元測試
單元設計
#我們可以看到的是,這些方面的軟體設計是相輔相成的。
為我們的單元測試提供訊號。如果我們必須手動檢查,那麼我們需要更多的測試。如果測試失敗,那麼我們的測試就處於錯誤的抽象層次(或沒有值,應該刪除)
#幫助我們處理單元內部和單元之間的複雜性。
#為重構提供了安全防護。
驗證並記錄我們單元的功能。
#易於編寫 有意義 的單元測試。
易於重構。
是否有一個流程可以幫助我們不斷地重構程式碼,以管理複雜性並保持系統的可擴展性?
TDD透過鼓勵一種不斷重構和迭代交付的開發方式,解決了雷曼所談論的定律和其他歷史上難以學到的教訓。
為小功能寫一個小測試
檢查測試是否失敗,並且有明顯的錯誤(紅色)
編寫最少的程式碼使測試通過(綠色)
重構
重複以上步驟
隨著你熟練程度的挺高,對於你來說這會變成一種很自然的工作方式,工作效率也會越來越高
你開始希望你的小測試單元完成整個測試不要花費太多時間,因為如果你看到你的程式一直處於非「綠色」狀態,那表示你有可能遇到了一點小麻煩。
有了這些測試回饋,你能輕鬆保證一些小型應用功能的穩定性。
軟體的優點在於我與可以根據需要去改變他。隨著時間的推移,由於一些不可預測的原因,大多數軟體需要根據需求去做出相應的改變;但是一開始不要工作過頭,過度的設計,因為未來太難預測。
因此為了滿足上面的需要,我們需要保持我們的軟體的可擴展性。否則當我們的軟體需要重構升級時,情況將變得非常糟糕。
一個好的單元測試可以幫助您快速、愉悅的地進行專案重構。
寫好的單元測試是一個設計問題,你必須用心思考去建立你的程式碼,使你的每個單元測試像拼樂高積木一樣有趣的整合在一起,順利完成整個專案的測試。
測試驅動開發(TDD Test-Driven Development)可以幫助並促使你去迭代開發一個精心設計的軟體,以他為技術支持,會對你未來的工作有很大的幫助。
原文網址:https://quii.gitbook.io/learn-go-with-tests/meta/why
翻譯網址:https: //learnku.com/go/t/34095
更多程式相關知識,請造訪:程式設計影片! !
以上是為什麼要進行單元測試?怎麼進行測試?的詳細內容。更多資訊請關注PHP中文網其他相關文章!