JWKライブラリを作成!8


8篇:あ...一番疲れた時間..。デバッグとユニットテスト(一部)


六編から事実ライブラリが作成されました.
しかし、実際には、コードを書くだけで正常に動作するかどうかはまだ確定していなかった.
もちろん、おおよその機能がうまく機能しているかどうかは確認していますが、私が見たJWKの中で最も代表的なGoogle APIで使われているhttps://www.googleapis.com/oauth2/v3/certsの中で、うまく機能していることを確認しました.
ただし、場合によっては正常に動作しているだけで、ライブラリに問題がないと判断することはできません.
そのため、賢い開発者はこのような考えを持っています.
ライブラリを小さなユニットに分けて各ユニットをテストすると、ほとんどのエラーがフィルタリングされる可能性がありますか?
やっぱり頭がいいだから私も賢い人が設計した単位テストを導入して私のライブラリを検証しました.
厳密に単位テストを行うだけでは、オブジェクトを作成して実行する必要がありますが、私はそれをできません.以前の単位テストで合格した部分が100%信頼されていると仮定して、単位テストコードを作成します.

Go言語でのユニットテストとコードオーバーライド



オープンソースライブラリで上記のラベルを見たことがありますか?
ここのプロジェクトはそのプロジェクトの一部を満たしています.同じゲームにとって、これは挑戦課題です.
ここで私が話したい内容はcodecovです.
コードオーバーライド率は、테스팅의 결과가 코드들을 얼마나 잘 확인하고 있는가?を示す指標の1つである.
テストを行うとき、テストを行うツールはソースコードとテストコードを分析し、テスト中に通過したソースコードの行数を測定します.
次いで、コードオーバーライド率は、코드 커버리지 = 테스팅 중 거쳐간 코드 라인 / 전체 코드 라인の計算式によって最終的に計算される.
このようにオーバーライド率を測定すると,テストコードを作成する際に,未確認の部分があるか否かを視覚的に確認できる.

上記の形式でコードオーバーライド率の結果を得た.
この結果については,テスト中に一度も確認しなかった部分を赤,テスト中に確認した部分を青で表した.
すなわち、被覆率は푸른색으로 표시된 줄 / (푸른색 줄 + 붉은색 줄)である.
実際、codecovはコードオーバーライド率を測定するためのツール名です.
コードオーバーライド率は様々な方法で測定できるが、最も代表的なのはソースコードを行として分析するコードオーバーライド率である.上に説明した部分は線で覆われています.

単位テストの作成


