なんてないこと

これは Kyash Advent Calendar 2025 の11日目の記事です。

またどうも。今年はアドベントカレンダーブログを2回書くことになってしまいまして、なにかこうかなぁと考えていました。 前回 はお仕事よりの話しをしたので、今回は個人的なことを書いてみようかなと思い至りました。

私は普段のお仕事では、macbook proを使わせてもらっているのですが、個人のPCはmacbookを使っていません。15年ぐらい前はWindowsを使って開発を行っていました。その頃はだいぶエンジニアとして業務をこなせるぐらいになり、自社開発を行っている会社に初転職したこともあり、気持ち的にも余裕ができてきた頃で、技術的なことに興味が向くようになってきた頃でした。色々な技術イベントに参加したりして、30歳ぐらいの頃ですかねぇ〜。

いつものようにEclipseIntelliJだったかな?)を開いて HHKB Lite2 のキーボードをカチャカチャ叩きながらコーディングし、 Tera term を起動して、サーバーにログインして色々ログ見たり、設定ファイルを修正していたのですが、ふと、お隣のお師匠のモニターを覗くと何やら全体的に紫色のターミナルを操作していました。「なんすかそれ??」って聞いてみたら、どうも彼はWindowsVirtualBox をインストールして、 Ubuntu Desktop を操作していたんですね。んで、Gnome terminalでサーバーにログインして作業していたんですね。それを見たとき、私は割りと衝撃をうけまして、なんでわざわざWindowsがあるのにLinux使ってますのん?なんかええことあらはりますのん?みたいに質問攻めしたのを覚えています。聞くと、彼の大学時代、研究室ではWindowsあまり触る機会がなく、自然にLinuxを扱うようになったこと、Windowsだとbashが使えないのでサーバーで作ったツールが使えないのが不便なこと、最近は支給されるPCのマシンスペックが上がって仮想環境でも十分X Window Systemが動くことなどを理由にUbuntu Desktopをメインで使っているとのことでした。Windowsで使うのはOffceソフトぐらいだから、Virtural BoxにPCのリソースをできるだけぶち込んで、快適に作業されているようでした。とはいえ、このような使い方は割りと変則的で、Windowsを消してLinuxのみで使っている人を見たことがあるけど、そういう変態的なことではないんだよ〜と優しく教えてくれました。

その頃の私はJavaのコーディングはこそそこそこできたものの、サーバーを1から立ち上げてApacheTomcatをつなげてHAProxyでロードバランサーを設置するみたいなインフラも担当していて、その分野には疎く、彼に教えを請う機会が多くありました。もちろん、今のようにAWSGCPなどのクラウドサービスはまだなく、必要があればデータセンターに赴いてラックの設置などもやっていました。そこそこコーディングはできると自信はあったのですが、彼のそれは段違い(大学院でJVMの研究をやってたので当たり前)だったし、サーバーアプリケーションがどのような仕組みで動いているのかをはっきり理解していたので、自分の不勉強さをしみじみと感じていました。そういう経緯もあり、彼の真似をすることにして、私もVirtual BoxにUbuntu Desktopを入れて普段使いするようになりました。

とはいえ、はじめから不便なく使えたわけではなく、様々な問題にぶち当たって色々調べながら教えてもらいながらですが。その頃は多分Ubuntu 12.04 LTSとかを使っていたんじゃないかな。Ubuntu Unityが話題となっていたことを記憶しています。当時から英字キーボードを使っていたのはあまり良くなかったかもしれないですね。OSは日本設定なのにキーボードはUSってあんまり情報がなかったんですよね。しかも仮想環境上の話しなので。そうやって不便だなって思いながらUbuntuを使っていて、それでもそろそろ使い慣れてきた頃、巷ではmacOSが流行していました。どうも今までのCPUではなく、Intel製のCPUにmacOSが乗っているものらしく、Web系の人はこぞって使っていたのを思い出します。私はまぁ、普通に使いやすいと感じていたUbuntuを使い続けていたのですが、流行に敏感なお年頃ですからね。mac使いたくなります。その頃のPMにお願いして、macbook airを支給してもらうように手配してもらいました。これが私のmac業務利用デビューでした。10年ぐらい前の話ですかね。33歳頃?

pc.watch.impress.co.jp

それまでは自宅で中古で手に入れたThinkpad X201を使っていたのですが、清水の舞台から飛び降りる覚悟で新品のmacbook air 11を購入し、個人PCもmacになりました。macは使いやすく、その後10年ほどmacを使い続けました。そして今です。仕事でも個人でもmacを使っていて、なんだか面白くないなぁと不思議と考え始めました。あの頃、死ぬ気で買ったmacbook air 11ちゃんは大きさといい軽さといい、お気に入りだったのですが、今やだいぶ前にOSの脚切りに合い、えーPC買い替える〜?みたいな葛藤を3年ぐらい思い悩んでいました。 近頃はiOSアプリやUnityゲームの開発もご無沙汰だし、家でやることといえばネットサーフィンかラズパイいじるぐらい。お家k8sとかもやりたいしなぁ。多分macじゃなくても困らないんだよね。高いし。11インチないし。

生活環境も変わったので、自分の趣味に何十万もかけたくない。とはいえ、サポート切れたPC使い続けるのもどうかと思い、はじめは手持ちのmacbook airをどうにかして使い続ける方法はないか模索しました。11インチという、今ではもはや手に入らない機械ですが、私にとっては自宅で作業するには今でもベストな機械です。アルミニウム製のせいか、大きさのわりに少々重いのが玉に瑕で、すでに充電器であるMagSafe2は断線してしまって前のPCで使っていたものに変換コネクタを挟んでギリギリ充電している状態ですが、それでも手放すには惜しい機械です。初めて買った新品の製品で、USキーボードにカスタマイズしたものなので、思い入れが思った以上にあるんでしょうね。色々調べてChromeOS Flexを知ります。ChromeOS Flexは起動時間が短く、体験がとても良かったのですが、ネットブラウジングしかできませんでした。ちょっとコード書きたいんだけどなと思ったときに不便に感じ、それなら昔使っていたUbuntuを入れてみようと思い、今度はUbuntuをインストールしてみました。これがなかなか使いやすく、僕のmacbook airちゃんでも軽快に動きます。GNOME Terminalも十分動くし、snapから色々アプリをインストールでき、普段使いにはとても重宝しました。ちょっと前と比べたら全然使いやすいOSになっていて、びっくりしました。

chromeos.google

でもですね。でもなぜでしょう。。アルミ製のmacの機械にLinuxがある不自然さ。言うなれば、 マイセン のような高級なお皿に豆腐のみがのせられている不自然さとでもいうのでしょうか。いや、冷奴大好きなんですが、なんでしょうね。なんか不自然さを感じてしまったのです。起動時にちょっとだけ出るログも、macには似合いませんでした。でも、Ubuntuのデスクトップ環境はリッチで使いやすく、JetBrains製品やUnity、Dockerも快適に使えます。なんだろうな。これはもう、Certificated Thinkpad変えってことかなと感じる心を禁じえませんでした。

ubuntu.com

ということで、ThinkPad X1 Carbon gen 8をメルカリで買いました。5万5千円ぐらいだったかしら。i7が搭載されていて、16GBのRAMが刺さっています。この子を気に入ったポイントは、WQHD液晶だった点です。今までRetinaディスプレイではないものの、きれいなデスクトップ環境であるmacbook airを使っていて、Ubuntuを久々に触ったときに感じたリッチなデスクトップなら、きれいなディスプレイが搭載されているもののほうが満足度が高いのではないか、高解像度でUbuntuを表示させたかった感じですね。512GBのSSDは少し物足りなかったので、それもメルカリでSamsungの1TBのSSDを7千円ぐらいで買ってトータル6万円ぐらいですかね。思った以上に満足しています。今なら初めっから買っておけばよかったと感じていますが、筐体はボロボロでもいいけど、値段が手頃(MAX6万ぐらいかなー。8万円は出したくない)でそれなりに新し目のThinkPadを探すのに時間がかかってしまいました。

で、新たな相棒であるThinkPad X1 Carbonを快適に使っていたのですが、どうもキーボードがおかしい。 sudo apt update を実行して正しいパスワードを入力しても間違えていますとなります。パスワードは確かにあっているはずですが、どうしてもうまくいきません。ブラウザで症状を検索しようにも、テキストエリアにキーボード入力しようとしたところ、一文字入力直後に全角半角が高速で切り替わり、正しく文字を打てないことに気が付きました。OSにログインするときはそんなことないので、はじめはPCは悪くなく、Ubuntuが変になっちゃっているのかなと考え、ChatGPTに聞いてOSを再インストールしたり、IBusを再起動するスクリプトを用意したり裏蓋開けて掃除したりしましたが、どうしても治りませんでした。うーん、これは困ったと思ったのですが、中古で購入したのでLenovoサポートも切れてるしなぁ。そこでThinkPad専門の修理屋さんに症状を聞いてもらおうと思い電話したところ、その症状ならキーボード交換したらたいてい治るとのことでした。そのままの日本語キーボード交換なら3万円弱、英語のキーボード交換することも可能だけど、部品がないから倍ぐらいの値段になると。

