服務計算 TDD實踐——實現快速排序演算法

無關風月風光無發表於2020-09-27

服務計算 TDD實踐——實現快速排序演算法

1. 概念理解——TDD

1.1 什麼是TDD?

TDD 是 Test-Driven Development 的首字母縮寫,其中文為 “測試驅動開發”,在百度百科中,TDD 的定義如下:

TDD 是敏捷開發中的一項核心實踐和技術,也是一種設計方法論。TDD的原理是在開發功能程式碼之前,先編寫單元測試用例程式碼,測試程式碼確定需要編寫什麼產品程式碼。TDD 是 XP(Extreme Programming)的核心實踐。它的主要推動者是 Kent Beck。

1.2 TDD 程式設計方式

在傳統的程式設計方式中,程式設計師在需求分析結束後開始編碼,直到編寫完成之後才開始業務邏輯的測試,這使得程式的除錯變的困難,而且通過這種方式編寫的程式通常邏輯不夠清晰,可讀性較差,使得程式的修改和維護變得十分困哪。為了消除傳統程式設計方式帶來的弊端,TDD 的程式設計方式與傳統方式有很大不同:

  • 先分解任務,將大問題分解為若干個小問題;
  • 舉例,用例項化需求,澄清需求細節;
  • 寫測試,只關注需求,程式的輸入輸出,不關心中間過程;
  • 寫實現,不考慮別的需求,用最簡單的方式滿足當前這個小需求;
  • 重構,使先前編寫的程式碼變得整潔規範;
  • 執行測試,若出現問題則補充測試用例,並修正程式碼;
  • 程式碼編寫完成。

1.3 TDD 的優點

  • 我們每次只需實現一個子任務,降低了開發者的思維負擔;
  • 每次實現一個子任務都需進行單元測試,可以及時修正錯誤;
  • 提前編寫測試可以幫助我們明確需求及其細節,避免需求理解的偏差等問題;
  • 覆蓋完全的單元測試使得優化程式碼或修改業務邏輯時變得安全;

1.4 TDD 的流程

  • 寫一個測試用例;
  • 嘗試執行測試(必然失敗);
  • 先用最少的程式碼通過編譯,執行測試;
  • 把程式碼補充完整,使得它能通過測試;
  • 程式碼重構,提高程式碼質量;
  • 編寫、執行基準測試。

1.5 相關詞彙理解

  • 重構:改善程式碼的內部結構,提升程式碼的質量和效能;
  • 測試:功能測試,用於測試程式的邏輯是否正確;
  • 基準測試:效能測試,用於測試函式效能。

2. TDD實現快速排序

2.1 編寫測試用例 quicksort_test.go

單元測試的編寫格式為:

  • 檔名必須以 _test.go 結尾;
  • 方法名必須是 Test 開頭;
  • 方法引數必須是 t *testing.T

因為我們需要實現的功能為快速排序,因此我們需要測試一個無序陣列在經過 Quicksort 函式處理後是否有序(升序),據此可以編寫如下測試:

package sort

import "testing"

func TestQuicksort(t *testing.T){
	cases := []struct {
		in, want []int
	}{
		{ []int{5, 3, 2, 1, 4}, []int{1, 2, 3, 4, 5} },
	}

	for _, c := range cases {
		temp := c.in
		Quicksort(c.in)
		for i := 0; i < len(c.in); i++ {
			if c.in[i] != c.want[i] {
				t.Errorf("Quicksort(%q) == %q, want %q", temp, c.in, c.want)
				break
			}
		}
	}
}

2.2 嘗試執行測試

使用 go test ./sort 嘗試執行測試,顯然,因為我們尚未實現 Quicksort 函式,因此該測試必定是失敗的。雖然我們知道該測試不可能成功,但我們還是必須進行此次嘗試,使得執行結果使我們所期待的失敗(找不到 Quicksort 函式),而不是其他失敗(編譯錯誤等),這樣當程式出現錯誤的時候,我們可以很快定位到出錯的位置。執行測試的結果如下:
go test 無法執行

2.3 用最少的程式碼通過編譯,執行測試

為了使測試過程不再出現編譯錯誤,我們需要先定義 Quicksort 函式供測試函式呼叫:

package sort

func Quicksort(s []int) []int {
	return s
}

此時執行測試的結果如下:

go test 成功執行

2.4 把程式碼補充完整,使得它能通過測試

package sort

func Quicksort(s []int) {
	if len(s) > 0 {
		x := s[0]
		i := 0
		j := len(s) - 1
		
		for i < j {
			for ; i < j; j-- {
				if s[j] < x {
					s[i] = s[j]
					i++
					break
				}
			}
			
			for ; i < j; i++ {
				if s[i] > x {
					s[j] = s[i]
					j--
					break
				}
			}	
		}
	
		s[i] = x
		
		Quicksort(s[:i])
		Quicksort(s[i+1:])
	}
}

執行測試,如果測試失敗,根據失敗資訊可以快速定位到出錯位置進行更正,直到通過測試:

go test 測試通過

2.5 程式碼重構,提高程式碼質量

對過長的快速排序函式進行重構,將其分解為較短的,易理解的,重用性較高的幾個函式:

package sort

func AdjustNum(s []int, x int, i int, j int) (int, int) {
	for ; i < j; j-- {
		if s[j] < x {
			s[i] = s[j]
			i++
			break
		}
	}
	
	for ; i < j; i++ {
		if s[i] > x {
			s[j] = s[i]
			j--
			break
		}
	}
	return i, j
}

func AdjustSlice(s []int) int {
	x := s[0]
	i := 0
	j := len(s) - 1
		
	for i < j {
		i, j = AdjustNum(s, x, i, j)
	}
	s[i] = x
	
	return i	
}

func Quicksort(s []int) {
	if len(s) > 0 {
		i := AdjustSlice(s)
		Quicksort(s[:i])
		Quicksort(s[i+1:])
	}
}

再次執行測試,確保快速排序函式的功能不發生改變:

go test 測試通過

2.6 編寫、執行基準測試

基準測試的編寫格式為:

  • 檔名必須以 _test.go 結尾;
  • 方法名必須是 Test 開頭;
  • 方法引數必須是 b *testing.B

按照上面的要求編寫基準測試(將基準測試與單元測試放在同一測試檔案中):

func BenchmarkQuicksort(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Quicksort([]int{5, 3, 4, 2, 1})
    }
}

基準測試的結果為:

基準測試

相關文章