実際、今貼っているテストはまだ終わっていません.だから文章のテーマはテストです.
テストが完了したら、それを一つ一つに縛ります.
では、どのようにテストを行うかというと、まずGo testをコードオーバーライド率をテストする方法として使用します.
このツールはgo言語が独自に提供したテストとコードオーバーライド率測定用のツールで、他の外部ツールを使用することなくコードオーバーライド率を測定できます.
go testはgo test <테스트할 대상>形式で実行できます.
go言語で新しいライブラリをダウンロードする場合は、go get <라이브러리 주소>形式でダウンロードするのと同じです.
すべてのコードを一貫したオーバーライド率で記述するには時間がかかるため、まずこのブログでは、sortkey.goという内部名のキーワードをソートするための簡単なファイルのみを作成します.
まず,テスト全体コードを以下に示す.
func TestSortkey(t *testing.T) {
	t.Run("name is different", func(t *testing.T) {
		data := []jwk.Key{
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "D"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "B"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "C"}, KeyType: jwk.KeyTypeOctet},
		}
		ids := make([]string, len(data))
		for i, k := range data {
			ids[i] = k.Kid()
		}
		sort.Strings(ids)
		jwk.SortKey(data)
		for i, v := range ids {
			if data[i].Kid() != v {
				t.Fatalf("must be data[%d] == '%s'", i, v)
			}
		}
	})
	t.Run("name is same, but different type", func(t *testing.T) {
		data := []jwk.Key{
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeRSA},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeEC},
		}
		jwk.SortKey(data)
		if data[0].Kty() != jwk.KeyTypeEC {
			t.Fatalf("must be data[%d].Kty() == '%s'", 0, jwk.KeyTypeEC)
		}
		if data[1].Kty() != jwk.KeyTypeRSA {
			t.Fatalf("must be data[%d].Kty() == '%s'", 1, jwk.KeyTypeRSA)
		}
		if data[2].Kty() != jwk.KeyTypeOctet {
			t.Fatalf("must be data[%d].Kty() == '%s'", 2, jwk.KeyTypeOctet)
		}
	})
	t.Run("name and type mixed", func(t *testing.T) {
		// A(oct), B(oct), C(EC), C(oct), D(oct), E(EC), E(RSA), E(oct)
		data := []jwk.Key{
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeEC},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "C"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeRSA},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "D"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "B"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "C"}, KeyType: jwk.KeyTypeEC},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeOctet},
		}
		expected := []jwk.Key{
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "A"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "B"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "C"}, KeyType: jwk.KeyTypeEC},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "C"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "D"}, KeyType: jwk.KeyTypeOctet},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeEC},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeRSA},
			&jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: "E"}, KeyType: jwk.KeyTypeOctet},
		}
		jwk.SortKey(data)
		for i, k := range expected {
			if data[i].Kid() != k.Kid() {
				t.Fatalf("data[%d] must be kid = '%s'", i, k.Kid())
			}
			if data[i].Kty() != k.Kty() {
				t.Fatalf("data[%d] must be kty = '%s'", i, k.Kty())
			}
		}
	})
	t.Run("random generated 1.5M, mixed test", func(t *testing.T) {
		const GENERATED_LENGTH = 1_500_000
		const ID_MINLEN = 2
		const ID_MAXLEN = 4
		var TYPE_ONEOF = []jwk.KeyType{jwk.KeyTypeOctet, jwk.KeyTypeEC, jwk.KeyTypeRSA}
		var KTY_TO_INT_TABLE = map[jwk.KeyType]int{
			jwk.KeyTypeEC:    -3,
			jwk.KeyTypeRSA:   -2,
			jwk.KeyTypeOctet: -1,
		}
		var CHARSET = []rune("abcdefghijklmnopqrstuvwxyz")
		gen_id := func(length int) string {
			result := make([]rune, 0, length)
			for i := 0; i < length; i++ {
				result = append(result, CHARSET[rand.Intn(len(CHARSET))])
			}
			return string(result)
		}

		keys := make([]jwk.Key, 0, GENERATED_LENGTH)
		ids := make([]string, 0, GENERATED_LENGTH)
		for i := 0; i < GENERATED_LENGTH; i++ {
			kid := gen_id(ID_MINLEN + rand.Intn((ID_MAXLEN-ID_MINLEN)+1))
			kty := TYPE_ONEOF[rand.Intn(len(TYPE_ONEOF))]
			keys = append(keys, &jwk.UnknownKey{BaseKey: jwk.BaseKey{KeyID: kid}, KeyType: kty})
			ids = append(ids, kid)
		}
		sort.Strings(ids)
		jwk.SortKey(keys)
		for i := 0; i < GENERATED_LENGTH; i++ {
			if ids[i] != keys[i].Kid() {
				t.Fatalf("expected data[%d].Kid() is '%s', but got '%s'", i, ids[i], keys[i].Kty())
			}
			currID := keys[i].Kid()
			currTYP := KTY_TO_INT_TABLE[keys[i].Kty()]
			for j := i; j < GENERATED_LENGTH && keys[j].Kid() == currID; j++ {
				jTYP := KTY_TO_INT_TABLE[keys[j].Kty()]
				if currTYP > jTYP {
					t.Fatalf("from data[%d] to data[%d], data[%d].Kty() is '%s', but data[%d].Kty() is '%s'", i, j, i, keys[i].Kty(), j, keys[j].Kty())
				}
				if jTYP > currTYP {
					currTYP = jTYP
				}
			}
		}

	})
}
ここで、TestSortKeyの下には、name is differentname is same, but different typename and type mixedrandom generated 1.5M, mixed testの4つの試験例がある.
各要素はSortKeyをテストする様々な方法の一部であり、まずなぜコードをテストするのか.聖書について話すには、まずSortKeyから話すべきだ.SortKeyは、[]Keyと入力され、すなわち、キーの配列を受け入れ、その配列を並べ替える関数である.
これはGoがGenericをサポートしていないため、ソートのためにsort.Interfaceを実装するタイプを直接宣言しなければならないが、このタイプには何の問題もないため、ライブラリ内の対応するタイプは非表示になっているが、この機能を利用可能にするために関数が定義されている.
これを定義するのは、キーをソートするときに2つの要素が必要だからです.
まず、鍵はkidと呼ばれる鍵のIDにより辞書形式で並べ替えられる.(大文字と小文字を区別する)その後、鍵のkidが同じである場合、ktyと呼ばれる追加の部分によって追加のソートが必要となる.
このときktyはstringタイプであるが、辞書順ではなく"EC", "RSA", "oct"の順に並べ替えるべきである.
次に、各テストケースについて説明します.

