こしあん
2025-02-02

CodeBuildでPython単体テストを自動化


20{icon} {views}

TerraformでCodeBuildプロジェクトを構築し、非公開GitHubリポジトリからPythonのunittestを実行してビルド可否を判定する手順を紹介します。ソース認証にはPersonalAccessTokenを用い、ブランチ切り替えでテストの成功・失敗を簡単に確認します。

はじめに

  • DOPの勉強しててDVAの頃から出てくるCodeシリーズがあんまり使ったことなかったのでやってみた
  • やりたいことは、CodeBuildの中で単体テストを動かして、テストがパスすれば成功、パスしなかったら失敗を吐き出すもの

Codeシリーズ

  • Codeシリーズは、
    • CodeCommit:ソース管理。AWS版のGit。新規受付終了
    • CodeArtifact:アーティファクト管理。PyPiやnpmの代わりに使える。
    • CodeBuild:ビルドの自動化。今回扱う部分
    • CodeDeploy:デプロイの自動化
    • CodePipeline:ビルドからデプロイまでを一括して流せるパイプライン
  • 単体テストはCodeBuildとCodePipelineで動かすのがあり、CodePipelineのほうが高度なことができる。今回はCodeBuildだけで行う単純な例

GitHubの非公開リポジトリ

GitHubの非公開リポジトリも対応している。CodeBuildの場合は、PersonalAccessToken(PAT)で連携する。

  • run_unittest.py
  • requirements.txt

というファイルをリポジトリに作る。requirements.txtは必要なライブラリを書くが、今回は何もいらないので空ファイルでOK。

「入力値が奇数なら2乗、偶数なら2倍」という単純な関数について単体テストを行う。

import unittest

# メインコード
def process_number(n):
    """
    入力値が奇数なら2乗、偶数なら2倍して返す関数
    """
    if n % 2 == 1:
        return n * n
    else:
        return n * 2

# 単体テスト
class TestProcessNumber(unittest.TestCase):
    def test_odd_number1(self):
        """奇数の場合のテスト: 3 -> 9 (3の2乗)"""
        self.assertEqual(process_number(3), 9)

    def test_odd_number2(self):
        """奇数の場合のテスト: -1 -> 1 (-1の2乗)"""
        self.assertEqual(process_number(-1), 1)

    def test_even_number1(self):
        """偶数の場合のテスト: 4 -> 8 (4の2倍)"""
        self.assertEqual(process_number(4), 8)

    def test_even_number2(self):
        """偶数の場合のテスト: -2 -> -4 (-2の2倍)"""
        self.assertEqual(process_number(-2), -4)

    def test_zero(self):
        """0の場合のテスト: 0は偶数なので0の2倍で0"""
        self.assertEqual(process_number(0), 0)

if __name__ == '__main__':
    unittest.main()

unittestはPythonの標準ライブラリ。ローカルで実行すると以下のようにテストが通る

----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

CodeBuildの準備(Terraform)

  • 非公開リポジトリをCloneする場合の認証情報は、PersonalAccessTokenで行う。aws_codebuild_source_credentialに登録する
  • ここがハマったのだが、認証情報はaws_codebuild_project関連付けなくて良い。逆に言えばaws_codebuild_source_credentialで登録した認証情報1個しか使えない
  • source_versionで指定しなければメインブランチが使える。適当なリモートブランチを指定することも可能。
########################################
# CodeBuild用のIAMロールの作成
########################################

resource "aws_iam_role" "codebuild_role" {
  name = "codebuild-service-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "codebuild.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

# CloudWatch Logs などにアクセスするためのポリシーを付与
resource "aws_iam_role_policy" "codebuild_policy" {
  name = "codebuild-policy"
  role = aws_iam_role.codebuild_role.id
  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}
EOF
}

########################################
# 非公開GitHubリポジトリ用の認証情報の登録
########################################
# ここではGitHubのパーソナルアクセストークンを利用する例です。
# トークンはTerraformの変数(例:github_token)などで安全に管理してください。

resource "aws_codebuild_source_credential" "github_credential" {
  auth_type   = "PERSONAL_ACCESS_TOKEN"
  token       = var.github_token
  server_type = "GITHUB"
}

########################################
# CodeBuildプロジェクトの作成(非公開リポジトリ対応)
########################################

resource "aws_codebuild_project" "unit_test_project" {
  name         = "unit-test-project"
  description  = "CodeBuildプロジェクト(非公開GitHubのPythonテストコード実行)"
  service_role = aws_iam_role.codebuild_role.arn

  # ここで特定のブランチを指定(例:failure_path ブランチ)
#   source_version = "refs/heads/failure_path"

  source {
    type      = "GITHUB"
    # 非公開リポジトリの場合も通常のリポジトリURLを指定します
    # 例:https://github.com/your-username/your-private-repo.git
    location  = var.github_url
    buildspec = <<EOF
version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.12
    commands:
      - echo "Installing dependencies..."
      - pip install -r requirements.txt
  build:
    commands:
      - echo "Running unit tests..."
      - python run_unittest.py
EOF
  }


  environment {
    compute_type = "BUILD_GENERAL1_SMALL"
    image        = "aws/codebuild/standard:7.0"
    type         = "LINUX_CONTAINER"
  }

  artifacts {
    type = "NO_ARTIFACTS"
  }
}

ブランチを切り替えてテスト

  • 上記のPythonコードをメインブランチにPushしている前提。terraform applyでCodeBuildをデプロイしているものとする。
  • failure_pathというブランチでは、単体テストが失敗する例としてメインコードを以下に切り替えてPushする。
# メインコード
def process_number(n):
    """
    入力値が奇数なら2乗、偶数なら2倍して返す関数
    """
    if n % 2 == 1:
        return n * n
    else:
        return n * 3 # ここを2から3に変更

成功時

失敗時

GitHub Actionsと同様で、エラーコードを上げて(exitのコードが0以外)プログラムを終了させれば自動的にビルドプロセスが失敗とされるようだ。別にunittestを使う必要はない。

所感

  • GitHub Actionsとどう使い分けるんだ問題はあるけど、ベースのDockerイメージやランタイムが豊富に用意されているんで結構使えそうな気がする
  • 認証情報をPATで渡すのがうーんという気はするけど、ちゃんとやりたかったらCodePipeline使えという話なのだろう。


Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です