この頃になると、薄々キーボード交換したら治るのかなって正直思っていて、キーボード交換するなら英語キーボードに変えたいなと思っていました。ただ、薄いThinkPad X1 Carbonのキーボード交換する自信はなく、多少お金がかかっても交換作業は業者の方にお願いしようと考えていたのですが、倍かぁ〜。その金額は多分このPCを買ったときの値段に勝るとも劣らない値段よ。ということを、ChatGPTに相談したらThinkPadはマニュアルが充実しているし、キーボードを交換してる動画や記事もあるし、できるんちゃう?やってみたら?みたいに推してきます。なら肝心のキーボード部品あるのかしらと思って調べてみたのですが、eBuyで$80ぐらいでありました。eBuyで商品を購入するのが初めてだったので、またまたChatGPTに相談したところ、eBuyは購入者の評価をよく見たほうがいい、少なくとも好評価4以上ついてたほうがええし、ネガティブなコメントがついてない出品者のほうがええでって言われました。私が探した商品は好評価であるものの、ネガティブなコメントも若干あったようで、ThinkPartsっていうわりと信用できるショップもあるでって教えてくれました。

www.thinkparts.com

$80は格安だったものの、交換してうまくいかなかったら立ち直れないかも・・・。eBuyでやり取りするのも初めてだし、悩んだ末にThinkPartsさんで$120ほどで英語キーボード交換パーツを購入しました。これで結局、ThinkPadにかけたお金が9万円ほどになってしまいました。当初の予定の予算では6万円程度だったので、オーバーしてしまいましたが、これ、実は1年半ぐらい悩んで試行錯誤してそれでも治らなくて悩み抜いた挙げ句にやっと年末に出した結論でした。(ThinkPadを買おうと決めるまでも2年ぐらいかかってる)交換作業はそれなりにドキドキしましたが、なんとか交換することができて良かったです。かれこれ15年近く英語キーボードを使っているので、 @ の位置が2の上じゃないと困るし、 [] が横並びになっていないと打ち間違える確率がかなり高いんですよね。

1点困ったことは、私が購入したパーツは部品番号(FRU)はあっているのですが、WWAN版のもので互換性が少々異なるようで、裏蓋を閉めたときに蓋がうきました。それ以外、指紋認証パーツやマザーボード、ディスプレイパーツはバッチリ合ったので、薄くて軽くてスマートなThinkPadの外観を損なってしまっているように見えました。隙間を見たり、既存のキーボードパーツと見比べてみると、マザーボードを固定させるための突起が新しいパーツでは少々長く、その突起が裏蓋に干渉して浮かせているようでした。これをニッパーで切断してヤスリで整えたら裏蓋がちゃんと閉まったので、OKとします。


ということで、私のパソコン事情を書いてみました。ものすごい個人的な営みだし、誰の役にも立たない内容になってしまって少々恥ずかしいです。ただ、私個人としては、1つのパソコンで1年以上遊んでいて、とっても楽しいです。昔、無いお金をなんとか捻出してApple Storeでカスタマイズして買ったmacbook air 11もだいぶお気に入りなのですが、今回久々に手に入れたThinkPadもかなり気に入っています。手をかけられる子なので、またなにか不具合が起きることに若干期待してしまっている自分がいるのが不思議です。なお、お気に入りのmacbook air 11ですが、OpenCore Legacy Pacherというツールを使ってSonomaが動いています。やっぱmacの機械にはmacOSが動いているのがふさわしいですね。

github.com

以上です。特にこれといって得する情報はなかったですが、多分、私はプログラミングが好きなんだけど、プログラミング以上にパソコンが好きなんだろうな〜っていうことが分かったような気がします。来年はなにしようかな〜。Kubernetesの本が気になってるんで、ラズパイ買ってクラスタ組んで遊びたいな。

https://www.amazon.co.jp/dp/B0CVQCNN6W

明日の投稿もぜひお楽しみに。バイバーイ。

やり残していたことの答えっぽいもの

これは Kyash Advent Calendar 2025 の6日目の記事です。

年の瀬ですね。今年は おかあさんといっしょファミリーコンサート に行ってきまして、ゆういちろうおにいさんの歌唱を生で見て感動したのが印象的でした。毎朝見慣れているうたのおにいさんおねえさん、たいそうのおにいさんおねえさんですが、ステージ上の彼らは煌びやかで、感動的でした。

ステージといえば、今年は Kyash TechTalk #8 - スポットマネー開発の裏側 という、弊社主催のイベントのスピーカーとして壇上に立って発表してきました。おにいさん、おねえさんのように、うまく立ち回ることができず、また久々の登壇機会だったのでしどろもどろしてしまいましたが、なんとか体裁だけは保てたように思います。

speakerdeck.com

この資料の以下のページでも話してきたのですが、今回のブログは そんなにうまいことできていない ことがテーマです。

業務の都合上、ExcelAPI仕様書をPDFに変換し、Geminiを使ってOpenAPI形式のyamlファイルを生成したのですが、これがなかなか手間のかかる作業でした。当時はブラウザでGeminiにアクセスし、PDFファイルをアップロードしてAIに指示(Prompt)を出してYAMLを表示してもらってコピーしてローカルファイルにペーストしてGitにPushする流れでした。 作業の流れはPromptの内容がキモだったし、そこがみんなでワイワイできるポイントだったのですが、それ以外の作業であるアップロードやファイルコピー、Git操作がわりと面倒で、Promptの再現性を保つためにスイッチングコストが結構ばかにならないと感じていました。

また、単発的にPromptを何度も試したので、再現性を保てず、前回はどういう工夫をして、それのなにが良かったんだっけ?みたいなのが抜け落ちるのをもったいなく思っていました。

スライドにも書きましたが、CLIツールを利用したほうがタイプ量も減るし、私には合っている気がしていました。また、GitHub ActionsにできたらPull Requestを複数人でレビューできるので、より簡単で再現性を保てるのではないかと考え、年の瀬の駆け込みでActionsを作ってみました。


作ったActionsは、リポジトリのdocs配下にあるPDFファイルを読み込んで、AIに解析してもらい、それを元に生成したyamlファイルをコミットしてPull Requestを作る機能をつけました。個人的にAPI Keysが取得しやすかったので、AIはClaude Sonnet 4.5にしました。 また、push毎にActionsを実行してトークンを不用意に使いすぎてしまうことを恐れ、 workflow_dispatch で手動実行することにしました。

name: Generate YAML from Claude

on:
  workflow_dispatch: 

