這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
GO TEST
如果提供一個庫,或者提供了一個模組,如何給使用者描述這個API的用法?一般都是給文檔,但是文檔非常容易不同步,在golang中,有更進階的做法,也是最好的做法。
以go-fdkaac為例,這個是一個go binding,調用了lib-fdkaac的c函數,提供aac的codec的一個庫。
首先,由於用靜態庫的方式引用,涉及到了如何下載fdkaac的c代碼和編譯,需要在doc中說明,也就是README.md中說明Usage:https://github.com/winlinvip/go-fdkaac#usage
在Usage中,準備好代碼和環境後,就只需要連結到使用的examples:
而這個使用說明本身就是utest,有期望值,可以運行:
winlin:go-fdkaac winlin$ go test ./...? github.com/winlinvip/go-fdkaac [no test files]ok github.com/winlinvip/go-fdkaac/dec 0.008swinlin:go-fdkaac winlin$
文檔變成了可以執行的代碼,或者說,沒有了經常變化的文檔,只有代碼,golang這點做得真是perfect!
而example的package,和API的package是不一樣的。這個和utest是不一樣的,utest因為要訪問package內部的內容,所以和API同一個package,比較方便。example因為是給使用者用的例子,當然和API的package不能一樣,也不可能一樣。
GOLANG的Example,強迫API設計者,能從User角度出發,將API設計得很簡單易懂,因為複雜的API不好用在寫Example時很容易就發現了:
package dec_testimport ( "fmt" "github.com/winlinvip/go-fdkaac/dec")func ExampleAacDecoder_RAW() { var err error d := dec.NewAacDecoder() asc := []byte{0x12, 0x10} if err := d.InitRaw(asc); err != nil { fmt.Println("init decoder failed, err is", err) return } defer d.Close() // directly decode the frame to pcm. var pcm []byte if pcm,err = d.Decode([]byte{ 0x21, 0x17, 0x55, 0x35, 0xa1, 0x0c, 0x2f, 0x00, 0x00, 0x50, 0x23, 0xa6, 0x81, 0xbf, 0x9c, 0xbf, 0x13, 0x73, 0xa9, 0xb0, 0x41, 0xed, 0x60, 0x23, 0x48, 0xf7, 0x34, 0x07, 0x12, 0x53, 0xd8, 0xeb, 0x49, 0xf4, 0x1e, 0x73, 0xc9, 0x01, 0xfd, 0x16, 0x9f, 0x8e, 0xb5, 0xd5, 0x9b, 0xb6, 0x49, 0xdb, 0x35, 0x61, 0x3b, 0x54, 0xad, 0x5f, 0x9d, 0x34, 0x94, 0x88, 0x58, 0x89, 0x33, 0x54, 0x89, 0xc4, 0x09, 0x80, 0xa2, 0xa1, 0x28, 0x81, 0x42, 0x10, 0x48, 0x94, 0x05, 0xfb, 0x03, 0xc7, 0x64, 0xe1, 0x54, 0x17, 0xf6, 0x65, 0x15, 0x00, 0x48, 0xa9, 0x80, 0x00, 0x38}); err != nil { fmt.Println("decode failed, err is", err) return } fmt.Println("SampleRate:", d.SampleRate()) fmt.Println("FrameSize:", d.FrameSize()) fmt.Println("NumChannels:", d.NumChannels()) fmt.Println("AacSampleRate:", d.AacSampleRate()) fmt.Println("Profile:", d.Profile()) fmt.Println("AudioObjectType:", d.AudioObjectType()) fmt.Println("ChannelConfig:", d.ChannelConfig()) fmt.Println("Bitrate:", d.Bitrate()) fmt.Println("AacSamplesPerFrame:", d.AacSamplesPerFrame()) fmt.Println("AacNumChannels:", d.AacNumChannels()) fmt.Println("ExtensionAudioObjectType:", d.ExtensionAudioObjectType()) fmt.Println("ExtensionSamplingRate:", d.ExtensionSamplingRate()) fmt.Println("NumLostAccessUnits:", d.NumLostAccessUnits()) fmt.Println("NumTotalBytes:", d.NumTotalBytes()) fmt.Println("NumBadBytes:", d.NumBadBytes()) fmt.Println("NumTotalAccessUnits:", d.NumTotalAccessUnits()) fmt.Println("NumBadAccessUnits:", d.NumBadAccessUnits()) fmt.Println("SampleBits:", d.SampleBits()) fmt.Println("PCM:", len(pcm)) // Output: // SampleRate: 44100 // FrameSize: 1024 // NumChannels: 2 // AacSampleRate: 44100 // Profile: 1 // AudioObjectType: 2 // ChannelConfig: 2 // Bitrate: 31352 // AacSamplesPerFrame: 1024 // AacNumChannels: 2 // ExtensionAudioObjectType: 0 // ExtensionSamplingRate: 0 // NumLostAccessUnits: 0 // NumTotalBytes: 91 // NumBadBytes: 0 // NumTotalAccessUnits: 1 // NumBadAccessUnits: 0 // SampleBits: 16 // PCM: 4096}
這個API設計時,最初是和fdkaac一樣,有Fill([]byte)然後是Decode(),分成了兩步,可以多次Fill。後來發現這個實在不好用,不如直接Decode([]byte),可以用Decode(nil)進去。