php入門到就業線上直播課:進入學習
API 檔案、設計、偵錯、自動化測試一體化共同作業工具:
如果你不喜歡視訊,這裡有一個冗長的版本。
軟體是可以改變的。這就是為什麼它被稱為「軟」件,它的可塑性是比硬體強的。一個優秀的工程師團隊應該是一個公司一筆驚人的財富,他們編寫可以隨著業務發展而不斷增值的系統。
那麼,為什麼我們在這方面做的如此糟糕呢?你聽說過多少完全失敗的專案?或者成為「遺產」,必須完全重寫 (重寫通常也會失敗! )
軟體系統是如何「失敗」的呢?難道不能在它正確之前進行修改嗎?這就是我們的承諾!
很多人選擇用 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 編寫單元測試。根據定義,這些測試只能測試有用的功能。在有這些單元的基礎下,只要有需要,我們可以自由地實現重構,並且在大多數情況下測試不會成為我們的阻礙。
是的。單元測試是針對我所描述的「單元」的。它們 從不 只針對一個類/函數/任何東西。
我們已經講過了
重構
單元測試
單元設計
我們可以看到的是,這些方面的軟體設計是相輔相成的。
為我們的單元測試提供訊號。如果我們不得不手動檢查,那麼我們需要更多的測試。如果測試失敗,那麼我們的測試就處於錯誤的抽象級別(或者沒有值,應該刪除)
幫助我們處理單元內部和單元之間的複雜性。
為重構提供了安全防護。
驗證並記錄我們單元的功能。
易於編寫 有意義 的單元測試。
易於重構。
是否有一個流程可以幫助我們不斷地重構程式碼,以管理複雜性並保持系統的可伸縮性?
一些人可能會因為雷曼關於如何讓軟體不斷改變的思想,因此過度的設計,浪費大量的時間提前嘗試建立「完美的」可延伸系統,結果卻一事無成。
在過去糟糕的軟體時代,分析師團隊將花費6個月的時間編寫需求檔案,架構師團隊將花費6個月的時間進行設計,幾年後整個專案就失敗了。
我說過去的日子很糟糕,但現在還是這樣!
敏捷開發告訴我們,我們需要迭代地工作,從小處著手並不斷改進軟體,這樣我們就可以快速地得到關於軟體設計的反饋,以及軟體如何與實際使用者一起工作;TDD強制執行這種方法。
TDD通過鼓勵一種不斷重構和迭代交付的開發方式,解決了雷曼所談論的定律和其他歷史上難以學到的教訓。
為小功能寫一個小測試
檢查測試是否失敗,並有明顯的錯誤(紅色)
編寫最少的程式碼使測試通過(綠色)
重構
重複以上步驟
隨著你熟練程度的挺高,對於你來說這會變為一種很自然的工作方式,工作效率也會越來越高
你開始希望你的小測試單元完成整個測試不要花費太多時間,因為如果你看到你的程式一直處於非「綠色」狀態,那表明你有可能遇到了一點小麻煩。
有了這些測試反饋,你能輕鬆保證一些小型應用功能的穩定性。
軟體的優點在於我與可以根據需要去改變他。隨著時間的推移,由於一些不可預知的原因,大多數軟體需要根據需求去做出相應的改變;但是一開始不要工作過頭,過度的設計,因為未來太難預測。
因此為了滿足上面的需要,我們需要保持我們的軟體的可延伸性。否則當我們的軟體需要重構升級時,情況將變得非常糟糕。
一個好的單元測試可以幫助您快速、愉悅的地進行專案重構。
編寫好的單元測試是一個設計問題,你必須用心思考去去構建你的程式碼,使你的每個單元測試像拼樂高積木一樣有趣的整合在一起,順利完成整個專案的測試。
測試驅動開發(TDD Test-Driven Development)可以幫助並促使你去迭代開發一個精心設計的軟體,以他為技術支援,會對你未來的工作有很大的幫助。
原文地址:https://quii.gitbook.io/learn-go-with-tests/meta/why
譯文地址:https://learnku.com/go/t/34095
更多程式設計相關知識,請存取:!!
以上就是為什麼要進行單元測試?怎麼進行測試?的詳細內容,更多請關注TW511.COM其它相關文章!