关于 automated-testing
面向 ServiceNow 开发者、QA 工程师和管理员的 Automated Test Framework (ATF) 测试技能,涵盖测试套件、步骤、断言及运行结果分析。适用于开发验证、升级检查和回归测试。
主要功能
- 01创建测试套件与测试
- 02编排服务端、客户端和 UI 步骤
- 03编写断言并管理测试数据
- 04参数化及定时运行测试
- 05分析执行结果与失败步骤
- 06将测试接入 CI/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 的查询步骤配置
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 |
| Record - Query | 查询记录 | Record |
| Record - Update | 更新记录 | Record |
| Record - Delete | 删除记录 | Record |
| Open a new form | 打开空白表单 | Form |
| Open an existing record | 按 sys_id 打开 | Form |
| Set field value | 设置表单字段 | Form |
| Click a button | 按钮交互 | Form |
| Run Server Side Script | 自定义服务端代码 | Script |
| Impersonate | 更改用户上下文 | Security |
| Verify field value | 断言字段相等 | Assertion |
| Log Message | 写入输出 | Debug |
| Wait | 暂停执行 | Flow |
工具使用概要
| 操作 | 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 | 修改测试属性 |
最佳实践
测试设计
- 单一职责: 每个测试都应验证一个特定场景
- 独立性: 测试不应依赖其他测试的执行顺序
- 隔离性: 使用唯一的测试数据,避免与其他测试冲突
- 清理: 始终清理测试数据,即使测试失败也不例外
- 命名: 使用描述性名称:
[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 配置