[TOC]
## <span style="font-size:15px">**一、testing包說明**</span>
* 測試用例文件不會參與正常源碼的編譯,不會被包含到可執行文件中;
* 測試用例的文件名必須以`_test.go`結尾;
* 需要使用 import 導入 testing 包;
* 測試函數的名稱要以`Test`或`Benchmark`開頭,后面可以跟任意字母組成的字符串,但第一個字母必須大寫,例如 TestAbc(),一個測試用例文件中可以包含多個測試函數;
* 單元測試則以`(t *testing.T)`作為參數,性能測試以`(t *testing.B)`做為參數;
* 測試用例文件使用`go test`命令來執行,源碼中不需要 main() 函數作為入口,所有以`_test.go`結尾的源碼文件內以`Test`開頭的函數都會自動執行。
## <span style="font-size:15px">**二、功能測試(t \*testing.T)**</span>
| 庫函數 | 說明 |
| --- | --- |
| Log(args ...any) | 打印日志 |
| Logf(format string, args ...any) | 格式化其參數打印日志 |
| Fatal(args ...any) | 打印日志,執行FailNow()函數 |
| Fatalf(format string, args ...any) | 打印日志,執行FailNow()函數 |
| Error(args ...any) | 打印錯誤日志后,執行Fail()函數 |
| Errorf(format string, args ...any) | 打印錯誤日志后,執行Fail()函數 |
| Run(name string, f func(t *T)) bool | 運行單測函數 |
| Cleanup(f func()) | 作用類似于在函數中執行defer來做程序執行后的后續操作,比如釋放資源、清除依賴等。<br>參考:https://www.cnblogs.com/YYRise/p/12376689.html#%E4%BD%BF%E7%94%A8-cleanup |
| Deadline() (deadline time.Time, ok bool) | 設置測試的超時時間 |
| Fail() | Fail將函數標記為已失敗,但仍在繼續執行。 |
| Failed() | 檢查函數是否已失敗 |
| FailNow() | 將函數標記失敗并立即停止執行 |
| Helper() | Helper 函數用于將調用函數標記為測試輔助的函數。有時候測試中可能會有一些重復的代碼或者需要共享的輔助函數,這時就可以使用 Helper 函數來避免重復代碼,提高測試代碼的可維護性。Helper 函數可以被測試函數調用,但是它們不會被測試框架視為獨立的測試用例。這樣可以使測試函數更加清晰和簡潔 |
| Name() | 返回正在運行的測試或基準測試的名稱 |
| Parallel() | Parallel 函數用于標記測試函數可以并行執行。當測試函數被標記為 Parallel 時,測試框架會在執行測試時創建多個 goroutine 并行執行這些測試函數,從而加快測試的執行速度 |
| Setenv(key, value string) | 設置環境變量 |
| Skip(args ...any) | 打印日志,并執行SkipNow() |
| Skipf(format string, args ...any) | 打印日志,并執行SkipNow() |
| SkipNow() | 跳過當前測試,而不會導致測試失敗 |
| Skipped() bool | 檢查函數是否跳過 |
| TempDir() string | 用于創建一個臨時目錄,輔助測試 |
**Cleanup與 defer 的區別:**
1. 執行時機不同:Cleanup 函數會在測試函數執行完畢后立即執行,而 defer 語句會在當前函數返回前執行。
2. 調用方式不同:Cleanup 函數需要在測試函數中顯式調用,而 defer 語句可以在任何函數中使用。
3. 多個調用的處理方式不同:Cleanup 函數可以多次調用,每次調用都會按照調用順序執行清理操作;而 defer 語句只能在當前函數中使用一次,多次調用會按照后進先出的順序執行。
4. 錯誤處理方式不同:Cleanup 函數的清理操作不會影響測試函數的錯誤處理,即使清理操作中發生錯誤,測試函數的錯誤仍然會被捕獲和報告;而 defer 語句中的清理操作如果發生錯誤,可能會影響當前函數的錯誤處理。
5. 總的來說,Cleanup 函數適用于在測試函數執行完畢后進行一些清理操作,而 defer 語句適用于在函數返回前執行一些必要的清理操作。
```
func TestMD5(t *testing.T) {
if MD5("xxxxx") != "fb0e22c79ac75679e9881e6ba183b354" {
t.Fatalf("執行失敗")
}
t.Log("執行成功")
}
```
## <span style="font-size:15px">**三、性能測試(b *testing.B)**</span>
* 用例需以 Benchmark 為前綴
* 參數必須是 b *testing.B
* 函數返回值必須為空
| 庫函數 | 說明 |
| --- | --- |
| Log(args ...any) | 打印日志 |
| Logf(format string, args ...any) | 格式化其參數打印日志 |
| Fatal(args ...any) | 打印日志,執行FailNow()函數 |
| Fatalf(format string, args ...any) | 打印日志,執行FailNow()函數 |
| Error(args ...any) | 打印錯誤日志后,執行Fail()函數 |
| Errorf(format string, args ...any) | 打印錯誤日志后,執行Fail()函數 |
| Run(name string, f func(t *B)) bool | 運行單測函數 |
| Cleanup(f func()) | 作用類似于在函數中執行defer來做程序執行后的后續操作,比如釋放資源、清除依賴等。<br>參考:https://www.cnblogs.com/YYRise/p/12376689.html#%E4%BD%BF%E7%94%A8-cleanup |
| Fail() | Fail將函數標記為已失敗,但仍在繼續執行。 |
| Failed() | 檢查函數是否已失敗 |
| FailNow() | 將函數標記失敗并立即停止執行 |
| Helper() | Helper 函數用于將調用函數標記為測試輔助的函數。有時候測試中可能會有一些重復的代碼或者需要共享的輔助函數,這時就可以使用 Helper 函數來避免重復代碼,提高測試代碼的可維護性。Helper 函數可以被測試函數調用,但是它們不會被測試框架視為獨立的測試用例。這樣可以使測試函數更加清晰和簡潔 |
| Name() | 返回正在運行的測試或基準測試的名稱 |
| Setenv(key, value string) | 設置環境變量 |
| Skip(args ...any) | 打印日志,并執行SkipNow() |
| Skipf(format string, args ...any) | 打印日志,并執行SkipNow() |
| SkipNow() | 跳過當前測試,而不會導致測試失敗 |
| Skipped() bool | 檢查函數是否跳過 |
| TempDir() string | 用于創建一個臨時目錄,輔助測試 |
| N | 表示循環的次數,因為需要反復調用測試代碼來評估性能。b.N 的值會以1, 2, 5, 10, 20, 50, …這樣的規律遞增下去直到運行時間大于1秒鐘,由于程序判斷運行時間穩定才會停止運行 |
| ReportAllocs() | ReportAllocs為該基準啟用malloc統計信息。它相當于設置test.benchmem,但它只影響調用ReportAllocs的基準函數。 |
| ReportMetric(n float64, unit string) | 用于報告性能測試中收集到的指標數據,其中 n 是浮點數類型的指標值,unit 是指標的單位。 |
| ResetTimer() | ResetTimer 是 Golang 中 testing 包中的一個函數,用于重置計時器,通常用于基準測試中。在基準測試中,我們通常會使用 StartTimer 來開始計時,然后執行被測試的代碼,最后使用 StopTimer 來停止計時。在循環測試中,ResetTimer 可以用于重置計時器,以便在同一段代碼上多次運行計時,而不必重新創建計時器。 |
| RunParallel(body func(*PB)) | 用于在多個goroutine中并行運行測試函數。在調用 RunParallel 方法時,需要提供一個函數,該函數會在多個goroutine中并行執行。 |
| SetBytes(n int64) | SetBytes 方法用于設置每次操作處理的數據大小。這個方法通常用于基準測試中,可以幫助測試者評估在不同數據負載下的性能表現。通過設置數據大小,可以更好地模擬實際應用中的場景,從而更準確地評估代碼的性能。 |
| SetParallelism(p int) | 用于設置并行測試的數量 |
| StartTimer() | 開始計時測試。此函數在測試啟動前自動調用,但也可以用于在調用StopTimer后恢復計時 |
| StopTimer() |停止計時|
**運行命令:**
性能測試命令為 go test [參數],比如 go test -bench=. ,具體的命令參數及含義如下:
* -bench regexp 性能測試,運行指定的測試函。
* -bench . 運行所有的benchmark函數測試,指定名稱則只執行具體測試方法而不是全部。
* -benchmem 性能測試的時候顯示測試函數的內存分配的統計信息。
* -count n 運行測試和性能多少此,默認一次。
* -run regexp 只運行特定的測試函數。
* -timeout t 測試時間如果超過 t 則panic,默認10分鐘。
* -v 顯示測試的詳細信息。
```
func BenchmarkValid(b *testing.B) {
str := `{"foo":"bar"}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
json.Valid(([]byte(str)))
}
}
[root@master-4vfjd util]# go test -bench=BenchmarkValid
goos: linux
goarch: amd64
pkg: sangfor.com/xdr/xlink/common/util
cpu: Intel(R) Core(TM)2 Duo CPU T7700 @ 2.40GHz
BenchmarkValid-4 9462286 133.8 ns/op
PASS
ok sangfor.com/xdr/xlink/common/util 3.931s
```
**運行結果含義:**
* BenchmarkValid 是性能測試函數名稱
* -4 表示 GOMAXPROCS 的值為4
* 9462286 表示一共執行了9462286次,即b.N的值
* 133.8 ns/op 表示平均每次操作花費了 133.8 納秒
## <span style="font-size:15px">**四、代碼覆蓋率測試**</span>
* 執行并輸出覆蓋率:`go?test?-v?-cover`
* 將 cover 的詳細信息保存到cover.out 中:`go?test?-cover?-coverprofile=cover.out?-covermode=count`
* -cover?允許代碼分析
* -covermode?代碼分析模式(set:是否執行;count:執行次數;atomic:次數,并發執行)
* -coverprofile?輸出結果文件
* go?tool?cover?-func=cover.out
### <span style="font-size:15px">1. 測試單元測試的覆蓋率</span>
```
// 結果顯示只有43.9%覆蓋率
[root@master-4vfjd util]# go test -v -cover
=== RUN Test2
httpChainUtil_test.go:113: http://127.0.0.1:2563
--- PASS: Test2 (0.00s)
=== RUN Test1
httpChainUtil_test.go:140: http://127.0.0.1:12707
--- PASS: Test1 (0.12s)
=== RUN TestBytes2ReadOnlyString
=== RUN TestBytes2ReadOnlyString/Test_Bytes2ReadOnlyString
--- PASS: TestBytes2ReadOnlyString (0.00s)
PASS
coverage: 43.9% of statements
ok sangfor.com/xdr/xlink/common/util 0.367s
```
### <span style="font-size:15px">2. 查看每個函數的覆蓋率</span>
```
[root@master-4vfjd util]# go test -cover -coverprofile=cover.out -covermode=count
...
[root@master-4vfjd util]# go tool cover -func=cover.out
...
sangfor.com/xdr/xlink/common/util/rsaCrypt.go:19: ReadFile 76.9%
sangfor.com/xdr/xlink/common/util/rsaCrypt.go:42: RsaDecrypt 0.0%
sangfor.com/xdr/xlink/common/util/rsaCrypt.go:81: RsaEncrypt 64.0%
total: (statements) 43.9%
```
### <span style="font-size:15px">3. 使用html 的方式查看具體的覆蓋情況</span>
```
[root@master-4vfjd util]# go tool cover -html=cover.out
HTML output written to /tmp/cover629243415/coverage.html
```

## <span style="font-size:15px">**五、并發測試**</span>
默認情況下,指定包的測試是按照順序執行的,但也可以通過在測試的函數內部使用`t.Parallel()`來標志某些測試也可以被安全的并發執行。在并行執行的情況下,只有當那些被標記為并行的測試才會被并行執行,所以只有一個測試函數時是沒意義的。
在并發情況下,同時運行的測試的數量默認取決于`GOMAXPROCS`。它可以通過`-parallel n`被指定(`go test -parallel 4`)
**并發測試實例:**
```
package main
import (
"fmt"
"testing"
"time"
)
func TestParallel(t *testing.T) {
// 設置測試的并行數量
t.Parallel()
// 模擬一個需要耗時的測試
time.Sleep(1 * time.Second)
// 打印測試結果
fmt.Println("Test completed")
}
func TestSequential(t *testing.T) {
// 模擬一個需要耗時的測試
time.Sleep(1 * time.Second)
// 打印測試結果
fmt.Println("Test completed")
}
func main() {
// 運行測試
testing.Main(func(pat, str string) (bool, error) { return true, nil }, []testing.InternalTest{
{Name: "TestParallel", F: TestParallel},
{Name: "TestSequential", F: TestSequential},
}, nil, nil)
}
```
## <span style="font-size:15px">**六. 競爭檢查**</span>
數據競爭是指兩個或多個goroutine并發訪問同一塊內存,并且至少其中一個是寫操作,可能會導致panic等問題,如`panic: runtime error: invalid memory address or nil pointer dereference`
`go run-v -race`:-race 參數是指開啟競爭檢查,可以檢測并發操作是否安全。
`go test -race`:執行測試并開啟競爭檢查
**實例:**
```
// vim race.go
package main
import "fmt"
func main() {
done := make(chan bool)
m := make(map[string]string)
m["name"] = "world"
go func() {
m["name"] = "data race"
done <- true
}()
fmt.Println("Hello,", m["name"])
<-done
}
```
```
[root@master-4vfjd util]# go run -race race.go
Hello, world
==================
WARNING: DATA RACE
Write at 0x00c000070150 by goroutine 7:
runtime.mapassign_faststr()
/root/go1.18.10/go/src/runtime/map_faststr.go:203 +0x0
main.main.func1()
/root/XLink/common/util/race.go:10 +0x50
Previous read at 0x00c000070150 by main goroutine:
runtime.mapaccess1_faststr()
/root/go1.18.10/go/src/runtime/map_faststr.go:13 +0x0
main.main()
/root/XLink/common/util/race.go:13 +0x16b
Goroutine 7 (running) created at:
main.main()
/root/XLink/common/util/race.go:9 +0x14e
==================
==================
WARNING: DATA RACE
Write at 0x00c000100088 by goroutine 7:
main.main.func1()
/root/XLink/common/util/race.go:10 +0x5c
Previous read at 0x00c000100088 by main goroutine:
main.main()
/root/XLink/common/util/race.go:13 +0x175
Goroutine 7 (running) created at:
main.main()
/root/XLink/common/util/race.go:9 +0x14e
==================
```
在這段代碼中,goroutine 7 和主goroutine 同時訪問了map m,其中一個是寫操作,另一個是讀操作,導致了數據競爭。
具體來說,goroutine 7 在匿名函數中對map m進行了寫操作,而主goroutine 中的 fmt.Println("Hello,", m["name"]) 則是對map m進行了讀操作,這就引發了數據競爭。
在運行時,Go語言的競爭檢測器(race detector)檢測到了這個問題,并給出了警告信息,指出了具體的數據競爭情況,包括讀寫的內存地址和涉及的goroutine。
要解決這個問題,可以使用互斥鎖(mutex)來保護并發訪問的map,確保同一時間只有一個goroutine在訪問該map,從而避免數據競爭的發生。
```
// 互斥鎖方式修復
package main
import (
"fmt"
"sync"
)
func main() {
done := make(chan bool)
m := make(map[string]string)
m["name"] = "world"
var mutex sync.Mutex
go func() {
mutex.Lock()
m["name"] = "data race"
mutex.Unlock()
done <- true
}()
mutex.Lock()
fmt.Println("Hello,", m["name"])
mutex.Unlock()
<-done
}
```
- PHP
- PHP基礎
- PHP介紹
- 如何理解PHP是弱類型語言
- 超全局變量
- $_SERVER詳解
- 字符串處理函數
- 常用數組函數
- 文件處理函數
- 常用時間函數
- 日歷函數
- 常用url處理函數
- 易混淆函數區別(面試題常見)
- 時間戳
- PHP進階
- PSR規范
- RESTFUL規范
- 面向對象
- 三大基本特征和五大基本原則
- 訪問權限
- static關鍵字
- static關鍵字
- 靜態變量與普通變量
- 靜態方法與普通方法
- const關鍵字
- final關鍵字
- abstract關鍵字
- self、$this、parent::關鍵字
- 接口(interface)
- trait關鍵字
- instanceof關鍵字
- 魔術方法
- 構造函數和析構函數
- 私有屬性的設置獲取
- __toString()方法
- __clone()方法
- __call()方法
- 類的自動加載
- 設計模式詳解
- 關于設計模式的一些建議
- 工廠模式
- 簡單工廠模式
- 工廠方法模式
- 抽象工廠模式
- 區別和適用范圍
- 策略模式
- 單例模式
- HTTP
- 定義
- 特點
- 工作過程
- request
- response
- HTTP狀態碼
- URL
- GET和POST的區別
- HTTPS
- session與cookie
- 排序算法
- 冒泡排序算法
- 二分查找算法
- 直接插入排序算法
- 希爾排序算法
- 選擇排序算法
- 快速排序算法
- 循環算法
- 遞歸與尾遞歸
- 迭代
- 日期相關的類
- DateTimeInterface接口
- DateTime類
- DateTimeImmutable類
- DateInterval類
- DateTimeZone類
- DatePeriod類
- format參數格式
- DateInterval的format格式化參數
- 預定義接口
- ArrayAccess(數組式訪問)接口
- Serializable (序列化)接口
- Traversable(遍歷)接口
- Closure類
- Iterator(迭代器)接口
- IteratorAggregate(聚合迭代器) 接口
- Generator (生成器)接口
- composer
- composer安裝與使用
- python
- python3執行tarfile解壓文件報錯:tarfile.ReadError:file could not be opened successfully
- golang
- 單元測試
- 單元測試框架
- Golang內置testing包
- GoConvey庫
- testify庫
- 打樁與mock
- GoMock框架
- Gomonkey框架
- HTTP Mock
- httpMock
- mux庫/httptest
- 數據庫
- MYSQL
- SQL語言的分類
- 事務(重點)
- 索引
- 存儲過程
- 觸發器
- 視圖
- 導入導出數據庫
- 優化mysql數據庫的方法
- MyISAM與InnoDB區別
- 外連接、內連接的區別
- 物理文件結構
- PostgreSQL
- 編譯安裝
- pgsql常用命令
- pgsql應用目錄(bin目錄)文件結構解析
- pg_ctl
- initdb
- psql
- clusterdb
- cluster命令
- createdb
- dropdb
- createuser
- dropuser
- pg_config
- pg_controldata
- pg_checksums
- pgbench
- pg_basebackup
- pg_dump
- pg_dumpall
- pg_isready
- pg_receivewal
- pg_recvlogical
- pg_resetwal
- pg_restore
- pg_rewind
- pg_test_fsync
- pg_test_timing
- pg_upgrade
- pg_verifybackup
- pg_archivecleanup
- pg_waldump
- postgres
- reindexdb
- vacuumdb
- ecpg
- pgsql數據目錄文件結構解析
- pgsql數據目錄文件結構解析
- postgresql.conf解析
- pgsql系統配置參數說明
- pgsql索引類型
- 四種索引類型解析
- 索引之ctid解析
- 索引相關操作
- pgsql函數解析
- pgsql系統函數解析
- pgsql窗口函數解析
- pgsql聚合函數解析
- pgsql系統表解析
- pg_stat_all_indexes
- pg_stat_all_tables
- pg_statio_all_indexes
- pg_statio_all_tables
- pg_stat_database
- pg_stat_statements
- pg_extension
- pg_available_extensions
- pg_available_extension_versions
- pgsql基本原理
- 進程和內存結構
- 存儲結構
- 數據文件的內部結構
- 垃圾回收機制VACUUM
- 事務日志WAL
- 并發控制
- 介紹
- 事務ID-txid
- 元組結構-Tuple Structure
- 事務狀態記錄-Commit Log (clog)
- 事務快照-Transaction Snapshot
- 事務快照實例
- 事務隔離
- 事務隔離級別
- 讀已提交-Read committed
- 可重復讀-Repeatable read
- 可序列化-Serializable
- 讀未提交-Read uncommitted
- 鎖機制
- 擴展機制解析
- 擴展的定義
- 擴展的安裝方式
- 自定義創建擴展
- 擴展的管理
- 擴展使用實例
- 在pgsql中使用last、first聚合函數
- pgsql模糊查詢不走索引的解決方案
- pgsql的pg_trgm擴展解析與驗證
- 高可用
- LNMP
- LNMP環境搭建
- 一鍵安裝包
- 搭建方法
- 配置文件目錄
- 服務器管理系統
- 寶塔(Linux)
- 安裝與使用
- 開放API
- 自定義apache日志
- 一鍵安裝包LNMP1.5
- LNMP1.5:添加、刪除站點
- LNMP1.5:php多版本切換
- LNMP1.5 部署 thinkphp項目
- Operation not permitted解決方法
- Nginx
- Nginx的產生
- 正向代理和反向代理
- 負載均衡
- Linux常用命令
- 目錄與文件相關命令
- 目錄操作命令
- 文件編輯命令
- 文件查看命令
- 文件查找命令
- 文件權限命令
- 文件上傳下載命令
- 用戶和群組相關命令
- 用戶與用戶組的關系
- 用戶相關的系統配置文件
- 用戶相關命令
- 用戶組相關命令
- 壓縮與解壓相關命令
- .zip格式
- .tar.gz格式
- .gz格式
- .bz2格式
- 查看系統版本
- cpuinfo詳解
- meminfo詳解
- getconf獲取系統信息
- 磁盤空間相關命令
- 查看系統負載情況
- 系統環境變量
- 網絡相關命令
- ip命令詳解
- ip命令格式詳解
- ip address命令詳解
- ip link命令詳解
- ip rule命令詳解
- ip route命令詳解
- nslookup命令詳解
- traceroute命令詳解
- netstat命令詳解
- route命令詳解
- tcpdump命令詳解
- 系統進程相關命令
- ps命令詳解
- pstree命令詳解
- kill命令詳解
- 守護進程-supervisord
- 性能監控相關命令
- top命令詳解
- iostat命令詳解
- pidstat命令詳解
- iotop命令詳解
- mpstat命令詳解
- vmstat命令詳解
- ifstat命令詳解
- sar命令詳解
- iftop命令詳解
- 定時任務相關命令
- ssh登錄遠程主機
- ssh口令登錄
- ssh公鑰登錄
- ssh帶密碼登錄
- ssh端口映射
- ssh配置文件
- ssh安全設置
- 歷史紀錄
- history命令詳解
- linux開啟操作日志記錄
- 拓展
- git
- git初始化本地倉庫-https
- git初始化倉庫-ssh
- git-查看和設置config配置
- docker
- 概念
- docker原理
- docker鏡像原理
- docker Overlay2 文件系統原理
- docker日志原理
- docker日志驅動
- docker容器日志管理
- 原理論證
- 驗證容器的啟動是作為Docker Daemon的子進程
- 驗證syslog類型日志驅動
- 驗證journald類型日志驅動
- 驗證local類型日志驅動
- 修改容器的hostname
- 修改容器的hosts
- 驗證聯合掛載技術
- 驗證啟動多個容器對于磁盤的占用情況
- 驗證寫時復制原理
- 驗證docker內容尋址原理
- docker存儲目錄
- /var/lib/docker目錄
- image目錄
- overlay2目錄
- 數據卷
- 具名掛載和匿名掛載
- 數據卷容器
- Dockerfile詳解
- dockerfile指令詳解
- 實例:構造centos
- 實例:CMD和ENTRYPOINT的區別
- docker網絡詳解
- docker-compose
- 緩存
- redis
- redis的數據類型和應用場景
- redis持久化
- RDB持久化
- AOF持久化
- redis緩存穿透、緩存擊穿、緩存雪崩
- 常見網絡攻擊類型
- CSRF攻擊
- XSS攻擊
- SQL注入
- Cookie攻擊
- 歷史項目經驗
- 圖片上傳項目實例
- 原生php上傳方法實例
- base64圖片流
- tp5的上傳方法封裝實例
- 多級關系的遞歸查詢
- 數組轉樹結構
- thinkphp5.1+ajax實現導出Excel
- JS 刪除數組的某一項
- 判斷是否為索引數組
- ip操作