ジャンルから探す

QA English

Python活用でQA業務を効率化する5つの方法

QA業務でPythonが効くのは、UIテスト自動化・APIテスト・ログ解析・負荷テスト・CI連携の5領域です。どれも「手作業では回数をこなせない」ことが共通しています。

先に方針を書きます。5つを一度に導入しようとすると必ず失敗します。 効果が出るまでが早い順に、1つずつ足していくのが現実的です。この記事では、それぞれについてそのまま動くコードと、実務で詰まる点を書きます。

スポンサーリンク

QA業務のどこに効くのか

課題 使うもの 導入の重さ
回帰テストの負担が増え続ける Playwright / Selenium 重い
API変更で抜け漏れが出る pytest + requests 軽い
エラーログの目視確認に時間がかかる pandas 軽い
負荷テストが実施できない Locust
テストが手元でしか動かない GitHub Actions / Jenkins

最初に手を付けるなら API テストかログ解析です。 どちらも数十行で書けて、その日から効果が出ます。UIテストは効果も大きいですが、保守コストが高いので後回しで構いません。

1. UIテスト自動化:Playwright を優先する

これから新規に書くなら Playwright を推奨します。 待機処理が自動で入るため、Selenium で頻発する「通ったり落ちたりするテスト」がかなり減ります。

項目 Selenium Playwright
待機処理 自分で書く(WebDriverWait 自動で待つ
ブラウザの用意 Selenium Manager が自動化 playwright install
失敗時の調査 自分でスクショを実装 トレース機能が標準
既存資産 情報量が多い 比較的新しい

Playwright の例

pip install pytest-playwright
playwright install chromium
from playwright.sync_api import Page, expect

def test_login(page: Page):
    page.goto("https://example.com/login")

    page.get_by_label("メールアドレス").fill("user@example.com")
    page.get_by_label("パスワード").fill("password")
    page.get_by_role("button", name="ログイン").click()

    # 要素が現れるまで自動で待つ
    expect(page.get_by_role("heading", name="マイページ")).to_be_visible()

get_by_roleget_by_label を使うのがポイントです。CSSクラス名で要素を探すと、デザイン変更のたびにテストが壊れます。ユーザーから見える情報(役割・ラベル)で探すと、実装が変わっても壊れにくくなります。

Selenium の例

既存資産がある場合は Selenium のままでも問題ありません。ただし待機処理と後片付けは必須です。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")

driver = webdriver.Chrome(options=options)

try:
    driver.get("https://example.com")

    button = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "[data-testid='submit']"))
    )
    button.click()

    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid='result']"))
    )
finally:
    driver.quit()   # 失敗してもブラウザを閉じる

time.sleep() は使わないでください。 速い環境では無駄に待ち、遅い環境では足りません。Selenium・Appium・Jenkins の詳細はQAのテスト自動化にまとめています。

スポンサーリンク

2. APIテスト:pytest + requests

導入が最も軽く、効果が出るのも早い領域です。 数十行でリグレッション検出の仕組みができます。

pip install pytest requests
import pytest
import requests

BASE_URL = "https://jsonplaceholder.typicode.com"

@pytest.fixture(scope="session")
def session():
    s = requests.Session()
    s.headers.update({"Accept": "application/json"})
    yield s
    s.close()

def test_get_post(session):
    res = session.get(f"{BASE_URL}/posts/1", timeout=10)

    assert res.status_code == 200

    data = res.json()
    assert data["id"] == 1
    assert isinstance(data["userId"], int)   # 型も確認する
    assert data["title"]                     # 空文字でないこと

@pytest.mark.parametrize("post_id,expected", [
    (1, 200),
    (100, 200),
    (99999, 404),   # 存在しないID
])
def test_status_codes(session, post_id, expected):
    res = session.get(f"{BASE_URL}/posts/{post_id}", timeout=10)
    assert res.status_code == expected

押さえるべき点が3つあります。

  • timeout を必ず指定する:付けないと応答が返らないときテストが永久に止まります
  • 存在だけでなく型も確認するuserId が数値のはずが文字列で返る、という不具合は assert "userId" in data では検出できません
  • parametrize で境界値をまとめる同値分割と境界値分析で洗い出した値をそのまま並べられます

