Go 快速入門篇(三):單元測試、問題定位及代碼調試

2021-02-25 xueyuanjun

一、單元測試
編寫單元測試

在 Go 語言中,支持為功能模塊編寫單元測試代碼,繼續以上篇教程構建的計算器項目為例,在 simplemath 包中,我們可以為每一個運算模塊編寫了對應的單元測試代碼。

單元測試文件默認以同一目錄下文件名後綴 _test 作為標識,比如我們在 simplemath 目錄下新建 add_test.go 和 sqrt_test.go 文件,分別為 add.go 和 sqrt.go 編寫單元測試,對應的目錄結構如下:

編寫 add_test.go 代碼如下:

package simplemath

import "testing"

func TestAdd(t *testing.T) {
    r := Add(1, 2)
    if r != 3 {
        t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
    }
}

以及 sqrt_test.go 代碼如下:

package simplemath

import "testing"

func TestSqrt(t *testing.T) {
    v := Sqrt(9)
    if v != 3 {
        t.Errorf("Sqrt(9) failed. Got %v, expected 3.", v)
    }
}

在編寫單元測試時,需要引入 testing 包,你可以將其類比為 PHP 中的 PHPUnit 或 Java 中的 JUnit,我們可以基於該包提供的方法來實現自動化測試,測試方法的格式如下所示:

func TestXXX(t *testing.T) {
    // 測試邏輯
}

運行單元測試

接下來,怎麼運行這些單元測試呢?這也非常簡單。

在 GoLand 中,你可以選擇要執行單元測試的包,比如這裡的 simplemath,然後通過右鍵快捷菜單依次選擇 Run->go test simplemath:

GoLand 對指定包執行單元測試

就可以在 GoLand 底部 Run 窗口中看到測試運行結果:

GoLand 單元測試結果

可以看到,運行結果列出了測試的內容、測試結果和測試時間。如果我故意把 add_test.go 的代碼改成這樣的錯誤場景:

func TestAdd(t *testing.T) {
    r := Add(1, 2)
    if r != 2 {
        t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
    }
}

然後我們再次執行單元測試,這一次,我們可以直接選擇對 add_test.go 執行單元測試,在打開的 add_test.go 文件代碼編輯區域通過右鍵快捷菜單選擇 Run 'add_test.go' 即可:

GoLand 對指定文件執行單元測試

當然,你可以可以具體對某個方法進行單元測試,點擊 GoLand 在行號右側為測試方法渲染的綠色測試小圖標,選擇 Run 'TestAdd in calc/simplepath' 即可:

GoLand 對指定方法執行單元測試

不管以何種方式運行測試,最終都會得到如下的測試失敗結果:

GoLand 單元測試失敗

列印的錯誤信息非常簡潔,卻已經足夠讓開發者快速定位到問題代碼所在的文件和行數,從而在最短的時間內確認是單元測試的問題還是程序的問題。

二、問題定位與代碼調試列印變量

當然,對於一些簡單的測試,還可以通過列印變量的方式來定位問題,通常我們在 PHP 中就是這麼做的,比如通過 var_dump、printf、echo 之類的語句或函數列印返回的結果,在 Laravel 框架中還可以通過 dd 或 dump 方法進行簡單高效的變量列印調試,在 Go 語言中,對應的列印函數是前面介紹過的 Printf 或 Println 方法,用於對變量進行格式化輸出(類比 PHP 中的 printf 函數和 print 函數,PHP 沒有提供類似 Println 這樣的方法,但是你可以通過在列印字符串結尾加上 \n 來實現換行),這兩個方法都位於 fmt 格式化包中,我們可以這樣列印變量:

fval := 110.48 
ival := 200 
sval := "This is a string. " 
fmt.Println("The value of fval is", fval) 
fmt.Printf("fval=%f, ival=%d, sval=%s\n", fval, ival, sval) 
fmt.Printf("fval=%v, ival=%v, sval=%v\n", fval, ival, sval)