jobs:
  generate-claude:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:

      # 省略

      - name: Install Python dependencies
        run: |
          pip install -r .github/scripts/requirements_generate_claude_from_pdf.txt

      - name: Generate Claude YAML from PDFs
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
        run: |
          python .github/scripts/generate_claude_from_pdf.py

      - name: Create Pull Request
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # Git Configuration
          git config --global user.email "kyash-bot@kyash.co"
          git config --global user.name "kyash-bot"

          # Create new branch name
          BRANCH_NAME="auto-generate-claude-$(date +%Y%m%d-%H%M%S)"

          # Checkout new branch
          git checkout -b "$BRANCH_NAME"

          # Add generated file to git
          git add docs/openapi.yaml
          git commit -m "Generate OpenAPI YAML from PDF specifications

          This YAML file was automatically generated by Claude AI from PDF specifications in docs/spec/.
          
          Important: Please review the generated content carefully as it may contain errors.
          
          Generated with Claude Code"

          # Push branch to origin
          git push origin "$BRANCH_NAME"

          # Create Pull Request
          gh pr create \
            --title "Generate Claude YAML from PDFs API specifications" \
            --body "## Summary
            
            This PR contains an automatically generated OpenAPI YAML file created from PDF specifications in `docs/spec/`.
            
            ## Generated File
            - `docs/openapi.yaml`
            - OpenAPI 3.0.3 specification
            
            ## Source PDFs
            
            The following PDF files were processed:
            $(ls -1 docs/spec/*.pdf | sed 's/^/- /')
            
            ## Important Notes
            
            **This file was generated by AI (Claude)** - Please review carefully:
              
            - [ ] Verify all API endpoints are correct
            - [ ] Check request/response schemas
            - [ ] Validate parameter types and constraints
            - [ ] Confirm field descriptions are accurate
            - [ ] Review error response definitions
            
            ## Next Steps
            
            1. Review the generated YAML file
            2. Make any necessary corrections
            3. Approve and merge if satisfied
            
            Generated with [Claude Code](https://claude.com/claude-code)"
#!/usr/bin/env python3

# 省略

def extract_api_spec_from_pdf(client: anthropic.Anthropic, pdf_path: str, pdf_name: str) -> Dict[str, Any]:
    prompt = """このPDFファイルは日本語で書かれたAPI仕様書です。
このAPI仕様書を分析して、OpenAPI 3.0.3形式のYAML仕様の一部として出力してください。

以下の情報を抽出してください:
1. APIエンドポイント(パス)
2. HTTPメソッド(GET, POST, PUT, DELETEなど)
3. リクエストパラメータ(クエリ、パス、ボディ)
4. リクエストボディのスキーマ(該当する場合)
5. レスポンスの形式とスキーマ
6. 各フィールドの説明、型、制約(必須/任意、最大長など)
7. エラーレスポンスの定義

出力は以下の形式のJSONで返してください:

# 省略

重要な注意事項:
- 日本語の説明はそのまま維持してください
- データ型は適切なOpenAPI型(string, integer, number, boolean, array, objectなど)に変換してください
- 数値型の場合、整数はinteger、小数はnumberを使用してください
- 必須/任意の情報は required 配列に反映してください
- レスポンス例がある場合は examples に含めてください
- エラーレスポンスも responses に含めてください(400, 401, 500など)
- 出力は有効なJSONのみで、説明文は含めないでください
"""

    print(f"  Sending request to Claude API...")
    message = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        max_tokens=8192,
        messages=[
            {
                "role": "user",
                "content": [
                    {
                        "type": "document",
                        "source": {
                            "type": "base64",
                            "media_type": "application/pdf",
                            "data": pdf_data,
                        },
                    },
                    {
                        "type": "text",
                        "text": prompt
                    }
                ],
            }
        ],
    )

    # レスポンスからテキストを抽出
    response_text = message.content[0].text
    print(f"  Received response from Claude API")

    try:
        spec = json.loads(response_text)
        print(f"  ✓ Successfully parsed API spec")
        return spec
    except json.JSONDecodeError as e:
        print(f"  ✗ Error parsing JSON: {e}")
        print(f"  Response preview: {response_text[:300]}...")
        return None

def main():
    print("=" * 60)
    print("PDF to OpenAPI YAML Generator")
    print("=" * 60)

    # 環境変数からAPIキーを取得
    api_key = os.getenv('ANTHROPIC_API_KEY')
    if not api_key:
        print("❌ Error: ANTHROPIC_API_KEY environment variable is not set")
        sys.exit(1)

    # Claude APIクライアントを初期化
    client = anthropic.Anthropic(api_key=api_key)
    print("✓ Claude API client initialized\n")

    # PDFファイルのディレクトリ
    pdf_dir = Path('docs/spec')
    if not pdf_dir.exists():
        print(f"❌ Error: Directory {pdf_dir} does not exist")
        sys.exit(1)

    # PDFファイルを取得(.DS_Storeなどを除外)
    pdf_files = sorted([f for f in pdf_dir.glob('*.pdf') if not f.name.startswith('.')])
    print(f"Found {len(pdf_files)} PDF file(s) in {pdf_dir}\n")

    if not pdf_files:
        print("❌ Error: No PDF files found")
        sys.exit(1)

    # 各PDFからAPI仕様を抽出
    api_specs = []
    for i, pdf_file in enumerate(pdf_files, 1):
        print(f"[{i}/{len(pdf_files)}] Processing: {pdf_file.name}")
        try:
            spec = extract_api_spec_from_pdf(client, str(pdf_file), pdf_file.name)
            if spec:
                api_specs.append(spec)
                print(f"  ✓ Successfully extracted spec from {pdf_file.name}\n")
            else:
                print(f"  ⚠ Failed to extract spec from {pdf_file.name}\n")
        except Exception as e:
            print(f"  ❌ Error processing {pdf_file.name}: {e}\n")
            continue

    if not api_specs:
        print("❌ Error: No API specifications were extracted")
        sys.exit(1)

    print("=" * 60)
    print(f"✓ Successfully extracted {len(api_specs)} API specification(s)")
    print("=" * 60)

    # OpenAPI仕様にマージ
    print("\nMerging API specifications into OpenAPI format...")
    openapi_spec = merge_api_specs_to_openapi(api_specs)
    print(f"✓ Merged into single OpenAPI specification")
    print(f"  Total paths: {len(openapi_spec['paths'])}")

    # YAMLファイルとして出力
    output_file = Path('docs/openapi.yaml')
    output_file.parent.mkdir(parents=True, exist_ok=True)

    print(f"\nWriting to {output_file}...")
    with open(output_file, 'w', encoding='utf-8') as f:
        yaml.dump(openapi_spec, f, allow_unicode=True, sort_keys=False, default_flow_style=False)

    print("=" * 60)
    print(f"✅ SUCCESS: OpenAPI specification written to {output_file}")
    print("=" * 60)
    print("\n⚠️  重要: 生成されたYAMLファイルは必ずレビューしてください")
    print("   AIが生成した内容のため、誤りがある可能性があります\n")

if __name__ == '__main__':
    main()

上記のコードは大部分を端折っているので、そのままコピーしても動作しませんが、動作イメージは掴んでもらえるのではないでしょうか。promptという変数にClaude APIに指示するテキストを入れています。ファイルからプロンプト文字列を読み込ませたかったのですが、年末の駆け込み作業で時間をあまりかけられなかったため、そのまま埋め込んでいます。

なお、ぱっと見、絵文字があるしなんとなくおわかりだと思いますが、大部分をClaude codeに書いてもらいました。12月になって月が変わったので、なんとなくみんなが使っているClaude Proプランを契約してClaude codeに任せてみました。GitHub Actionsってあまり頻繁にいじらないので、 steps だっけ? jobs だっけ? name いるんだっけ?って毎回なっていたので雛形作ってくれるのはありがたかったです。また、Claude Code GitHub Actionsを使うのかなって思ってたんですが、anthropic-sdk-python で作ろうとしてたので、内容を見てそんな難しいことしてなさそうだったのでPython scriptを作ってもらいました。

github.com


イメージ的には以下のような使い方になります。 PDFのAPI仕様書をGit mergeして、Run workflowを押すとyamlファイルをcommitしたPull Requestが自動的にできて、メンバーでレビューするようなフローになります。

仕様書が来たら、GitHubにmergeして以下のActionsを実行します。そうすると、branchを作って生成したyamlファイルをコミットしてPull Requestを作ってくれます。

DescriptionもClaude codeが作ってくれました。使ったPDFファイルも書いてるし、作成したyamlファイルも書いているのでわかりやすい感じに仕上がりました。このPull Requestを元に、API仕様についてメンバーで議論したら良いのではないでしょうか。また、このPull RequestをmergeしたらOpenAPIのhtmlファイルを出力し、GitHub Pagesで社内公開できれば完璧です。

これを作ったきっかけは、巷を騒がせているAIってどうやったら活用できるのかなと考えていたことに起因します。 私自身、AIの進化のスピードは早いなと驚いていたのですが、ChatGPTが良いとかClaudeがより賢い、Geminiが追いついたみたいな話題にあまり関心を持てていませんでした。もちろん、業務で使っていたので、どのAIサービスがより良い回答が返ってくるかな〜などと、漠然とは感じていたのですが、それはたまたまその時のPromptがそうだっただけなのでは?なんて思っていたりもしていました。

また、Claude codeやCodexなどのCLIを使ってコードを書いてもらったりもしましたが、開発業務の効率が高まったか?と問われれば、う〜ん・・・と疑問を感じてしまいます。

今回、これをやってみて思ったのは、私がAIにカバーして欲しい領域は不確実性や不透明性が高い部分や理解が足りていない部分、例えば、フォーマットや文体が揃っていないドキュメントを扱う時や初めて書くコードベースのフォローかなと思いました。

開発業務を行うとき、私的に一番時間がかかるなと感じていたのは調査の時間であり、コードを書いているときはさほど時間がかかっていると感じていないのではないかと思います。たとえ時間がかかったとしても、そんなに苦に感じていないなぁと思っています。

なので、今回のようにフォーマットが揃っていない仕様書を扱う場合にとても有効に働いてくれたと感じています。形式が決まっていて、どこに何が書いてあるのか判然としている場合、プログラミングで自動化できますが、そうではない場合、人間の脳みそを使ってノイズと戦いながら読み進めなければならず、文脈を整理したり行間を読んだりすることに少々疲れてしまいます。ということは、私は文章を読んだり筆者の思いを深堀りすることをあまり本質的に得意としていないのかな。。

多分、私は開発の作業が好きなので、すでに手癖もあるし自分が効率的に作業できるようにPCもカスタマイズしているので、いちいちAIにその背景を伝えるのを億劫に感じているのかもしれません。 であるなら、自分の知識が足りていないところの理解を深めるためにAIを活用して、能力を高めるためにAIと向き合っていくのが今のところ私にとっていいのかなと思っています。

余談ですが、AIを使い始めてから本をよく読むようになりました。不思議ですね。Fact Checkのためでもあるのですが、多分、普通にAIに自然言語で指示するのが億劫なんでしょうね。生身の人間相手にはそうでもないのに、AIには面倒に感じる自分に少々驚いています。

明日の投稿もぜひお楽しみに。バイバーイ。

ウチのQAの現在地

これは Kyash Advent Calendar 2024 の16日目の記事です。

取るに足らないことですが、この記事のタイトルを思いついたとき、一丁目一番地というキーワードを連想しました。 私もそれなりに歳を重ね、それなりに物事を見通せる人になってきた気がしますが、「押っ取り刀」という聞いたこともない素晴らしい語感と意味を持つ言葉を教えてもらった先輩を思い出して、まだまだ知らないことがあるんだな〜と感慨に耽っています。年取ったもんだなぁ〜 前回 もAdvent Calendarを公開したのですが、もう1年経つんですね。 Kyash に入社して丸2年経過しました。

去年に引き続き、Backend Engineerとしてサーバー開発を続けていました。成功したり失敗したりしましたが、近頃、組織変更があって、QA(品質保証)の人に戻ることになりました。 会社にとってとても重要で、時間をかけて育てていきたい機能がリリースされるにあたり、失敗をできる限り低減したいという考えから、独立したQAが欲しいとのことで、私にお声がかかったようです。 1年ほど、プロダクトの開発にどっぷり浸かっていたので、色々見通せる部分が広くなってきたこと、これはなかなかに動かすのはしんどそうだなぁと思う部分が見えてきたタイミングでした。 チームに入ってから、少しの間、静観しながらメンバーの雰囲気を窺っていたのですが、私なりに課題に思えるテーマが見えてきました。

API Firstな感じがあまりしない

KyashのBackend Engineerのサーバー開発は、ほとんどがWeb API開発になります。 既存のAPI Endpointを利用するか、新しいEndpointを生やすのか、アクセスが重複したときの排他制御はどのように設計するのか、受け取ったレスポンスをどのようにハンドリングするのか、みたいな設計にまつわる議論がほとんどです。 API開発が盛んなので、機能を実装する前にもう少しAPI仕様を作り込んで、先にテストを書いてから開発に着手したほうが良いのではと感じていました。

「動いて風を知る」という標語をValueに掲げるKyashの文化は、開発前に仕様をかっちり固めてしまう前に実装に入り、不都合があればメンバー同士で話し合って軌道を修正していくスタイルが多いです。例えば、まずは動くものを短期間で開発し、それをレビューしながら改善を重ねるアプローチを重視しています。 今まではうまく機能していましたが、私が入ったチームが開発する機能は他社のチームとガッチリ協業する必要があり、うまく機能していないようでした。

開発プロセスの進め方として、API仕様の検討や議論は進められるのですが、それをDocumentに落としたり、実行できる環境を準備するプロセスが後の方にある印象を持っていました。要件定義の段階で、API仕様を確定させることは難しいですが、要件定義時点で考えうるAPI仕様を策定するプロセスが少し弱いと思っています。しかし、それはメンバー同士のコミュニケーションやフォローでカバーできていたので、私自身もその良さを認識していました。 しかし、似たような課題感をチームメンバーであるa1yamaさんも持っていたようで、このような施策を実行していました。

zenn.dev

結果的にOpenAPIを採用して、素晴らしいDocumentが出来上がってチームメンバーに良い影響を与えていました。私も出力されたAPI Documentを拝見しましたが、Backend Engineerとして、そうそう、こういうのは欲しいよね〜と感じました。 一方で、チームメンバーのヒアリングを進めているなかで、別の課題も残っているように感じました。それは、並行開発できずに開発とテストがうまいこと進められていないことでした。 前述の通り、Kyashの機能はほとんどWeb APIで実現しています。iOSAndroidの開発はBackend Engineerがそばにいるので、話し合いながら並行開発できている状態ではありましたが、私が入ったチームが実現したい機能は、他社のチームが作ったAPIと連動して協働する必要があり、足並みを揃えることに苦心していました。それはKyashのエンジニアメンバーが持つ開発文化や進め方、品質に対する考え方と、他社チームがも持つそれとの違いから発生しているように私は感じました。 Kyashは資金移動業者であり決済事業者でもあるので、堅牢なシステムを構築するメンバーが揃っています。なので、文化や考え方はあまり違わないだろうと思っていましたが、それでも今回、一緒に協業する他社チームとは文化や考え方が少し異なっていたようで、苦労しているようでした。

前述のOpenAPIを使ったDocumentationは素晴らしい取り組みでしたが、そこから更に踏み込んだ取り組みが必要なのではないかという考えに至りました。

関心の分離

更に踏み込んだ取り組みとして、OpenAPIでAPI仕様を決めた段階でmockサーバーを立ち上げ、Mobile EngineerメンバーとBackend Engineerメンバーそれぞれで開発を進める手法を取りたいと考えました。 また、他社チームのAPI仕様を受け取った段階で、External API mockサーバーを立ち上げることにより、Backend Engineerメンバーの開発進行を阻害しないことも大事だと思いました。

なぜなら、オブジェクト指向の文脈で話される「関心の分離」ではなく、チーム間における「関心の分離」を進めたほうが、このチームはうまくいくのではないかと考えたからです。関心の分離により、開発チームがそれぞれの分野に専念できる環境を作り出し、全体の効率を向上させる狙いがあります。 Mobile EngineerはAPIの内部実装に関心を持つ必要はないし、Backend Engineerもまた、協働チームが開発したAPIの内部実装に関心を持つ必要は本来ありません。また、APIの仕様を理解しており、動くテストダブルが存在すれば、別のチームが開発するAPIの実装進捗に合わせる必要もないはずです。 他にも、別のチームが想定しているエッジケース、例えばメンテナンスによるレスポンス(5xx系)の変化や、万一の時の障害時における異常系のレスポンスを受け取った際の私達のシステムのハンドリングなど、実装はしてるけど、テストするには難しい場面は多くあります。正常系のテストだけでなく、仕様上、想定されるレスポンスに対して、どれだけ作り込まれているかを確認するためにも、テストは重要です。開発者自身もそこまで考慮してテスト設計しているのであれば、安心できるはずです。

そこで、何かいい方法でテストダブルを作れないかと思案していたら、そういえば Postman ってそういうのできるやんと思い出して深掘りしてみました。 幸い、現メンバーも過去のメンバーも、実はKyashのBackend Engineerの中にはPostmanを利用していた方が大勢いました。私もまた、E2Eテストを実装する中でPostmanを多用していました。

関心の分離が進み、仕様に則ったmockサーバーを早い段階で作成できれば、モバイル(Android / iOS)とサーバーサイドの機能テストも個別でできるはずです。mockを使った機能テストをそれぞれで進め、最終的に結合テストリグレッションテストでモバイルとサーバーを繋ぎこんだテストを行うほうが、テスト対象の成果物の完成度が高い状態でテスト実施できるので、テスト効率が良いはずです。そうすることで、テスト工程を開発前に持ってくることができ、Test Driven Development(TDD)やShift Leftを推し進められるのではないかと期待しています。

Postman

正直、私はPostmanについては、HTTPクライアントのGUI版で、便利にHTTPリクエストを送れるぐらいのイメージしか持っていませんでした。 もちろん、他に色々便利機能があることを知ってはいましたが、使いこなせてるとは言えない状態でした。そんな折、Postmanが connpass でワークショップを定期的に開催されているようだったので、申し込んでみました。 ワークショップが開催される前に、意識的に使い込んでみたので、ほとんどの機能については知っている状態でしたが、 mock は私のチームにうまくマッチするのではないかと感じました。

Kyashでは、テストを実施する環境として、2つ存在します。開発があらかた終わって機能テストを行う環境と、機能テストを通過して改修箇所以外に影響が出ていないか確認するリグレッションテストを行うための環境です。 連携している外部サービスによっては、公開しているテスト環境が1つしかないサービスも多く、API Gatewayの切り替えて2つの環境でテストしたり、スタブを自前で実装してテストしています。 API Gatewayを切り替えて外部サービスのテスト環境に繋いでテストできればいいのですが、例えば一意のパラメタを渡す必要があるサービスの場合、複数の環境から生成した値だと重複してしまう可能性があるため、テストできません。そのような場合、スタブを実装するのですが、外部サービスのAPIレスポンスを擬似的に返却する実装を加えるため、プロダクトコードにスタブ実装が混入してしまいます。また、スタブモードに切り替えるためにconfigなどを更新する必要があり、デプロイが必要となります。 プロダクトコードにスタブ実装が入ってしまうとあまり見通しがよろしいとは言えないし、スタブに切り替えるためだけにmerge Pull Requestを作るのも面倒です。 また、そんなに多くはないけれども、スタブの実装自体もメンテしなければならないので、コストもかかります。なので、ここをPostmanで簡単にプロダクトコードに依存しない高機能なmockを作れないか模索中です。

mock以外にも、Postmanに期待する機能があります。例えば、Postmanの Newman を用いることで、 CircleCI Orb で自動化されたAPIテストを実行できます。 Kyashでは、APIテストについて scenarigo を利用しています。 scenarigo は素晴らしいツールですが、Backend Engineerのみが扱うツールという位置づけなので、チーム間のコラボレーションは期待できません。 Postmanでリクエストを登録しておけば、コレクションを共有しているBackend Engineer以外のメンバーでも気軽に好きな環境でHTTPリクエストを送ることができ、レスポンスを確認することができるので、デバッグや動作確認に大いに役に立つはずです。 私のチームでは、OpenAPIを広めていこうとしていますので、OpenAPIをPostmanのCollectionに変換する openapi-to-postman も、チーム間コラボレーションに役に立つことを期待しています。

Kyashをリリースしてから10年が経過し、それまで様々なエンジニアが実装してきたAPIには膨大な数のendpointが存在します。中には必要なのかそうではないのかわからないものも存在するなかで、APIテストを浸透させていくためには、Backend Engineerの方々の協力が不可欠です。 PostmanのCollectionを増やしていけば、Backend Engineerが触れたことのないendpointの動く仕様も簡単に手に入れることができるため、開発効率に貢献できるので、協力を得やすいと思っています。結果的に、APIテストの浸透を加速できるのではないかと期待しています。

現在は、私達のチームで利用するmockをPostmanで実現できないか検証しつつ、クライアント(Android / iOS)からコールされるAPIリクエストを確認し、E2Eテストの自動化を進めています。

ちょっとみせ

Postmanをチームに広めていくに当たり、「こんなことができるんだよ」とレクチャーしたほうが良いと考えたので、いくつかAPIテストを作ってみました。 その中で、以下のようなスクリプトを実装しました。

const luhnCheck = (number) => {
    let sum = 0;
    let shouldDouble = false;
    
    // 右から左に向かって数字を処理
    for (let i = number.length - 1; i >= 0; i--) {
        let digit = parseInt(number.charAt(i));
        if (shouldDouble) {
            digit *= 2;
            if (digit > 9) {
                digit -= 9;
            }
        }
        sum += digit;
        shouldDouble = !shouldDouble;
    }
    return (sum % 10 === 0);
}

module.exports = {
    luhnCheck
}

Postmanでは、 PackageLibrary を利用して、Node.jsベースの独自のJavascriptでテストコードを書けます。 上記の luhnCheck は、引数として渡された数値列がLuhnアルゴリズムによるチェックディジットとして正しいかチェックする関数です。これを利用して発行されたクレジットカード番号が正しいかチェックするAPIテストを実装しています。 また、Kyashはクレジットカード業界に属しているので、様々な暗号化方式を利用しています。暗号化した文字列を復号化する処理を様々な場面で利用していますので、比較的簡単にPackage LibraryにJavaScriptで実装できるのは嬉しいポイントでした。

おわりに

Software Engineer in Testとして入社してから、モバイルアプリのUIテスト自動化に取り組み、Backend EngineerとしてAPIのプロダクト開発に参加し、QAとして戻ってきました。 私はソフトウェア品質を高めていく活動を進めているのですが、すでに出来上がっているプロダクトのソフトウェア品質を高める活動には、メンバーの協力が欠かせません。そのため、活動を浸透させるための仕掛けを入念に設計し、粘り強く普及させる必要があります。そうしなければ、せっかく良い取り組みや活動が風化し、意味を失ってしまうと考えています。 とはいえ、メンバーにAPIテストを実装してもらうにしても、スイッチングコストが高くなってしまい、なかなか受け入れてもらえずテストコードが増えていきません。そうすると、テスト実装コストはQAが受け持つことになり、QAメンバーの負荷が上がり、どちらにしても結果的にテストコードが増えません。Backend Engineerの開発に必要(便利)なツールとして開発プロセスにCollectionの追加を組み込み、自然とテストコードが増えていく事が理想だなと考えていますので、すでに各々で使っているPostmanをうまく利用しない手はないと思っています。 そのために、私が率先してPostmanをうまく使いこなせるようになることで、TDDやShift Leftを実現したいと考えています。

テストや品質的な話をすると、どうしても面倒に感じてしまうし後回しにしがちだと思います。実際、私が開発してても同じ思いを持つ機会も多いです。でも、急がば回れではないですが、その時点で考えうる仕様を決めてしまい、動くmockサーバーを作ってしまってメンバーの手が止まる機会を少しでも少なくすれば、高品質な成果物がもっと早く出来上がるのではないかと思っています。

明日の投稿もぜひお楽しみに。バイバーイ。

育てる開発環境

これは Kyash Advent Calendar 2023 の13日目の記事です。

近頃、寒いのか暑いのかわからない気候で大変ですね。 体調管理が難しくて、鼻がぐずったりお腹こわしたり大変です。 もうちょっと厚めの毛布にしたほうがいいかなぁ〜なんて悩みながら日々を過ごしています。

Kyash で働き始めて1年ぐらい経過して、はじめの頃は Software Engineer in Test(SET)として自動化を推し進めていたのですが、最近はBackend Engineerとしてプロダクトの開発のお仕事のほうが多くなっています。 SETとして社内のプロダクトのリソース全てに関与していたとき、自動化を推し進めるにあたって考慮しなければならないことが多いなぁと感じていたのですが、Backend開発のお仕事においても同様の感想を持ちました。 その中でも特に課題に感じたのは開発環境です。

Kyashでは、各々の開発者、主にWeb開発に関わるメンバーにおいては、それぞれの開発環境が提供されています。 提供 という言葉を使ったわけは、各々にEC2インスタンスが1つ割り当てられ、開発用途として自由に使っても良いサーバーが提供されているという意味合いを表現したかったからです。 EC2インスタンス上で開発を進めていたのですが、少し使いにくいなと思う場面がでてきて、改善に努めています。


まず、メモリやストレージを食い尽くしたりCPU使用率が上がったとき、EC2インスタンス自体が立ち上がらなくなってしまった場合、自分で解決する手立てがないことです。 EC2インスタンスはTerraformで管理していて、マネジメントコンソールから色々操作する権限は少ないです。 これでは壊れたときに、自力で解決する機会が少なくなり、結局、直すにも他のチームに頼らざるを得ない状況です。

2つめは、コンテナのログを見るために、わざわざEC2インスタンスにログインし、docker compose コマンドを実行する必要があることです。 Kyashでは、マイクロサービスに寄せたプロジェクト構成を採用しており、全ての機能を成立させるには30個ほどのコンテナを起動させる必要があります。 コンテナ自体はEC2インスタンスで起動しているため、どうしてもEC2インスタンスにターミナルからログインして、$ docker compose log xxx を実行する必要があります。 複数のプロジェクトにまたがる改修をしていたり、機能調査するために複数のインスタンスの状態を確認したい場合、何個もターミナルを開かざるを得ない場合があります。

3つめは、デバッグポートを開けていないので、ブレイクポイントを置いたリモートデバッグができないことです。 KyashではIntelljのGolandやVScode統合開発環境として利用できますが、printfデバッグしかできなくなってしまいました。 Kyashはリリースされてから一定期間が経過し、ソースコードの量は膨大でして、どこでどんな値を変数に入れているのか分かりにくかったり、ある時点の状態でセッションにどういう値を持っているのかを追うのがしんどい場面があります。 そういうときに、ブレイクポイントを置いて、デバッグができないのはとても辛い状況でした。

他にもあるのですが、代表的なものは上記3つでした。 一方で、Docker Desktopのライセンスは会社で支払っており、ライセンスも付与してもらっていたので、これをうまく使いたいなと考えていました。 ということで、EC2インスタンスを利用した開発環境を導入する以前は上述の現象はなかったことだったので、昔のKyashの偉人が作ってきた開発環境を生き返らせることにしました。


VirtioFS

私が初めてKyashの開発環境を構築した1年前は、まだDocker DesktopでのファイルシステムのデフォルトはgRPC FUSEでした。 VirtioFSはまだ正式採用されていませんでしたが、現在は正式採用されました。

www.publickey1.jp

かつてのKyashでは、Intel Macを開発者に支給しており、開発環境もmacOS上のDockerに構築していました。 しかし、起動するコンテナが30個を超えてきたあたりから、macOSの動作が緩慢になり、 profiles タグを指定して、特定のコンテナのみ起動するなど、工夫して利用していました。 その後、profiles タグを指定して起動するコンテナを選びながら利用することも手間となり、EC2インスタンス上の開発環境を構築することになりました。

VirtioFSは、2022年末にDocker Desktopで正式採用されたファイルシステムで、ファイルアクセスの高速化が期待できるアップデートでした。 Docker Desktopが重くなる現象は私も体験済みで、色々工夫しなければならないことは知っていたのですが、今回扱う端末はM1 MacIntel Macより性能が良いともっぱらの噂だったし、VirtioFSが正式採用されたと聞いたので、かつてのmacOS上に構築する開発環境も工夫次第で良くなるんじゃないかと考えました。

Docker image

  • arm64

支給されたmacOSはM1チップ搭載のMacbook Proでしたので、コンテナのベースイメージを arm64 に対応したimageでビルドし直すことにしました。 docker composeで起動する30個のコンテナは、3種類ぐらいのDockerfileからビルドされたイメージを利用していたので、それぞれのイメージをビルドし直しました。 OSバージョンが古く、サポート期限が切れていたので、最新のLTSイメージを採用しました。

  • image size

ビルドイメージのサイズが大きいと、ローカルストレージが圧迫し、ビルドにも時間がかかってしまうので、 arm64 に対応したイメージで軽量なイメージを選びました。 Production環境と同等のイメージを採用したかったのですが、既存のビルドイメージの最新版のほうがサイズが小さいことがわかったので、結果的に既存のビルドイメージの最新版の arm64 に対応したものを選択しました。 なお、KyashはGo言語を主に使っているので、distrolessも検討したのですが、Dockerfileの内容を少し変更しなければならなかったため、今回は見送りました。 次の機会に軽量なコンテナイメージを利用できるようにしたいものです。

github.com

docker compose v2

これはパフォーマンスチューニングにあまり影響はないと思いますが、docker-compose.ymlをcompose.ymlに変更し、記述方法もDocker Compose V2に準拠した記述に変更しました。 最新のDocker Desktopを利用している場合、docker-compose コマンドは docker composeエイリアスされるようなので、せっかくなので、V2に合わせました。 ymlファイルの version の指定をなくしました。

matsuand.github.io

- version: '3'
services:

compose.ymlの分割

既に利用していたdocker-compose.ymlには、1つのファイルに全ての services を記述していたので、1,500行ほどの巨大なファイルになっていました。 profile タグをつけてある程度整理されていたとはいえ、どのようなコンテナが設定されているのか見通すのが困難な状態でした。 せっかく profile タグをつけていたので、 profile の種類ごとにcompose.ymlを作成し、 docker compose -f でファイル指定して起動するようにしました。 EC2ローカル環境で利用している docker compose コマンドは -f を指定しないので、デフォルトの docker-compose.yml を参照します。 今回作成するymlファイルはEC2ローカル環境に影響しないように、 compose-arm64.yml とし、それを profile ごとに分割しました。

volumes

Docker Desktopが重くなる原因の中でとても有名なものは、LinuxMacファイルシステムが異なるので、volumeマウントすると遅くなるというのは見聞きしていたので、volumeマウントのチューニングを行いました。 ローカル開発環境だけで使うのなら、そこまで即時ホスト上に反映する必要もないと思ったので、ほとんど cacheddelegated をvolumeに指定しました。

volumes:
  - ./tmp/postgresql/data:/var/lib/postgresql/data:delegated
  - ./tmp/mysqldata:/var/lib/mysql:deletated

docs.docker.jp

Makefile

compose.yml を分割すると、docker compose 実行時にいちいちymlを指定するのが面倒なため、Makefileを作成することで、コマンドの簡略化を行いました。 主に作成したmakeコマンドは以下です。

  • up : -f を指定して、docker compose upするコマンド
  • down : -f を指定して、docker compose downするコマンド
  • rm : 起動中のコンテナ一覧を peco に渡し、pecoから操作するコンテナを選んで stop してから rm するコマンド
  • bootstrap : コンテナ起動時、初期設定を行うためのコマンド
  • migrations : 各コンテナで必要になるDBの初期データを投入するためのコマンド

上記を組み合わせて、以下のコマンドを定義しました。

  • setup : 初めて開発環境を構築するときに実行するコマンド
  • reset : 全てのコンテナを削除し、DBデータやCacheを削除するコマンド
  • restart : 起動中のコンテナを1つ選んで削除し、再起動するコマンド

その他

環境構築時、手動で行っていたファイル変更や、足りないgo moduleをインストールする手順をスクリプトにまとめ、bootstrap時に実行するようにしました。 そうすることで、ほぼmakeコマンドのみで開発環境をコントロールできるようにしています。


おわりに

私が開発するときに、考慮しなければならないことや不便に感じることはできるだけ解消したいと考えています。 自分が開発するにあたって、気持ちよく作業するためにツールを開発したりチューニングしたりするのは開発者の嗜みだと思っています。 堅苦しいことばで表現すると開発生産性や作業効率の向上を行うために、組織理論や開発手法などにフォーカスが当たりがちですが、自分の手元の端末の開発しやすさにも目を向けて、開発環境もHackingしていきたいなと思っています。 組織の生産性を上げるためには小難しい施策が必要かもしれませんが、自分の生産性を上げるためには自分自身が工夫したり試行錯誤することが必要です。 キーボードやマウス、PC、モニターに持つこだわりを開発環境にも持って、開発しやすい開発環境を育てていきたいですね。

Kyashでは在宅勤務メンバーが多いため、ローカル環境の立ち上げに手間取ってしまうと回復させるためのフォローが大変です。 なので、できれば make コマンド一発で全ての環境が立ち上がることを理想としています。 幸いDocker Desktopを使った環境構築のノウハウは、検索でとても多くの情報を得ることができます。 解決できるヒントが数多くあるということは、自力解決能力を養う機会も多くなるため、レベルアップのためにDocker Desktopを使っていきたいと思います。

ちなみに

Quality Assuranceをリードするエンジニア(SET)として在籍しているのに、実装したり、ローカル環境再構築など、開発業務にコミットし過ぎでは?と思うときが時たまあります。 ただ、品質を上げたり不具合を少なくしたりするためには、システムテストの数を増やしたり、精度や頻度を上げることも有効であると考えていますが、そもそも不具合を作り込まない組織にすることも大切だとも考えています。 そのために、個々の開発者がストレスなく開発できるような環境を提供することや、リリース前に不具合に気づくことができる下地作りが必要だと思っています。 それが自動テストを動作させる上で必要なことだし、今やっていることが、Quality Assuranceをリードするエンジニアとして達成したい状態に近づくことに、大きく乖離しているとは思っていません。 歌って踊れてなんでもできるエンジニアになろうかなって思っています。

来年は、卒業されていったKyashの数々の偉人の積年の願いであるStaging環境をもっといい感じに運用するぞ〜!!!
明日の投稿もぜひお楽しみに。バイバーイ。

JaSST'23 Tokyoに参加しました

3月9日〜10日の2日間にかけて、 JaSST'23 Tokyo が開催されたので、参加してきました。 JaSSTは5年ぐらい前に当時のチームメンバー参加したことがあり、日本大学の学食でカレーを食べた記憶があります。

tech.pepabo.com

当時、参加してみてみんなでその気になり、その後、みんなで頑張って勉強してJSTQB Foundation試験を有明まで受けに行った良い思い出がありました。 それを契機に、私は脆弱性検査やペネトレーションテスト、自動化を行うセキュリティ関連の業務に従事することになるのですが、当時所属していた組織から離れることになってしまったため、JaSSTへの関与も希薄になってしまっていました。 風のうわさでみんなの活躍を見聞きすることはありますが、元気にやっているだろうか。 ちなみに、今回の JaSST'23 Tokyo 参加後、JaSST運営メンバーの方とお話できる機会があり、「次はALですね!」と言われて、お、おぅ。。と尻込んでしまいましたw

今回は残念ながら1人での参加になってしまいましたが、テスト活動に関連する職務についたので、当時の熱感を取り戻したくて参加しました。 会場は御茶ノ水にある日本大学ではなく、Discordによるオンライン参加でした。 2日間とも参加したのですが、会場移動がないので聞きたい講演を次々に回るのにはオンラインは便利だなと思いましたが、情報交換会をあまり活用できなかったなぁと反省しています。


現在も、当時と同じように効率的にテストが行えるようにツールを作ったり、UIテスト検証やAPIテストについての基盤作りに勤しんでいます。 Go言語で本格的に開発を行ったことはなかったので、ポインタや型アサーションなどの言語特徴を勉強しながら楽しく取り組んでいます。 当時と同じように、ソフトウェアの品質を高めるための「多層防御」を行うためにはQAだけでなくプロダクト開発に関わる組織全体で取り組む必要があるため、啓蒙活動も担当しているのですが、やっぱり一筋縄ではいかず、他社の事例や同じような職務についている方のお話を聞きたいという思いもありました。 全部は書ききれないので、参加した講演をかいつまんで紹介したいと思います。


基調講演 Chaos Engineering to Continuous Verification

  • 講師:Casey Rosenthal(Verica)

継続的なCI/CDサイクルの信頼性や安全性を高めるために、カオスエンジニアリングを提唱していました。 意図的に障害を起こすことにより、その後に起きるシステム的な問題や復旧プロセスの問題をあぶり出そうとしています。 例えば、ある組織でStaging環境のKafkaを意図的に止めたら、Production環境のKafkaがStaging環境のKafkaに依存していて本番障害が発生してしまったことを紹介されていました。 このようなシステム間の依存性はなかなか顕在化することが難しく、ソフトウェアの複雑性が高い場合に起き得る問題であるとしています。 システムの複雑性はシンプルに保つべきですが、アベイラビリティ(可用性)の高いソフトウェアは複雑性が高くなり、実現可能ではないので、検証して問題把握に務めることが大事だとおっしゃっていました。

また、システムの複雑性の他に、人的なエラーについても言及していました。 医療過誤件数を減らすために、医療過誤を多く起こしていたスタッフの上位5人を解雇したが、結果的に裁判件数が増えてしまったそうです。 解雇したスタッフはハイスキルを持った人材であったため、ローレベルのスタッフがより多くの医療過誤を起こしてしまったことが原因だったそうです。 しかし、人がエラーを起こす確率はほとんど同じで、根本原因を解消しても、アベイラビリティ(可用性)を上げることは難しいそうです。 なぜなら、人は必ずエラーを起こすので、最終的に「気をつけましょう」的な対策しかできないからです。 エラーの原因を起こしてしまった1人のせいにするのではなく、組織のコミュニケーションレベルや予算配分を含めた原因に対処するほうが、アベイラビリティ(可用性)は上がるそうです。

私のチームで読んでいる チームトポロジー 価値あるソフトウェアをすばやく届ける適応型組織設計 にも似た概念が書かれていましたが、ソフトウェアのアベイラビリティ(可用性)や品質を上げるためには、QAやエンジニアなどの特定のロールに責任を依存させるのではなく、組織全体で原因に対処する必要があることを再認識しました。

ただ、本番で障害を意図的に発生させるなど、危険度の高い試みについてはビジネス層や経営層を巻き込んだ意思決定が大前提にあり、少しハードルが高いなぁと感じました。


カオスエンジニアリングの裏話

  • 講師:堀 明子(オーティファイ)、松浦 隼人(オーティファイ)

オライリーから出版されている「カオスエンジニアリング」の訳者である Autify の松浦さんによる、カオスエンジニアリングをテーマにした講演を聞きました。 基調講演と同じく、私達が扱っているソフトウェアシステムの複雑性は高く、互いのコンポーネントがどのように干渉しあっているのかを把握することは、現状ますます難しくなってきています。 複雑性の高いソフトウェアに対して、ソフトウェアテストとカオスエンジニアリングがどのような関係性にあるのかを、以下のように説明されていました。

  • Known knowns : 知っているし、理解している(テスト)
  • Unknown kowns : 知らないが、理解している(直感)
  • Known unknowns : 知っているが、理解していない(監視)
  • Unknown unknowns : 知らないし、理解していない(カオスエンジニアリング、オブザーバビリティ)

ソフトウェアテストは妥当性確認を行うが、カオスエンジニアリングは未知のものを見つける作業であり、検証のアプローチが多いです。 Validation(妥当性確認)に重きを置くソフトウェアテスト、Verification(立証や検証)に重きを置くカオスエンジニアリングの対比を説明し、Unkown unknowns(知らないし、理解していない)部分を受け持つのがカオスエンジニアリングとのことでした。

  • Testing vs Experiments
    システムが期待通り動くことを確認する作業と、仮説検証し実験を試みる作業
  • Validation vs Verification
    妥当性確認を行う作業と、検証を行う作業
  • Steady states
    システムの定常状態に関する仮設を立てる (Steadyは決まった恋人みたいな和訳もあるが、安定して不変な状態と説明)
  • Blast radius
    実世界の事象を多様化(Vary)させること 多様化(Vary)させることが大事で、具体的には本番環境で実験すること、継続的に実行できるように自動化すること、影響範囲を局所化することなどを述べていました

Blast radiusは和訳すると「爆発半径」となるが、英語原文を直訳してわかりやすく「影響範囲」としたことなど、翻訳のときに使ったTipsを紹介してくれました。

principlesofchaos.org

セキュリティ監査を行うときにも通ずることだなぁと感じ、とても参考になる概念だなと思いました。 ソフトウェアテストにおいても、探索的テストがうまい人ってシステムの定常状態を把握し、仮説検証を自然と行える人がよく不具合を見つけられる印象を持っていたので、対立関係ではなく互いに参考になると思いました。


ローカル環境を用いたアジャイルテスティングの実践事例~より高速なフィードバックを目指して~

  • 講師:村岡 里紗(freee)

私が所属している組織でも、開発が終わった後にQAを行っており、品質保証活動がボトルネックになっているという課題があり、同じような課題にfreeeさんはどのように取り組んだのか興味があったので講演を聞きました。 freeeではマイクロサービスアーキテクチャを採用しており、開発環境の構築が厄介なので、検証環境を準備して開発エンジニアにデプロイしてもらっているそうです。 しかし、そうするとどうしてもテストが後回しになり、不具合検知タイミングが遅れてしまっていたことを課題にしていたそうです。 そこで、軽量なローカル環境をQAエンジニアの作業PCに構築し、スプリント毎にテストを行い、スプリントが終わった後に通しのテストを行うことによって、1週間かけて行っていたQA活動が3日で終わるようになったとのことです。 ただ、軽量なローカル環境を使えている人はまだ少ないようで、操作知識や環境の拡張時において、課題となるみたい。 やっぱりDockerを使って環境構築しているようでしたが、ターミナル操作ができるQAエンジニアは少ないようです。 私も同じことを考えていたので、「QAメンバーにDockerなどの開発環境をレクチャーするコツなどはないですか?」と質問してみましたが、地道にコマンドから教えている、と回答いただき、熱心に取り組んでいるんだなぁと感じました。

他にも、QA活動の一環として、開発の状況を把握するため、ミーティングに参加しまくっているそうです。 これについて、私はできていないなと反省しました。

軽量なローカル環境を持てるようにすることについての意義について、とても参考になりました。 定量的な成果(1週間〜3日)が出たことは素晴らしいのはもちろんですが、副次的な効果として、テスト担当者のドメインに対する習熟が促進され解像度が上がることも、品質活動に関して良い効果を生んでいるのではないかと思いました。 その結果として、より早い段階で不具合を検出でき、より具体的なフィードバックを行えるようになり、リリースサイクルの短縮にも寄与していると思います。 しかし、そのようなサイクルを回すには、QAエンジニア側も多くの負担を強いられるので、完全な形で実現するために色々考えて実施していかないとダメなんだろうなぁと感心しました。


結合テストの自動化にQAはどうかかわっていったか

speakerdeck.com

続けて、サイボウズさんのテストの自動化に関する取り組みに関する講演を聞きました。 UIテストよりE2Eテストの優先度を上げ、自動化の普及に取り組んでいるそうです。 APIテストのオーナーが開発チームにある場合、テストをどのように設計したら良いのかわからないそうです。 逆にオーナーがテストチームにある場合、修正に膨大な時間が取られます。 その場合、開発チームはテストが壊れることにあまり意識が向かなくなるようで、DevOpsチーム全体でAPIテストのオーナーシップを持たなければならないと言っていました。 DevOpsチーム全体でオーナーシップを持つことにより、問題が発覚するまでの時間が短くなり、開発者がテストを壊すことに意識が向くようになるそうです。 結果的にフィードバックループが速く回転することにより、全体としての負荷が下がったそうです。

テスト計画にも問題があったそうです。 テストの観点や背景が、暗黙知が働いていて曖昧になっていたようです。 このテストで何を評価したいのか?、このテストでやるべきなのか?、などの議論がなかなか進まなかったそうです。 QAメンバーもテスト項目の言語化スキルが必要と分かり、結果的にQAもより深く目的や観点を考えるきっかけとなり、テストの責務について認識するようになったそうです。 それによって、自動化の設計がしやすくなり、工数も減っていったそうです。 このように、QAと開発者が互いに信頼やリスペクト意識を持つことにより、より質の高いテストコードの設計ができるようになったそうです。

素晴らしい取り組みだと思いましたが、QAと開発の相互理解が肝となり、長い時間をかけて今の状況を作り上げたのではないかと感じました。 私は開発者なので、どうしても開発者目線でQAチームを見てしまうことがあるのですが、暗黙知が働いたテスト計画書を見た覚えはあります。 目的が明確でないと自動化の設計がとても複雑になり、冗長な設計になり、結果的に自動化することによって逆に負荷が高くなってしまいます。 QAと開発者が互いに尊重しあい、前向きに議論できる関係性が構築できれば質の高い自動テストが実現できる事例を紹介いただき、大変参考になりました。


終わりに、 JaSST'23 Tokyo に参加してみて、開発チームとテストチームは互いに尊重しあう文化作りが大切だなと改めて実感しました。 テスターからの不具合報告に「バグ」というキーワードを織り込んではいけないとかはよく耳にしますが、テストチームからの過度なへりくだった対応もあまり良い効果を生まないような気がします。 逆に開発チームがテストを破壊することに無関心でいることも、良い結果を生みません。 現在、テストの自動化に取り組んでいますが、ツールの検証や実装だけでなく、組織の文化的な側面にもアプローチしていかなければならないと感じました。

自動化は界王拳の夢をみるか

この記事は、 Kyash Advent Calendar 2022 の8日目の記事です。

adventar.org

 久々のブログ投稿となります。肌寒い日が続きますね。
今年、2022年11月、Kyashの仲間に加えてもらいました。Software Engineer in Test(SET)という役割で声をかけていただき、ソフトウェアテストに関する自動化を推進するポジションとして、1ヶ月程度、過ごしております。

 私は40歳の大台に差し掛かる今まで、ソフトウェアエンジニアとして主にバックエンドの開発に勤しんできました。もうかれこれ15年程度はこの業界で働いていて、荒波にのまれながらあれやこれや担当していたので、サーバー開発だけでなく、データ基盤やモバイル開、技術責任者なんかも経験してきましたが、私の主戦場はバックエンド(サーバー)開発だと自認しています。
 そんな中、今回、Software Engineer in Test(SET)という、少し毛色の変わったロールに挑戦しております。過去に脆弱性診断士としてセキュリティ診断を行っていたことや、品質向上のためにテストの自動化に取り組んでいたこと、JSTQB資格の取得などを評価いただいて、現在に至ります。
 入社してからまだ1ヶ月で、まだまだ成果と言えるような結果を出せているわけではないですが、今回は、今まで取り組んできたこと、これからKyashで、どのようにソフトウェアテストに取り組んでいきたいのかを書きたいと思います。


 Kyashは、資金移動業ライセンスを取得しており、お客様の大切なお金に関わるシステムを提供しているFintech企業なので、品質にかけるコストについて重要視しています。入社前に id:konifar さんや id:ymzkmct さんから、現状のQAについて、予めお話を伺っていたのですが、入社後、QAチームメンバーとコミュニケーションを取らせてもらって、改めて、想像以上にしっかりとテスト活動に取り組んでいるんだなと感じました。
 Kyashの開発サイクルでは、エンジニアチームが要件定義や実装を進めていく段階で、QAメンバーもテスト計画を立て、開発が終わったらマニュアル(手動)によるテストを実施し、品質の担保を維持しています。Kyashはモバイルアプリ中心のサービスであるため、どうしてもマニュアルテストでなければテスト実施できなかったこともあって、かなりの時間をかけてテストしています。
 しかし、サービスを開始してから5年程度経過し、多くの機能が実装され、中でもリグレッション(回帰)テストにかかる時間が増えてきたため、リリーススケジュールに影響するようになってきてしまいました。そこで、モバイルアプリのリグレッションテストの自動化を実現するため、様々な可能性を模索している段階です。

 現在、Webサービスのテストについて、多くのソリューションが提供されており、また、品質を重視する組織が増えてきたこともあり、E2Eやリグレッションテストの自動化率が上がってきているのではないかと思います。また、クラウドベースのインフラ環境を構築している組織も増え、テスト実施の素地を整えやすい環境になってきたことも、ソフトウェアテストの自動化を後押しする要因の1つかなと考えています。
 しかし、広く普及している便利なソフトウェアサービスは、モバイル端末で操作するものが多く、ブラウザ操作によるテストではなく、アプリ操作によるテストを行わなければならない場面も多いと思います。Kyashはまさに、アプリ操作がメインのサービスであり、自動化がしづらく、テスト工数の肥大化に悩まされています。

 Kyashでは、新しく追加した機能が正しく利用できるかの観点でテストシナリオを設計し、テスト計画に落とし込んでから機能テストを実施しています。その後、プログラムの変更に伴ったシステムへの予想外の影響が起きていないかを確認するために、リグレッションテストを実施しています。Kyashのシステムテストを長く担当してくれているメンバーによる探索的テストも実施しているため、広範囲に渡って不具合への対策が行われています。

 一方で、ソフトウェアテストチームが正しく機能していると感じましたが、開発チームは今どきなスクラムを実践できているのに、テストはウォーターフォールのまま置き去りにされていないかと感じました。ソフトウェアテスト下流工程のようになっていやしないかと。。
 とはいえ、それはKyashだけではなく、ソフトウェアテストにおいては往々として起こることであり、また、開発とテストのトレードオフ的な関係性を解消できている方が稀だと思います。重厚なテスト活動を行えば、開発期間が短くなるし、テスト活動を軽視すれば品質に問題が出るし・・・とか言うありがちな問題です。

 id:konifar さんや id:ymzkmct さんとお話したとき、決済事業者としての高い品質を保ったまま、効率的にソフトウェアテストを行うことにより、生産力の爆上げを両立したいと聞いたとき、正直、難しい課題だなと感じました。

blog.kyash.co

 決済事業者として、1円の間違いやミスも許されないドメインを扱っているので、テスト活動を軽視してよいわけがありません。ただ、Kyashは決済システムを0から自社で作ってきたという自負のある会社です。極めて作ることにこだわりがある組織なので、もしかしたら、培ってきた技術力を活かして、生産性の爆上げと高品質を両立できるのではないかと思い、入社を決めました。


 テスト活動の自動化において、KyashではAutify for Mobileを利用させていただいています。

autify.com

 Autify for Mobile の良いところは、テストコードを書かなくても、モバイルアプリ操作の自動化を実現できる点です。初めてテストプランを作成したとき、ブラウザ操作だけで、エミュレーターが起動し、テストシナリオが作成できたときは、「モバイルアプリのテスト自動化もここまで来たか!」と感動しました。
 Kyashでは、Pull Requestがマージされたとき、GitHub Actionsでアプリをアップロードし、テストプランを起動するように設定していましたが、度々、テストが失敗するケースがありました。テストが失敗する理由は様々ですが、Pull Requestをベースにテストシナリオを起動していては、シナリオを修正する契機が開発者に依存してしまい、テスターがテストを修正する機会が少なくなり、テストの精度がなかなか上がらないと考えたため、定期的にテストシナリオを起動するようにしました。
 Autify for Mobile では、定期実行する機能は提供されていませんが、テストプランを起動するAPIが提供されているので、これを利用して、平日2時間ごとにテストプランを起動するようにしました。

 Autify for Mobileのアカウント設定から、パーソナルアクセストークンを取得します。

 今回は、お手軽に定期実行したかったので、Google App Scriptを利用しましたが、以下のPOSTリクエストが実行できればどのような環境でも動きます。

curl -X 'POST' \
  'https://mobile-app.autify.com/api/v1/test_plans/xxxxx/test_plan_results' \
  -H "Authorization: Bearer 1234567890abc" \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "build_id": "xxyyzz"
}'

 そして、POSTリクエストに成功したら、以下のようなjsonが戻ってきます。起動したことを通知してほしかったので、SlackのIncomming Webhookにjsonの内容を投稿しています。

{
  "id": "abcdef",
  "test_plan": {
    "id": "xxxxx",
    "name": "Autify Test Plans",
    "created_at": "2022-11-01T01:32:42.999Z",
    "updated_at": "2022-11-05T01:54:18.999Z",
    "build": {
      "id": "xxyyzz",
      "name": "app.build",
      "version": "0.0.1",
      "created_at": "2022-11-29T09:04:56.999Z",
      "updated_at": "2022-11-29T09:04:57.999Z"
    },
    "execute_environments": [
      {
        "os": "iOS",
        "os_version": "14.4",
        "device": "iPhone 12"
      }
    ]
  }
}

 起動後、Autify for Mobileのテスト結果ページに実行中のテストプランが表示されます。今まで作ってきたテストプランの実行が終わるのに、だいたい1時間から2時間弱かかっています。
 Autifyには、テスト実行が終わったらSlackに通知する仕組みもあり、テスト実行が終わったら通知してくれます。

 このように、テスト実行回数を増やし、テストシナリオの精度を上げる取り組みを実施しています。


 Kyashでは、テスト自動化の取り組みを始めたばかりなので、Autifyのような自動化を始めやすいサービスから導入を進めています。自動テストは壊れやすいため、オートヒーリング機能についても期待しています。
 しかし、全てをAutifyで自動化できるとは考えておらず、AppiumやSeleniumなどのテスティングフレームワークを使ったテストコードの整備を行い、網羅率とテスト精度を上げていく取り組みを行っていきたいと考えています。

 また、アジャイル開発にテスト活動を組み入れたいと考えており、スプリントの最後にテストをするのではなく、スプリントサイクルにどのようにテストサイクルを組み込んでいくのがチームとしてベストなのか模索していきたいと考えています。

 Kyashは様々なサービスと連携しているため、テスト環境の整備や運用についても、テストエンジニアが率先して取り組んでいく予定です。それは、いついかなるタイミングでテストを実行しても、正しいテスト結果(正誤問わず)を返せる冪等性を担保した環境を整備することにより、開発サイクルを高速化できると考えているからです。いつでもどこでも正しくテストできる事により、Kyash Tech Teamが掲げる3倍界王拳の実現をテストエンジニアから後押ししていきたいと考えています。

 Webやアプリ開発の様々な技術を駆使して、ソフトウェアテストに取り組み、QuarityをEngineeringしていきたいと考えています。それはもしかしたら、QAエンジニアとか開発エンジニアという枠組みを取っ払い、すべてのKyashのエンジニアがソフトウェアテストの自動化や精度向上に関わる活動を行うマインドセットを持った組織になる未来が訪れることを意味するのかもしれません。

 Kyashでは、ソフトウェアテストの自動化を始め、様々な職種で仲間を募集しています。少しでも興味がございましたら、カジュアル面談でのご連絡もお待ちしております!

herp.careers

Debianの言語設定を変更した

unionsep.hatenablog.com

前にDebianのLocal timeをJSTに変えたんですが、そういえば、言語設定が英語のままだな〜と思ってました。 Amazon Linuxで言語設定変えたけど、これ、言語設定と共にやっといてもええかな〜と思ったんで、メモします。

unionsep.hatenablog.com

OSは Debian 10

$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 10 (buster)"
NAME="Debian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

こういう感じで英語な感じのレスポンス

$ ll
-bash: ll: command not found

日本語のパッケージがインストールされてなかったんで追加

$ sudo apt install task-japanese locales-all

ロケールを変更するには localectl コマンド これで /etc/default/localeja_JP.UTF-8 に変わった

$ sudo localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"

変えた設定を適用する

$ source /etc/default/locale

日本語が返ってきたっす

$ ll
-bash: ll: コマンドが見つかりません