Postman のコレクションを CI で動かす場合

Postman でシナリオを作ってある場合、pytest に書き直す必要はありません。 Newman を使えばそのまま実行できます。

npm install -g newman

newman run collection.json \
  -e environment.json \
  --reporters cli,junit \
  --reporter-junit-export results.xml

Postman 側のスクリプトの書き方はPostmanのPre-requestとPost-responseにまとめました。

「Postman で書くか pytest で書くか」は、チームの状況で決めてください。 非エンジニアもテストを書くなら Postman、コードレビューに載せたいなら pytest が向きます。

3. ログ解析:pandas で目視をやめる

エラーログを目で追う作業は、真っ先に自動化すべきです。

import pandas as pd

df = pd.read_csv("logs.csv", parse_dates=["timestamp"], encoding="utf-8-sig")

# エラーだけに絞る
errors = df[df["level"] == "ERROR"]

# 多い順に集計する
counts = errors["error_message"].value_counts()
print(counts.head(10))

# 時間帯別の発生数を見る
by_hour = errors.set_index("timestamp").resample("1h").size()
print(by_hour[by_hour > 0])

resample() で時間帯別に集計するのが有効です。 「特定の時刻に集中している」と分かれば、バッチ処理やデプロイとの関連を疑えます。総数だけ見ていても気づけません。

可視化するときは日本語フォントに注意

設定しないと、日本語のラベルが四角(□)になります。

pip install matplotlib matplotlib-fontja
import matplotlib.pyplot as plt
import matplotlib_fontja   # インポートするだけで日本語が通る

fig, ax = plt.subplots(figsize=(10, 5))
counts.head(10).sort_values().plot(kind="barh", ax=ax)

ax.set_title("エラーメッセージ 上位10件")
ax.set_xlabel("発生件数")

plt.tight_layout()
fig.savefig("errors.png", dpi=150, bbox_inches="tight")

2つコツがあります。

  • 横棒グラフ(barh)にする:エラーメッセージは長いので、縦棒だとラベルが読めません
  • sort_values() してから描く:並び順がバラバラだと比較になりません

グラフ描画の詳細はPandasとMatplotlibを活用したデータ可視化にまとめています。

異常検知の簡単な方法

統計的なしきい値を使うと、「いつもと違う」を機械的に拾えます。

# 直近の平均と標準偏差から外れ値を出す
mean = by_hour.mean()
std = by_hour.std()
threshold = mean + 3 * std

spikes = by_hour[by_hour > threshold]

if not spikes.empty:
    print("異常なエラー増加を検出しました")
    print(spikes)

固定のしきい値(「100件を超えたら通知」など)は運用が続きません。 アクセス数が増えればエラー数も増えるためです。平均からの乖離で見る方が保守が楽です。

スポンサーリンク

4. 負荷テスト:Locust

Python でシナリオを書けるのが利点です。JMeter の GUI より、ユーザーの行動を再現しやすくなります。

pip install locust
from locust import HttpUser, task, between

class WebsiteUser(HttpUser):
    # 各アクションの間に1〜3秒の間隔を空ける
    wait_time = between(1, 3)

    def on_start(self):
        """各仮想ユーザーの開始時に1回だけ実行される"""
        res = self.client.post("/api/login", json={
            "email": "user@example.com",
            "password": "password",
        })
        self.token = res.json().get("token")

    @task(3)   # 重み3:他より3倍多く実行される
    def view_top(self):
        self.client.get("/", name="トップページ")

    @task(1)
    def search(self):
        self.client.get("/search?q=test", name="検索")

    @task(1)
    def view_detail(self):
        # URLが可変でも name を揃えると集計が1行にまとまる
        self.client.get("/items/123", name="/items/[id]")