對應的輸出結果是:

The value of fval is 110.48
fval=110.480000, ival=200, sval=This is a string. 
fval=110.48, ival=200, sval=This is a string.

輸出日誌

如果代碼是在線上生產環境執行,列印變量這種定位問題的方式就不合適了,這個時候我們可以通過 log 包提供的方法列印關鍵信息或錯誤信息日誌,方便對線上問題進行追蹤,關於日誌功能後面我們在進階版的工程管理中會詳細介紹,這裡先了解下即可。

IDE 調試

如果你是通過 GoLand 進行開發的話,直接在代碼中設置斷點(單擊對應代碼行即可),然後選中要調試的源碼文件,在右鍵下拉菜單中點擊「Debug」對應的選項即可開始對指定文件代碼進行斷點調試:

GoLand 代碼調試

進入調試模式後,代碼執行流程會暫停在斷點處。在 GoLand 界面下方控制臺的 Debug 窗口中,可以看到當前程序的堆棧信息,你可以通過手動控制(跳入、跳出、進入下一行、終止調試等)對代碼進行調試,具體操作模式和 PhpStorm、ItelliJ IDEA 差不多:

GDB 調試

日常開發使用 GoLand 自帶的代碼調試功能就夠了,如果你想要通過更加 Hacker 的方式調試代碼,可以選擇 GDB。

GDB 是一個由 GNU 開源組織發布的、Unix/Linux 作業系統下的、基於命令行的、功能強大的程序調試工具,Go 語言編譯後的二進位文件支持通過 GDB 進行調試,比如上篇教程通過 go build calc 編譯出來的可執行文件 calc,就可以直接用以下命令以調試模式運行:

gdb calc

註:Windows 系統不支持該工具,Mac 下可以通過 brew install gdb 命令安裝。

然後,你就可以通過 GDB 支持的指令以命令行的方式對 Go 代碼進行調試了,你可以通過 l 指令查看代碼:

要跳到某一行查看通過 l <line> 傳入行數即可:

要為某一行設置斷點可以通過 b <line> 來實現:

然後通過 run 命令來運行程序,如果是在 Mac 系統上,可能會報下面這個錯:

這是因為 Darwin 內核在你沒有特殊權限的情況下,不允許調試其它進程。調試某個進程,意味著你對這個進程有完全的控制權限,所以為了防止被惡意利用,它是默認禁止的。允許 gdb 控制其它進程最好的方法就是用系統信任的證書對它進行籤名,對應的解決方法參考這裡:https://opensource.apple.com/source/lldb/lldb-69/docs/code-signing.txt。

進入下一行可以用 n 指令,列印變量可以用 p <var> 指令傳入變量名。。。更多指令使用我就不深入展開了,因為對於新手來說,不推薦使用 GDB 進行代碼調試,直接使用 GoLand 更友好,不是嗎?如果你想探究 GDB 調試的更多用法,請查看對應的官方文檔 。

小結

至此,Go 語言零基礎入門三步曲已經完結,分別是第一個 Go 程序、簡單的工程管理、單元測試及代碼調試,下一篇起,我們將正式開始介紹 Go 語言的語言特性、面向對象編程、並發編程、網絡編程等高級使用指南,你可以通過關注本公眾號關注此系列教程的更新動態,學習過程中有任何問題,可以通過教程下方的評論或加入「Go 語言研習社」與學院君討論:

本系列教程首發在 geekr.dev,你可以點擊頁面左下角閱讀原文連結查看最新更新的教程。