鍵に異なる名前がある場合、name is distive


このテストでは、名前が異なることを確認します.
上記のテストでは、身長はいずれもoctであり、同じタイプのkidであり、AがそれぞれBCDの場合、ソートが正しいかどうかを検証した.
テストの確認は,鍵のみを収集したstring配列の結果と比較して確認する.

キーの名前は同じですが、タイプによっては同じ名前ですが、タイプによって異なります。


このテストでは、キー名が同じでタイプが異なる場合に、正しく位置合わせされていることを確認します.
すなわち、上記の場合、{kid:"A", kty : "oct"}{kid:"A", kty : "RSA"}{kid:"A", kty : "EC"}ECRSAoctの順に正しく配列されていることを確認する.

name and type mixedは、鍵の名前とタイプを決定するために使用されます。


このテストでは、名前とタイプが異なる場合を決定します.
以上の2つのテストキャビネットが共存している場合は、出力が正しいことを確認してください.

ランダムに生成された1.5 M混合テストで、100万個のテストケースを動的に作成できます。


このテストでは、プロジェクトが必要に応じてソートされているかどうかを確認するために、100万件のテストケースが作成されます.
ここではなぜ百万個のテストケースを作成するのか、このテストではIDは[A-Z]{2,4}の形式を持つ正規表現で表されています.
このときのすべての可能な状況の数は26^2 + 26^3 + 26^4である.475228です.
したがって,鍵がランダムに生成されても鍵タイプは3つあるため,475228 * 3 = 1425684個であるため,1.5百万個であれば,単独のテストケースを作成しなくても,可能な限りの数が発生する可能性がある.

テストコードの確認



最終的にこのコードを実行すると、次のように表示されます.
参考として、2.5%のコードオーバーライド率が現れ、実際にはこれよりも多くのテストコードがあるため、2.5%が現れた.今はできなくても95%に引き上げるのが私のやるべきことです.
コードオーバーライド率とユニットテストは、オープンソースライブラリでよく見られる*_test.go形式のファイルです.これは何のファイルですか.調査で分かった.
その後、大学のソフトウェア工学概論の授業で、これが何なのか詳しく知りました.
個人的にコードオーバーライド率と単位テストを知らないとき、私は本当にやっていますか?よくこんな疑問があります.
しかし、コードオーバーライドとユニットテストを学ぶ過程で、少なくとも私が気づかなかったコード部分はありません!それがとてもいいと確信させられました.
今、ある程度のユニットテストコードが完成した後、文章を書き、文章の使い方をまとめ、CI/CDにGithub動作を適用したいと思います.
もちろん前にアップロードしたことがありますが、自分の考え通りに书いたものは一つもありません.大体の意味です.