Open1

helmで単体テスト

lhanlhan

https://event.cloudnativedays.jp/cicd2023/talks/1767
をみて、helmでの単体テスト、ほしかったものや!terratestいいやん!と、goで実装して、よし、いい感じ、なるほどなるほど、結構簡単に実装できるなあ、とりあえず別のツールがないかhelm unittestで検索してみようで見つけてしまったもの
https://github.com/helm-unittest/helm-unittest
利用しようと思っていたユースケースが一般的すぎて、yaml数行で上位互換の機能がすでに実装済みであることを見つけたときの悲しさ…、にまさる独自実装しなくて済んだ喜び

TDDで開発されていない限り、テストでほしいものは以前できたことからの差分なはず。helmチャートの場合、変更を加えるのは新しい引数を付けたいとかであることが多い。すると簡単に過去のマニフェスト生成を取れて、そこから意図した結果のマニフェストに変更することは人間に優しい。あとは期待したマニフェストを生成できるチャートとなるように変更を加えるという作業が一般的

helm-unittestを使うとこのフローを簡単に回せて、かつ意図しない変更をしていないことを確認できる

試しにnginx-ingress( https://github.com/nginxinc/helm-charts )で出力したマニフェストが意図した出力になるようにvaluesを安全に変更するフローを見てみる

過去のスナップショットを取って比較するための設定ファイル。これがhelm-unittestをインストールする以外に唯一必要な仕掛け

suite: test all
templates:
- controller-configmap.yaml
- controller-deployment.yaml
- controller-service.yaml
- controller-serviceaccount.yaml
- rbac.yaml
tests:
- it: should work
  asserts:
  - matchSnapshot: {}

-uでデフォルトの設定時のスナップショットを取る。スナップショットは__snapshot__フォルダに保存される

% helm unittest -u .

### Chart [ nginx-ingress ] .

 PASS  test all	tests/all_test.yaml

Charts:      1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshot:    6 passed, 6 total
Time:        103.507667ms

スナップショット側を先に書き換えて、意図した出力にする。例としてipv6を無効化してみる

% diff tests/__snapshot__/all_test.yaml.snap tests/__snapshot__/all_test.yaml.snap.org
69c69
<                 - -disable-ipv6=true
---
>                 - -disable-ipv6=false

テストを走らせると変更(テストケースとの差分)が検知される

% helm unittest .

### Chart [ nginx-ingress ] .

 FAIL  test all	tests/all_test.yaml
	- should work

		- asserts[0] `matchSnapshot` fail
			Template:	nginx-ingress/templates/controller-deployment.yaml
			DocumentIndex:	0
			Path:
			Expected to match snapshot 2:
				--- Expected
				+++ Actual
				@@ -54,3 +54,3 @@
				             - -include-year=false
				-            - -disable-ipv6=true
				+            - -disable-ipv6=false
				             - -enable-tls-passthrough=false


Snapshot Summary: 1 snapshot failed in 1 test suite. Check changes and use `-u` to update snapshot.

Charts:      1 failed, 0 passed, 1 total
Test Suites: 1 failed, 0 passed, 1 total
Tests:       1 failed, 0 passed, 1 total
Snapshot:    1 failed, 5 passed, 6 total
Time:        137.791375ms

サンプルの変更ではチャート自体への修正は必要なく、valuesファイルを設定すれば良い。のでそのとおりに変更する

% diff values.yaml values-test.yaml
417c417
<   disableIPV6: false
---
>   disableIPV6: true

変更したvaluesファイルを食わせて、再度テストを走らせて意図した出力が出ていることを確認する

% diff tests/all_test.yaml tests/all_test.yaml.org
10,11d9
<   values:
<   - ./values-test.yaml

% helm unittest .

### Chart [ nginx-ingress ] .

 PASS  test all	tests/all_test.yaml

Charts:      1 passed, 1 total
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshot:    6 passed, 6 total
Time:        109.755209ms

あまりにも便利すぎて涙した

最後のフォルダ構造はこんな感じ

% tree tests
tests
├── __snapshot__
│   ├── all_test.yaml.snap
│   └── all_test.yaml.snap.org
├── all_test.yaml
├── all_test.yaml.org
└── values-test.yaml

一般的にhelmチャートにおいてvaluesの一覧とにらめっこして結果出力されるマニフェストを人間側で想像する。しかし、往々にしてドキュメントが古かったり解釈の違いで、意図しないマニフェストが出力されたり、思わぬ副作用をもらったりする

上記のフローで変更をすれば、先に生成されるべきマニフェストファイルを得られるため、あとからつじつまを合わせることができる。ぱっと思いつくユースケースとしてClusterRole/ClusterRoleBindingで書かれたチャートをRole/RoleBindingで動かす際にもっと楽にできそう。日々のチャートの変更も安心して実施できることだろう