相關焦點

  • Android單元測試——初探
    紅灰李的博客地址:http://www.jianshu.com/users/6ca395ea7e3e這篇文章主要是總結一下我自己在學習Android單元測試過程中的收穫及感悟,同時也希望可以幫助到正在學習Android單元測試的小夥伴們.由於時間及經驗有限,文中可能存在錯誤與不足,歡迎大家指出,我會在第一時間對文章進行修改糾正.
  • .NET高級調試系列-Windbg調試入門篇
    準備近期寫2篇精華文章,集中給大家分享一下如果通過Windbg進行.NET高級調試。今天我們來一篇入門的文章。首先,Windbg是什麼?Windows Debugger,簡稱WinDbg,.NET 最強分析調試利器。
  • 移動APP測試中關鍵代碼定位
    ,經常需要去定位網絡框架及Java層的數據包請求方法,又比如想知道在觸發某個業務的時候,都調用了哪些方法,方法與方法之間是如何調用的,誰調用了誰,誰又被誰調用了,這些都是在分析測試過程中經常需要考慮的,本文就結合自己實際項目測試經驗,談談自己在實際項目中常用的關鍵代碼查找的一些思路和方法。
  • VBA代碼的調試行為
    當房子建好後,我們首先要測試房子的各項指標是否達到了我們要求,這個就是程序調試。不是每個程序寫完後都能馬上正常運行,在某種程度上講,寫完代碼後的調試本身就是寫代碼的一部分。有的程序調試過程很快,有的會時間長些,有的甚至會推倒重來。
  • 使用testify和mockery庫簡化單元測試
    後來發現 testify/require 和 testify/assert 可以大大簡化單元測試的寫法,完全可以替代 t.Fatalf 和 t.Errorf,而且代碼實現更為簡短、優雅。再後來,發現了 mockery 庫,它可以為 Go interface 生成一個 mocks struct。
  • GO語言入門(第一個go程序)
    本文節選自《go入門指南》GO語言介紹指導設計原則
  • Task12: 單元測試
    11.單元測試本節代碼樣例見code/utest文件夾在日常開發中,我們通常需要針對現有的功能進行單元測試,以驗證開發的正確性。在go標準庫中有一個叫做testing的測試框架,可以進行單元測試,命令是go test xxx。測試文件通常是以xx_test.go命名,放在同一包下面。
  • Google Go 語言從入門到應用必備開源項目
    關一、入門篇0. 開源圖書 《Go Web編程》一本開源的書籍《Go Web編程》《build web application with golang》。書中作者主要分享了Go寫web應用的一些東西:Revel 框架支持熱編譯,當編輯、保存和刷新源碼時,Revel 會自動編譯代碼和模板;全棧特性,支持路由、參數解析、緩存、測試、國際化等功能。5.
  • Python代碼調試的那些「最少且必要」技巧
    異常處理模塊能幫助我們在運行期間處理異常信息,但Python代碼還有更為基礎的錯誤——語法錯誤和邏輯錯誤。語法錯誤相對簡單,在解釋器的幫助下,我們很快就能定位錯誤所在。但對邏輯錯誤的調試就難多了,這些語法或邏輯層面的錯誤,構成了各式各樣的代碼bug。
  • go runtime debug 小技巧
    結果令我有點沮喪,搜到的幾乎所有文章開篇都是通過GDB調試, 然後就是不同平臺下的彙編代碼。。。這令我很不開心, 雖然C/C++應用很廣泛, 但是我對它真的沒啥興趣啊, 對它相關的調試工具就更加不感冒了, 雖然它可以調試go程序, 但是總感覺心裡少了點什麼, 難道dlv它不香嘛, 於是就有了今天這篇文章。
  • Python學習之錯誤調試和測試
    好比一個員工處理不了一個問題時,就把問題拋給他的老闆,如果他的老闆也處理不了,就一直往上拋,最終會拋給CEO去處理。raise語句如果不帶參數,就會把當前錯誤原樣拋出。此外,在except中raise一個Error,還可以把一種類型的錯誤轉化成另一種類型:調試程序能一次寫完並正常運行的概率很小,基本不超過1%。總會有各種各樣的bug需要修正。
  • 單元測試可測試程式設計師代碼編寫的正確性,如何使用VS2019測試項目
    單元測試是指編寫代碼來驗證開發者編寫代碼的正確性,一般單元測試也是由開發者完成的,自已開發單元測試代碼來檢查自己編寫代碼的通過性。定義:單元測試是開發人員編寫的、用於檢測在特定條件下目標代碼正確性的代碼,單元測試是代碼級別的測試。
  • GDB與Valgrind ,調試C++代碼內存的工具
    筆者 入"坑"C++之後,在調試 C++代碼的過程之中,學習了不少調試代碼內存的工具。希望借這個機會來介紹一下筆者常用的工具,GDB,Valgrind等等,相信大家通過好好運用這些工具,能更好的馴服內存這匹"野馬"。
  • 聊聊單元測試
    還有一種情況是,寫代碼的時候並沒有考慮這代碼要怎麼測,因此寫完了以後發現寫單元測試很難,沒有現成的測試入口。這時候項目交付的deadline又快到了,唉,要不先放著改天再寫吧。當然我們都知道,這個改天大概率再也不會做。我們有一萬個理由可以不做單元測試。但是這就好比,組裝一架飛機不用測試各個零件的運作是否符合預期,直接讓它飛起來再看有哪些問題。
  • 推薦一個快速定位深度學習代碼bug的煉丹神器!
    為了幫助自己和其他程式設計師調試張量代碼,Terence Parr 寫了一個名叫 TensorSensor 的庫(pip install tensor-sensor 直接安裝) 。TensorSensor 通過增加消息和可視化 Python 代碼來展示張量變量的形狀,讓異常更清晰(見下圖)。它可以兼容 TensorFlow、PyTorch 和 Numpy以及 Keras 和 fastai 等高級庫。
  • [GO語言基礎] 一.為什麼我要學習Golang以及GO語言入門普及
    C/C++的問題:開發效率低,對開發者要求高;libc只向後兼容,運維難度偏大。Lua/Python的問題:動態語言,缺少編譯過程,低級錯誤頻出;缺少有效的性能分析及調試工具。不要這樣學習:只看書,不上機只運行,不調試只收藏,不實踐,半途而廢只做課後練習,不做工程項目應該這樣學習:Golang語言的基本學習方法和其他程式語言類似,需要注意以下幾點:高效愉快地學習先建立一個整體框架,然後細節在實際工作中,要培養用到什麼,能夠快速學習什麼能力
  • 如何入門網站安全滲透測試
    從大學畢業的時候開始簡單入門,寫寫網站程序代碼,搞搞sql注入以及安全測試,到現在Sinesafe當安全工程師,差不多在安全行業成長了11年,發現不懂得問題隨著實戰滲透測試中非常多,還是學到老乾到老才是成功之道。
  • 單元測試的藝術
    以前也讀過關於單元測試、測試驅動開發的書,也採用了相關的方法實踐了一段時間,但就是因為缺乏對測試的系統了解,所以一開始著急著快速編碼完成任務,就把測試放在一邊了
  • 用 Hypothesis 快速測試你的 Python 代碼
    該測試庫覆蓋了大多數情況,並且確實可以幫助你查找代碼中的錯誤。這篇文章為展示了如何使用Hypothesis在Python中進行測試,並提供了一些示例。我們如何區分測試?所謂靜態測試(static testing)就是不實際運行被測軟體,而只是靜態地檢查程序代碼、界面或文檔中可能存在的錯誤的過程。如果軟體或其部分實際執行,我們稱之為動態測試。編寫單元測試和集成測試屬於動態測試。另一種常見的方法是盒式方法。基本上,它可以分為白盒測試和黑盒測試(以及灰盒測試作為兩者的混合)。
  • Golang後臺單元測試實踐
    Why單元測試新功能的增加,代碼複雜性的提高,優化代碼的需要,或新技術的出現都會導致重構代碼的需求。在沒有寫單元測試的情況下,對代碼進行大規模修改,是一件不敢想像的事情,因為寫錯的概率實在太大了。而如果原代碼有單元測試,即使修改了代碼單測依然通過,說明沒有破壞程序正確性,一點都不慌!