重要なのは3点です。

  • wait_time を必ず設定する:省略すると仮想ユーザーが休みなくリクエストを送り、現実にはあり得ない負荷になります
  • name で集計をまとめる/items/123/items/456 が別項目として集計されると、結果が読めません
  • @task の重みで実際の利用比率に寄せる:全ページを均等に叩いても実態を反映しません

CI から実行する場合は、GUI を使わずヘッドレスで動かします。

locust -f locustfile.py \
  --headless \
  --users 100 \
  --spawn-rate 10 \
  --run-time 3m \
  --host https://staging.example.com \
  --html report.html

--spawn-rate(1秒あたりの増加人数)をユーザー数と同じにしないでください。 全員が同時に接続する状況は現実には起きず、測定結果が実態とかけ離れます。

結果の読み方

平均レスポンスタイムだけを見てはいけません。

指標 見方
平均 参考程度。外れ値に引っ張られる
95%タイル 実質的な体感速度。ここを基準にする
失敗率 0%でなければ原因を特定する
RPS 頭打ちになる地点がシステムの限界

平均250msでも、95%タイルが3秒なら20人に1人が3秒待っています。

また、負荷試験は必ず許可された環境で実施してください。 本番環境や、自分に権限のないサービスに負荷をかけてはいけません。

5. CI との統合

手元で実行しているうちは、自動テストの価値は半分以下です。 「変更のたびに自動で走り、落ちたら気づける」状態にして初めて効果が出ます。

GitHub Actions の例

name: API Test

on:
  pull_request:
  schedule:
    - cron: '0 0 * * *'   # 毎日実行する

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
          cache: 'pip'

      - run: pip install -r requirements.txt

      - run: pytest tests/ --junitxml=results.xml
        env:
          API_TOKEN: ${{ secrets.API_TOKEN }}

      - name: テスト結果をアップロード
        if: always()      # 失敗しても結果を残す
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: |
            results.xml
            screenshots/

2点が重要です。

  • if: always() を付ける:これが無いと、テストが失敗したときに結果ファイルが残らず、原因を調べられません
  • 認証情報は secrets から渡す:コードに直書きしない

UIテストでは失敗時のスクリーンショットを残す

# conftest.py
import pytest
from pathlib import Path

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()

    if report.when == "call" and report.failed:
        page = item.funcargs.get("page")
        if page:
            Path("screenshots").mkdir(exist_ok=True)
            page.screenshot(path=f"screenshots/{item.name}.png", full_page=True)

これが無いと、CIで落ちたUIテストの原因はほぼ分かりません。 ログだけでは画面の状態が再現できないためです。

導入する順番

一度に全部やろうとすると、どれも中途半端になります。効果が出るまでが早い順に進めてください。

  • 1. ログ解析:数十行で書けて、その日から効く。誰の合意も要らない
  • 2. APIテスト:UIより壊れにくく、保守が軽い。主要エンドポイントから
  • 3. CI連携:ここまでを自動実行に載せる
  • 4. UIテスト:主要導線を3〜5本だけ。網羅は狙わない
  • 5. 負荷テスト:必要になったタイミングで

最も避けたいのは「落ちても誰も見ないテスト」です。 そうなった時点で、あるだけ負債になります。本数を増やすより、落ちたら必ず対応される状態を先に作ってください。

まとめ

  • 新規のUIテストは Playwright を優先。get_by_role で要素を探すと壊れにくい
  • Selenium を使うなら WebDriverWaittry/finally が必須。time.sleep() は使わない
  • APIテストは timeout を必ず指定し、存在だけでなく型も確認する
  • Postman の資産があるなら Newman でそのまま CI に載せる。書き直さなくてよい
  • ログは resample() で時間帯別に見る。グラフは matplotlib-fontja で日本語対応
  • 異常検知は固定しきい値ではなく平均からの乖離
  • Locust は wait_timename を必ず設定。95%タイルで評価する
  • CI では if: always() で結果を残し、失敗時のスクリーンショットを保存する
  • 導入順はログ解析 → APIテスト → CI連携 → UIテスト → 負荷テスト

ツール選定より前に、「何を守りたいか」を決めるのが先です。その判断軸はE2Eで「守るべきもの」はこう決めるにまとめています。