automated-testing について
ServiceNow の開発者、QA エンジニア、管理者向けに、Automated Test Framework (ATF) の利用方法を扱うスキルです。テストスイートやステップ、検証、結果分析を通じて、開発中やアップグレード後のテストを支援します。
主な機能
- 01テストスイートとテストの作成
- 02サーバー、クライアント、UI ステップの構成
- 03検証とテストデータ管理
- 04テストのパラメーター化とスケジュール実行
- 05実行結果と失敗ステップの分析
- 06CI/CD へのテスト統合
活用シーン
- 01デプロイ前のプラットフォーム変更の検証
- 02開発中のテスト駆動検証
- 03アップグレード後の回帰テスト
MCP 対応
MCP ツールで ServiceNow の ATF レコードを作成・検索し、バックグラウンドスクリプトでテストを実行する方法を説明します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ happy-technologies-llc/ happy-platform-skills --skill automated-testingインストール後、Claude Code で/automated-testingを入力して読み込まれたか確認します。
詳細
自動テストフレームワーク(ATF)
概要
自動テストフレームワーク(ATF)は、プラットフォームの機能を検証するためのServiceNowネイティブのテストソリューションです。このスキルでは、以下について説明します。
- テストスイートの作成とテストの整理
- さまざまなステップタイプ(サーバー、クライアント、UI)を使ったテストの作成
- アサーションと検証の記述
- テストデータのセットアップとクリーンアップの管理
- パラメーター化テストの実装
- テストの手動実行とスケジュール実行
- テスト結果と失敗の分析
- ATFとCI/CDパイプラインの統合
- 保守しやすく信頼性の高いテストのベストプラクティス
使用するタイミング: 本番環境への変更のデプロイ前、開発中(TDD)、アップグレード後、回帰テストの一環として使用します。
対象ユーザー: プラットフォームの信頼性を確保する必要がある開発者、QAエンジニア、管理者。
前提条件
- ロール:
atf_test_admin(フルアクセス)またはatf_test_designer(テストの作成/編集) - プラグイン: Automated Test Framework (com.snc.automated_testing)
- アクセス権: sys_atf_test、sys_atf_test_suite、sys_atf_step テーブル
- 知識: ServiceNowスクリプト(GlideRecord、クライアントスクリプト)の基礎知識
- 関連スキル:
admin/script-execution、admin/update-set-management
ATFアーキテクチャ
┌─────────────────────────────────────────────────────────────┐
│ Test Suite │
│ (Groups related tests, runs in sequence or parallel) │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Test 1 │ │ Test 2 │ │ Test 3 │ │
│ │ (Scenario) │ │ (Scenario) │ │ (Scenario) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐ │
│ │ Steps │ │ Steps │ │ Steps │ │
│ │ 1. Setup │ │ 1. Setup │ │ 1. Setup │ │
│ │ 2. Action │ │ 2. Action │ │ 2. Action │ │
│ │ 3. Assert │ │ 3. Assert │ │ 3. Assert │ │
│ │ 4. Cleanup │ │ 4. Cleanup │ │ 4. Cleanup │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
主なテーブル
| テーブル | 用途 |
|---|---|
sys_atf_test_suite | テストスイートのコンテナー |
sys_atf_test | 個々のテスト定義 |
sys_atf_step | テスト内のテストステップ |
sys_atf_step_config | ステップタイプの設定 |
sys_atf_test_result | テスト実行結果 |
sys_atf_step_result | 個々のステップの結果 |
sys_atf_parameter | テストパラメーター |
sys_atf_variable | テスト変数(実行時データ) |
sys_atf_test_suite_test | スイートとテストの関連付け |
手順
フェーズ1:テストスイートの作成
ステップ1.1:テストスイートを作成する
関連するテストをスイートにまとめると、管理と実行が容易になります。
MCPを使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test_suite
data:
name: "Incident Management Tests"
description: "Comprehensive tests for incident creation, assignment, resolution, and closure workflows"
active: true
run_parallel: false
application: [app_sys_id] # Optional: for scoped apps
REST APIを使用:
POST /api/now/table/sys_atf_test_suite
Content-Type: application/json
{
"name": "Incident Management Tests",
"description": "Comprehensive tests for incident creation, assignment, resolution, and closure workflows",
"active": "true",
"run_parallel": "false"
}
ステップ1.2:既存のテストスイートを検索する
MCPを使用:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_suite
query: active=true
fields: sys_id,name,description,run_parallel,sys_updated_on
limit: 50
フェーズ2:テストの作成
ステップ2.1:基本的なテストを作成する
各テストは、検証対象となる特定のシナリオを表します。
MCPを使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test
data:
name: "Create P1 Incident and Verify Auto-Assignment"
description: "Tests that P1 incidents are automatically assigned to the Critical Incidents team"
active: true
type: test_script # test_script, browser, quick_start
テストタイプ:
| タイプ | 値 | 説明 |
|---|---|---|
| サーバーサイド | test_script | サーバーサイドJavaScriptのテスト |
| ブラウザー | browser | クライアントサイドUIのテスト |
| クイックスタート | quick_start | ガイド付きのテスト作成 |
ステップ2.2:テストをスイートに追加する
テストをテストスイートに関連付けます。
MCPを使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_test_suite_test
data:
test_suite: [suite_sys_id]
test: [test_sys_id]
order: 100 # Execution order (100, 200, 300...)
フェーズ3:テストステップの作成
ステップ3.1:サーバーサイドのテストステップ
サーバーサイドのステップでは、GlideRecord操作とサーバーサイドJavaScriptを実行します。
ステップ:レコードを作成
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Record - Insert"
inputs:
table: incident
fields:
short_description: "ATF Test - Server Outage P1"
description: "Automated test incident for validation"
priority: 1
category: hardware
subcategory: server
outputs:
record: inserted_incident # Variable name for reference
ステップ:レコードを検索
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
active: true
step_config: [step_config_sys_id] # "Record - Query"
inputs:
table: incident
query: number=${inserted_incident.number}
outputs:
record: queried_incident
ステップ:レコードを更新
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
active: true
step_config: [step_config_sys_id] # "Record - Update"
inputs:
record: ${inserted_incident}
fields:
state: 2 # In Progress
assigned_to: [user_sys_id]
work_notes: "ATF: Assigning for testing"
ステップ:サーバーサイドスクリプトを実行
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
inputs:
script: |
// Custom server-side validation
var gr = new GlideRecord('incident');
gr.get('${inserted_incident.sys_id}');
// Store result for assertion
outputs.actual_state = gr.state.toString();
outputs.assigned_group = gr.assignment_group.getDisplayValue();
outputs.is_valid = (gr.state == 2);
outputs:
actual_state: actual_state
assigned_group: assigned_group
is_valid: is_valid
ステップ3.2:クライアントサイドのテストステップ
クライアントサイドのステップでは、UIの動作とクライアントスクリプトをテストします。
ステップ:フォームを開く
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Open a new form"
inputs:
table: incident
ステップ:フィールド値を設定
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
active: true
step_config: [step_config_sys_id] # "Set field value"
inputs:
field: priority
value: 1 - Critical
ステップ:ボタンをクリック
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
active: true
step_config: [step_config_sys_id] # "Click a button"
inputs:
button_name: Submit
ステップ:フィールドの状態を検証
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
active: true
step_config: [step_config_sys_id] # "Field state validation"
inputs:
field: caller_id
is_mandatory: true
is_visible: true
is_readonly: false
ステップ3.3:UIテストステップ
UIステップでは、ServiceNowのインターフェイスを操作します。
ステップ:レコードを開く
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Open an existing record"
inputs:
table: incident
sys_id: ${inserted_incident.sys_id}
ステップ:モジュールに移動
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id] # "Navigate to a module"
inputs:
module: Incident > Create New
フェーズ4:アサーションと検証
ステップ4.1:基本的なアサーション
フィールド値をアサート
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify field value"
inputs:
record: ${inserted_incident}
field: state
expected_value: 2
operator: =
レコードの存在をアサート
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify record exists"
inputs:
table: incident
query: number=${inserted_incident.number}^active=true
レコード数をアサート
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Verify record count"
inputs:
table: task
query: parent=${inserted_incident.sys_id}
expected_count: 3
operator: >=
ステップ4.2:カスタムスクリプトによるアサーション
複雑な検証には、スクリプトによるアサーションを使用します。
アサーションを含むサーバーサイドスクリプトを実行
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
inputs:
script: |
// Complex assertion logic
var testPassed = true;
var messages = [];
// Get the incident
var gr = new GlideRecord('incident');
gr.get('${inserted_incident.sys_id}');
// Assertion 1: State validation
if (gr.state != 2) {
testPassed = false;
messages.push('Expected state 2, got ' + gr.state);
}
// Assertion 2: Assignment validation
if (gr.assignment_group.nil()) {
testPassed = false;
messages.push('Assignment group should not be empty for P1');
}
// Assertion 3: SLA attached
var sla = new GlideRecord('task_sla');
sla.addQuery('task', gr.sys_id);
sla.query();
if (!sla.hasNext()) {
testPassed = false;
messages.push('Expected SLA to be attached to P1 incident');
}
// Set outputs
outputs.test_passed = testPassed;
outputs.validation_messages = messages.join('; ');
// This will fail the step if assertions fail
if (!testPassed) {
throw new Error('Assertions failed: ' + outputs.validation_messages);
}
ステップ 4.3:アサーション演算子
| 演算子 | 説明 | 例 |
|---|---|---|
= | 等しい | state = 2 |
!= | 等しくない | state != 7 |
< | より小さい | priority < 3 |
<= | 以下 | priority <= 2 |
> | より大きい | age > 0 |
>= | 以上 | count >= 1 |
contains | 文字列を含む | description contains "error" |
starts with | 文字列の接頭辞 | number starts with "INC" |
ends with | 文字列の接尾辞 | email ends with "@company.com" |
is empty | null または空 | assigned_to is empty |
is not empty | 値がある | caller_id is not empty |
フェーズ 5:テストデータの管理
ステップ 5.1:テストデータのセットアップ
各テストの開始時にテストデータを作成します。
データセットアップステップを使用
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 10
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
description: "Setup: Create test data"
inputs:
script: |
// Create test user
var user = new GlideRecord('sys_user');
user.initialize();
user.user_name = 'atf_test_user_' + gs.generateGUID().substring(0, 8);
user.first_name = 'ATF';
user.last_name = 'Test User';
user.email = user.user_name + '@test.example.com';
user.active = true;
outputs.test_user_sys_id = user.insert();
outputs.test_user_name = user.user_name;
// Create test group
var group = new GlideRecord('sys_user_group');
group.initialize();
group.name = 'ATF Test Group ' + gs.generateGUID().substring(0, 8);
group.active = true;
outputs.test_group_sys_id = group.insert();
outputs.test_group_name = group.name;
gs.info('ATF: Created test user ' + outputs.test_user_name);
gs.info('ATF: Created test group ' + outputs.test_group_name);
ステップ 5.2:テストデータのクリーンアップ
テストデータが蓄積しないよう、必ずクリーンアップしてください。
クリーンアップステップを使用(テスト終了時)
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
active: true
step_config: [step_config_sys_id] # "Run Server Side Script"
description: "Cleanup: Remove test data"
inputs:
script: |
// Cleanup test incident
if ('${inserted_incident.sys_id}') {
var inc = new GlideRecord('incident');
if (inc.get('${inserted_incident.sys_id}')) {
inc.deleteRecord();
gs.info('ATF: Cleaned up test incident');
}
}
// Cleanup test user
if ('${test_user_sys_id}') {
var user = new GlideRecord('sys_user');
if (user.get('${test_user_sys_id}')) {
user.deleteRecord();
gs.info('ATF: Cleaned up test user');
}
}
// Cleanup test group
if ('${test_group_sys_id}') {
var group = new GlideRecord('sys_user_group');
if (group.get('${test_group_sys_id}')) {
group.deleteRecord();
gs.info('ATF: Cleaned up test group');
}
}
ステップ 5.3:再利用可能なデータセットアップ(Data Broker)
複数のシナリオで一貫したデータが必要なテストの場合:
Data Broker の Query ステップ設定
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: nameLIKEData Broker
fields: sys_id,name,description,category
フェーズ 6:パラメーター化テスト
ステップ 6.1:テストパラメーターの作成
パラメーターを使用すると、異なる入力で同じテストを実行できます。
MCP を使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: priority_value
label: "Priority Value"
default_value: 3
type: integer
hint: "Incident priority (1-5)"
複数のパラメーターを作成:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: category
label: "Category"
default_value: software
type: string
hint: "Incident category"
# Additional parameter
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_parameter
data:
test: [test_sys_id]
name: expected_sla_minutes
label: "Expected SLA (minutes)"
default_value: 60
type: integer
hint: "Expected SLA resolution time"
ステップ 6.2:ステップでパラメーターを使用
${} 構文を使用してパラメーターを参照します。
レコード挿入でパラメーターを使用
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
active: true
step_config: [step_config_sys_id]
description: "Create incident with parameterized values"
inputs:
table: incident
fields:
short_description: "ATF Parameterized Test"
priority: ${priority_value}
category: ${category}
アサーションでパラメーターを使用
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
active: true
step_config: [step_config_sys_id]
description: "Verify SLA meets expected time"
inputs:
script: |
var sla = new GlideRecord('task_sla');
sla.addQuery('task', '${inserted_incident.sys_id}');
sla.query();
if (sla.next()) {
var plannedMinutes = sla.planned_end_time.dateNumericValue() -
sla.start_time.dateNumericValue();
plannedMinutes = plannedMinutes / (1000 * 60);
outputs.actual_sla_minutes = Math.round(plannedMinutes);
outputs.expected_sla_minutes = ${expected_sla_minutes};
if (plannedMinutes > ${expected_sla_minutes}) {
throw new Error('SLA ' + plannedMinutes + ' mins exceeds expected ' +
${expected_sla_minutes} + ' mins');
}
}
フェーズ 7:テストの実行
ステップ 7.1:手動実行
単一のテストを実行:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var runner = new sn_atf.ATFTestRunner();
runner.setTest('[test_sys_id]');
var resultId = runner.run();
gs.info('ATF: Test execution started. Result ID: ' + resultId);
description: Run ATF test manually
テストスイートを実行:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var suiteRunner = new sn_atf.ATFTestSuiteRunner();
suiteRunner.setSuite('[suite_sys_id]');
var resultId = suiteRunner.run();
gs.info('ATF: Suite execution started. Result ID: ' + resultId);
description: Run ATF test suite
ステップ 7.2:スケジュール実行
テストを定期的に実行するスケジュール済みジョブを作成します。
MCP を使用:
Tool: SN-Create-Record
Parameters:
table_name: sysauto_script
data:
name: "Nightly ATF Regression Suite"
active: true
run_type: daily
time: "02:00:00"
script: |
// Run regression test suite nightly
var suiteRunner = new sn_atf.ATFTestSuiteRunner();
suiteRunner.setSuite('[regression_suite_sys_id]');
// Optional: Set parameters
suiteRunner.setParameter('environment', 'nightly');
var resultId = suiteRunner.run();
// Log result
gs.info('Nightly regression started. Result: ' + resultId);
// Optional: Send notification on completion
// (handled by ATF result business rules)
ステップ 7.3:偽装して実行
異なるユーザーとしてテストし、ロールベースのアクセスを検証します。
MCP を使用:
Tool: SN-Execute-Background-Script
Parameters:
script: |
var runner = new sn_atf.ATFTestRunner();
runner.setTest('[test_sys_id]');
runner.setImpersonateUser('[user_sys_id]'); // Run as this user
var resultId = runner.run();
gs.info('ATF: Test running as impersonated user. Result: ' + resultId);
description: Run ATF test with user impersonation
フェーズ 8:テスト結果の分析
ステップ 8.1:テスト結果のクエリ
最新のテスト結果を取得:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_result
query: test=[test_sys_id]^ORDERBYDESCsys_created_on
fields: sys_id,status,start_time,end_time,duration,message
limit: 10
スイート実行結果を取得:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_test_suite_result
query: test_suite=[suite_sys_id]^ORDERBYDESCsys_created_on
fields: sys_id,status,start_time,end_time,test_count,pass_count,fail_count,skip_count
limit: 5
ステップ 8.2:失敗したステップを分析
失敗したテストのステップ単位の結果を取得:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_result
query: test_result=[test_result_sys_id]^status=failure
fields: sys_id,step_config,status,message,output
limit: 50
ステップ 8.3:テストメトリクスの集計
MCP を使用:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Aggregate test metrics for the last 7 days
var startDate = gs.daysAgo(7);
var metrics = {
total: 0,
passed: 0,
failed: 0,
skipped: 0,
error: 0,
passRate: 0,
avgDuration: 0
};
var ga = new GlideAggregate('sys_atf_test_result');
ga.addQuery('sys_created_on', '>', startDate);
ga.addAggregate('COUNT');
ga.addAggregate('COUNT', 'status');
ga.addAggregate('AVG', 'duration');
ga.groupBy('status');
ga.query();
while (ga.next()) {
var status = ga.status.toString();
var count = parseInt(ga.getAggregate('COUNT'));
metrics.total += count;
if (status === 'success') metrics.passed = count;
else if (status === 'failure') metrics.failed = count;
else if (status === 'skipped') metrics.skipped = count;
else metrics.error += count;
}
metrics.passRate = metrics.total > 0 ?
Math.round((metrics.passed / metrics.total) * 100) : 0;
gs.info('ATF Metrics (Last 7 Days): ' + JSON.stringify(metrics, null, 2));
description: Generate ATF test metrics
フェーズ 9:CI/CD 統合
ステップ 9.1:CI/CD 用 REST API
ServiceNow は、ATF 統合用の REST API を提供しています。
REST 経由でテストスイートを開始:
POST /api/sn_cicd/testsuite/run
Content-Type: application/json
{
"test_suite_sys_id": "[suite_sys_id]",
"browser_name": "Chrome",
"browser_version": "latest"
}
テストの進行状況を確認:
GET /api/sn_cicd/progress/[result_id]
テスト結果を取得:
GET /api/sn_cicd/testsuite/results/[result_id]
ステップ 9.2:Jenkins との統合
Jenkins パイプラインの例:
pipeline {
agent any
environment {
SN_INSTANCE = 'https://dev123.service-now.com'
SN_CREDENTIALS = credentials('servicenow-api')
}
stages {
stage('Run ATF Tests') {
steps {
script {
// Start test suite
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/testsuite/run",
httpMode: 'POST',
authentication: 'servicenow-api',
contentType: 'APPLICATION_JSON',
requestBody: """
{
"test_suite_sys_id": "${params.TEST_SUITE_ID}",
"browser_name": "Chrome"
}
"""
)
def result = readJSON text: response.content
env.RESULT_ID = result.result.sys_id
}
}
}
stage('Wait for Results') {
steps {
script {
def status = 'running'
while (status == 'running') {
sleep 30
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/progress/${env.RESULT_ID}",
authentication: 'servicenow-api'
)
def progress = readJSON text: response.content
status = progress.result.status
}
}
}
}
stage('Evaluate Results') {
steps {
script {
def response = httpRequest(
url: "${SN_INSTANCE}/api/sn_cicd/testsuite/results/${env.RESULT_ID}",
authentication: 'servicenow-api'
)
def results = readJSON text: response.content
if (results.result.status != 'success') {
error "ATF tests failed: ${results.result.fail_count} failures"
}
}
}
}
}
}
ステップ 9.3:GitHub Actions との統合
GitHub Actions ワークフロー:
name: ServiceNow ATF Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
atf-tests:
runs-on: ubuntu-latest
steps:
- name: Run ATF Test Suite
id: run-tests
run: |
RESPONSE=$(curl -s -X POST \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
-H "Content-Type: application/json" \
-d '{"test_suite_sys_id":"${{ vars.ATF_SUITE_ID }}"}' \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/testsuite/run")
RESULT_ID=$(echo $RESPONSE | jq -r '.result.sys_id')
echo "result_id=$RESULT_ID" >> $GITHUB_OUTPUT
- name: Wait for Completion
run: |
STATUS="running"
while [ "$STATUS" = "running" ]; do
sleep 30
RESPONSE=$(curl -s \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/progress/${{ steps.run-tests.outputs.result_id }}")
STATUS=$(echo $RESPONSE | jq -r '.result.status')
echo "Test status: $STATUS"
done
- name: Get Results
run: |
RESPONSE=$(curl -s \
-u "${{ secrets.SN_USERNAME }}:${{ secrets.SN_PASSWORD }}" \
"${{ vars.SN_INSTANCE }}/api/sn_cicd/testsuite/results/${{ steps.run-tests.outputs.result_id }}")
STATUS=$(echo $RESPONSE | jq -r '.result.status')
PASS=$(echo $RESPONSE | jq -r '.result.pass_count')
FAIL=$(echo $RESPONSE | jq -r '.result.fail_count')
echo "Results: $PASS passed, $FAIL failed"
if [ "$STATUS" != "success" ]; then
echo "::error::ATF tests failed"
exit 1
fi
フェーズ 10:高度なパターン
ステップ 10.1:UI テストのページオブジェクトパターン
一般的な UI 操作用の再利用可能なステップ設定を作成します。
利用可能なステップ設定をクエリ:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: active=true
fields: sys_id,name,category,description
limit: 100
一般的なステップ設定:
| カテゴリ | ステップ設定 | 目的 |
|---|---|---|
| レコード | Record - Insert | 新しいレコードを作成 |
| レコード | Record - Query | 既存のレコードを検索 |
| レコード | Record - Update | レコードを変更 |
| レコード | Record - Delete | レコードを削除 |
| フォーム | Open a new form | 新しいフォームに移動 |
| フォーム | Open an existing record | レコードに移動 |
| フォーム | Set field value | フィールドに値を入力 |
| フォーム | Click a button | ボタンのアクションを実行 |
| アサーション | Verify field value | フィールドの内容を検証 |
| スクリプト | Run Server Side Script | カスタムロジックを実行 |
ステップ 10.2:テストデータファクトリーパターン
テストデータを生成するためのスクリプトインクルードを作成します。
MCP を使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_script_include
data:
name: ATFTestDataFactory
api_name: global.ATFTestDataFactory
active: true
client_callable: false
description: "Factory for creating consistent ATF test data"
script: |
var ATFTestDataFactory = Class.create();
ATFTestDataFactory.prototype = {
initialize: function() {
this.createdRecords = [];
},
createTestIncident: function(options) {
options = options || {};
var gr = new GlideRecord('incident');
gr.initialize();
gr.short_description = options.short_description ||
'ATF Test Incident ' + gs.generateGUID().substring(0, 8);
gr.description = options.description || 'Created by ATF Test';
gr.priority = options.priority || 3;
gr.category = options.category || 'software';
gr.caller_id = options.caller_id || gs.getUserID();
var sysId = gr.insert();
this.createdRecords.push({ table: 'incident', sys_id: sysId });
return sysId;
},
createTestUser: function(options) {
options = options || {};
var username = 'atf_' + gs.generateGUID().substring(0, 8);
var gr = new GlideRecord('sys_user');
gr.initialize();
gr.user_name = options.user_name || username;
gr.first_name = options.first_name || 'ATF';
gr.last_name = options.last_name || 'User';
gr.email = options.email || username + '@test.example.com';
gr.active = true;
var sysId = gr.insert();
this.createdRecords.push({ table: 'sys_user', sys_id: sysId });
return sysId;
},
createTestGroup: function(options) {
options = options || {};
var gr = new GlideRecord('sys_user_group');
gr.initialize();
gr.name = options.name || 'ATF Group ' + gs.generateGUID().substring(0, 8);
gr.active = true;
var sysId = gr.insert();
this.createdRecords.push({ table: 'sys_user_group', sys_id: sysId });
return sysId;
},
cleanup: function() {
// Delete in reverse order to handle dependencies
for (var i = this.createdRecords.length - 1; i >= 0; i--) {
var record = this.createdRecords[i];
var gr = new GlideRecord(record.table);
if (gr.get(record.sys_id)) {
gr.deleteRecord();
}
}
this.createdRecords = [];
},
type: 'ATFTestDataFactory'
};
テストでファクトリーを使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 10
step_config: [run_script_config_sys_id]
description: "Setup: Create test data using factory"
inputs:
script: |
var factory = new ATFTestDataFactory();
// Create test data
outputs.incident_sys_id = factory.createTestIncident({
priority: 1,
short_description: 'Critical Server Issue'
});
outputs.user_sys_id = factory.createTestUser({
first_name: 'Test',
last_name: 'Admin'
});
// Store factory for cleanup
outputs.factory = factory;
ステップ 10.3:条件付きテスト実行
条件に基づいてテストをスキップします。
MCP を使用:
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 5
step_config: [run_script_config_sys_id]
description: "Pre-check: Verify test prerequisites"
inputs:
script: |
// Check if required plugin is active
var plugin = new GlideRecord('v_plugin');
plugin.addQuery('id', 'com.snc.change_management');
plugin.addQuery('state', 'active');
plugin.query();
if (!plugin.hasNext()) {
// Skip this test if plugin not installed
outputs.skip_test = true;
outputs.skip_reason = 'Change Management plugin not active';
// This will mark test as skipped, not failed
stepResult.setStatus('skipped');
stepResult.setMessage('Prerequisites not met: ' + outputs.skip_reason);
}
共通のステップ設定
利用可能なすべてのステップタイプを照会:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_step_config
query: active=true
fields: sys_id,name,category,description,inputs
limit: 200
必須のステップ設定:
| sys_id(変動) | 名前 | カテゴリ |
|---|---|---|
| Record - Insert | 新しいレコードを作成 | レコード |
| Record - Query | レコードを照会 | レコード |
| Record - Update | レコードを更新 | レコード |
| Record - Delete | レコードを削除 | レコード |
| Open a new form | 空のフォームを開く | フォーム |
| Open an existing record | sys_id で開く | フォーム |
| Set field value | フォームフィールドを設定 | フォーム |
| Click a button | ボタンを操作 | フォーム |
| Run Server Side Script | カスタムサーバーコード | スクリプト |
| Impersonate | ユーザーコンテキストを変更 | セキュリティ |
| Verify field value | フィールドの値が一致することを検証 | アサーション |
| Log Message | 出力に書き込む | デバッグ |
| Wait | 実行を一時停止 | フロー |
ツールの使用概要
| 操作 | MCP ツール | 目的 |
|---|---|---|
| テストスイートを作成 | SN-Create-Record | sys_atf_test_suite を作成 |
| テストを作成 | SN-Create-Record | sys_atf_test を作成 |
| ステップを作成 | SN-Create-Record | sys_atf_step を作成 |
| パラメーターを追加 | SN-Create-Record | sys_atf_parameter を作成 |
| 結果を照会 | SN-Query-Table | sys_atf_test_result を読み取る |
| テストを実行 | SN-Execute-Background-Script | テスト実行をトリガー |
| ステップ設定を取得 | SN-Query-Table | sys_atf_step_config を一覧表示 |
| テストを更新 | SN-Update-Record | テストのプロパティを変更 |
ベストプラクティス
テスト設計
- 単一責任: 各テストでは、1 つの特定のシナリオを検証します
- 独立性: テストは、ほかのテストの実行順序に依存しないようにします
- 分離: ほかのテストとの競合を防ぐため、一意のテストデータを使用します
- クリーンアップ: テストが失敗した場合も含め、必ずテストデータをクリーンアップします
- 命名: 内容がわかる名前を使用します:
[Feature]_[Scenario]_[ExpectedResult]
テストデータ
- 一意のデータを生成: テストデータ名には GUID またはタイムスタンプを使用します
- 本番データを避ける: テストで実際のユーザーデータを参照しないでください
- 最小限のデータ: テストに必要なデータのみを作成します
- データファクトリーパターン: 一貫したデータ作成にはスクリプトインクルードを使用します
アサーション
- 具体的なアサーション: 「空でない」ことだけでなく、具体的な値をアサートします
- 複数のチェック: 結果に関する重要な側面をすべて検証します
- 意味のあるメッセージ: アサーション失敗メッセージにコンテキストを含めます
- 否定テスト: 無効な操作が正しく拒否されることをテストします
パフォーマンス
- クエリを制限: setLimit() と効率的なクエリを使用します
- バッチ操作: 類似の操作をまとめて実行します
- 可能な限り UI テストを避ける: サーバー側のテストはより高速で、信頼性も高くなります
- テストスイートを並列実行: 独立したテストスイートを並列実行します
メンテナンス
- バージョン管理: テストを更新セットに含めます
- ドキュメント: テストとステップに説明を追加します
- リファクタリング: 要件の変更に応じてテストを更新します
- 定期的な実行: 問題を早期に検出するため、テストを頻繁に実行します
トラブルシューティング
テストが開始されない
症状: テストのステータスが「Pending」のまま変わらない 原因:
- ATF Runner が設定されていない
- UI テスト用のテストクライアントがない
- テストスケジューラーが無効になっている 解決策:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_schedule
query: active=true
fields: sys_id,name,state,last_run
UI テストがランダムに失敗する
症状: 同じ UI テストが成功したり、断続的に失敗したりする 原因:
- ページ読み込みのタイミングの問題
- 実行間で DOM が変更される
- ブラウザーのバージョンの違い 解決策:
- UI 操作の前に Wait ステップを追加します
- 安定した要素セレクターを使用します
- テスト設定でブラウザーのバージョンを指定します
ステップ変数が解決されない
症状: ${variable} が出力にそのまま表示される
原因:
- 前のステップで変数が設定されていない
- 変数名の入力ミス
- ステップの順序が正しくない 解決策:
Tool: SN-Query-Table
Parameters:
table_name: sys_atf_variable
query: test=[test_sys_id]
fields: name,step,value
テストデータがクリーンアップされない
症状: インスタンス内にテストデータが蓄積される 原因:
- クリーンアップステップが追加されていない
- クリーンアップステップが完了前に失敗した
- クリーンアップ前にテストが停止した 解決策:
- クリーンアップスクリプトで try-finally パターンを使用します
- 孤立したデータを対象とするスケジュール済みクリーンアップジョブを作成します:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Clean up orphaned ATF test data
var tables = ['incident', 'sys_user', 'sys_user_group'];
var cutoff = gs.daysAgo(1); // Older than 1 day
tables.forEach(function(table) {
var gr = new GlideRecord(table);
gr.addQuery('short_description', 'STARTSWITH', 'ATF');
gr.addQuery('sys_created_on', '<', cutoff);
gr.query();
var count = 0;
while (gr.next()) {
gr.deleteRecord();
count++;
}
if (count > 0) {
gs.info('Cleaned up ' + count + ' orphaned records from ' + table);
}
});
description: Clean orphaned ATF test data
CI/CD パイプラインのタイムアウト
症状: テスト完了を待機中にパイプラインが失敗する 原因:
- テストスイートの実行に時間がかかりすぎる
- ServiceNow インスタンスのパフォーマンスに問題がある
- ネットワーク接続に問題がある 解決策:
- パイプラインに適切なタイムアウトを設定する
- 大規模なテストスイートを小規模なものに分割する
- テストを並列実行する
例
例 1:インシデントのライフサイクル全体のテスト
Tool: SN-Create-Record (Test)
Parameters:
table_name: sys_atf_test
data:
name: "Incident_Lifecycle_CreateToClose"
description: "Validates complete incident workflow from creation to closure"
active: true
# Step 1: Create Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
step_config: [record_insert_config]
inputs:
table: incident
fields:
short_description: ATF Lifecycle Test
priority: 3
category: software
outputs:
record: test_incident
# Step 2: Assert Created
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
step_config: [verify_field_config]
inputs:
record: ${test_incident}
field: state
expected_value: 1
operator: =
# Step 3: Assign Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
step_config: [record_update_config]
inputs:
record: ${test_incident}
fields:
state: 2
assigned_to: [admin_sys_id]
# Step 4: Resolve Incident
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
step_config: [record_update_config]
inputs:
record: ${test_incident}
fields:
state: 6
resolution_code: Solved (Permanently)
resolution_notes: ATF Test Resolution
# Step 5: Assert Resolved
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
step_config: [verify_field_config]
inputs:
record: ${test_incident}
field: state
expected_value: 6
# Step 6: Cleanup
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
step_config: [record_delete_config]
inputs:
record: ${test_incident}
例 2:ロールベースのアクセス制御テスト
# Test that ITIL users can update incidents but not delete them
# Step 1: Create test incident as admin
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 100
step_config: [record_insert_config]
inputs:
table: incident
fields:
short_description: ATF RBAC Test
outputs:
record: test_incident
# Step 2: Impersonate ITIL user
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 200
step_config: [impersonate_config]
inputs:
user: [itil_user_sys_id]
# Step 3: Update should succeed
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 300
step_config: [run_script_config]
inputs:
script: |
var gr = new GlideRecord('incident');
gr.get('${test_incident.sys_id}');
gr.work_notes = 'ITIL user update test';
var result = gr.update();
if (!result) {
throw new Error('ITIL user should be able to update incidents');
}
outputs.update_succeeded = true;
# Step 4: Delete should fail
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 400
step_config: [run_script_config]
inputs:
script: |
var gr = new GlideRecord('incident');
gr.get('${test_incident.sys_id}');
try {
var deleted = gr.deleteRecord();
if (deleted) {
throw new Error('ITIL user should NOT be able to delete incidents');
}
} catch (e) {
// Expected - delete should fail
outputs.delete_blocked = true;
}
# Step 5: Stop impersonation
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 500
step_config: [impersonate_config]
inputs:
stop_impersonation: true
# Step 6: Cleanup as admin
Tool: SN-Create-Record
Parameters:
table_name: sys_atf_step
data:
test: [test_sys_id]
order: 9999
step_config: [record_delete_config]
inputs:
record: ${test_incident}
関連スキル
admin/script-execution- ATF のバックグラウンドスクリプトのパターンadmin/update-set-management- テストのバージョン管理admin/deployment-workflow- インスタンス間でのテストのデプロイsecurity/acl-management- ACL 設定のテスト