霍格沃兹测试开发学社

《Python测试开发进阶训练营》(随到随学!)
2023年第2期《Python全栈开发与自动化测试班》(开班在即)
报名联系weixin/qq:2314507862

Playwright数据库断言:测试前后数据验证

关注 霍格沃兹测试学院公众号,回复「资料」, 领取人工智能测试开发技术合集

在自动化测试中,我们常常会遇到这样的场景:测试一个用户注册功能,接口返回了成功,但你真的确定用户数据正确写入数据库了吗?或者测试一个删除功能后,如何验证数据确实从数据库中移除了?这就是数据库断言的价值所在。

为什么需要数据库断言?
现代应用测试往往包含多个层次:UI测试、API测试和数据库验证。Playwright虽然主打UI自动化,但结合Node.js生态,我们可以轻松实现端到端的验证,包括数据库层。

让我通过一个实际案例展示如何将数据库断言集成到Playwright测试中。

实战:用户注册流程的数据库验证
假设我们正在测试一个用户注册流程,需要验证:

注册前,用户不存在于数据库中
注册后,用户信息正确写入数据库
用户密码已加密存储
第一步:建立数据库连接
首先,我们需要在Playwright测试项目中配置数据库连接。这里以PostgreSQL为例,但原理适用于任何数据库。

// utils/database.js
import pg from'pg';
const { Pool } = pg;

class DatabaseHelper {
constructor() {
this.pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME || 'test_db',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
max: 10, // 连接池最大连接数
idleTimeoutMillis: 30000
});
}

async query(sql, params = []) {
const client = awaitthis.pool.connect();
try {
const result = await client.query(sql, params);
return result.rows;
} finally {
client.release();
}
}

async close() {
awaitthis.pool.end();
}
}

exportdefault DatabaseHelper;
第二步:创建测试工具函数
// utils/testHelpers.js
import DatabaseHelper from'./database.js';

exportclass DBAssertions {
constructor() {
this.db = new DatabaseHelper();
}

/**

  • 验证用户是否存在
    */
    async userShouldNotExist(email) {
    const users = awaitthis.db.query(
    'SELECT * FROM users WHERE email = $1',
    [email]
    );
if (users.length > 0) {
  thrownewError(`用户 ${email} 不应该存在于数据库中,但找到了`);
}
returntrue;

}

/**

  • 验证用户存在且信息正确
    */
    async userShouldExist(userData) {
    const users = awaitthis.db.query(
    'SELECT * FROM users WHERE email = $1',
    [userData.email]
    );
if (users.length === 0) {
  thrownewError(`用户 ${userData.email} 应该存在于数据库中,但未找到`);
}

const user = users[0];

// 验证基本信息
if (user.username !== userData.username) {
  thrownewError(`用户名不匹配: 期望 "${userData.username}", 实际 "${user.username}"`);
}

// 验证密码已加密(不是明文)
if (user.password === userData.plainPassword) {
  thrownewError('密码未加密存储!');
}

// 验证加密密码格式(示例:bcrypt哈希)
if (!user.password.startsWith('$2b$') && !user.password.startsWith('$2a$')) {
  console.warn('密码可能未使用bcrypt加密');
}

return user;

}

/**

  • 清理测试数据
    */
    async cleanupTestUser(email) {
    try {
    awaitthis.db.query(
    'DELETE FROM users WHERE email = $1',
    [email]
    );
    console.log(已清理测试用户: ${email});
    } catch (error) {
    console.warn(清理用户时出错: ${error.message});
    }
    }

async close() {
awaitthis.db.close();
}
}
第三步:编写集成测试
现在,让我们将这些数据库断言集成到Playwright测试中。

// tests/register.spec.js
import { test, expect } from'@playwright/test';
import { DBAssertions } from'../utils/testHelpers.js';

// 使用测试钩子管理数据库连接
test.describe('用户注册流程', () => {
let dbAssertions;
const testUser = {
email: testuser_${Date.now()}@example.com,
username: testuser_${Date.now()},
plainPassword: 'Test123!@#'
};

// 测试前设置
test.beforeAll(async () => {
dbAssertions = new DBAssertions();
});

// 测试后清理
test.afterAll(async () => {
await dbAssertions.cleanupTestUser(testUser.email);
await dbAssertions.close();
});

test('新用户注册应正确写入数据库', async ({ page }) => {
// 步骤1:验证用户注册前不存在
await test.step('验证用户注册前不存在', async () => {
await expect(async () => {
await dbAssertions.userShouldNotExist(testUser.email);
}).not.toThrow();
});

// 步骤2:执行UI注册流程
await test.step('通过UI完成注册', async () => {
  await page.goto('/register');
  
  await page.fill('#email', testUser.email);
  await page.fill('#username', testUser.username);
  await page.fill('#password', testUser.plainPassword);
  await page.fill('#confirmPassword', testUser.plainPassword);
  
  await page.click('button[type="submit"]');
  
  // 等待注册成功提示
  await expect(page.locator('.success-message')).toBeVisible();
});

// 步骤3:验证数据库中的数据
await test.step('验证数据库中的数据完整性', async () => {
  // 添加短暂延迟,确保数据已持久化
  await page.waitForTimeout(500);
  
  const dbUser = await dbAssertions.userShouldExist(testUser);
  
  // 额外的验证:注册时间应该很近
  const registrationTime = newDate(dbUser.created_at);
  const now = newDate();
  const timeDiff = (now - registrationTime) / 1000; // 转换为秒
  
  expect(timeDiff).toBeLessThan(60); // 注册时间应该在1分钟内
  
  // 验证账户状态
  expect(dbUser.is_active).toBe(true);
  expect(dbUser.is_verified).toBe(false); // 新用户未验证
});

// 步骤4:验证UI状态与数据库一致
await test.step('验证UI反映正确的用户状态', async () => {
  await page.goto('/profile');
  
  // 从UI获取用户信息
  const uiUsername = await page.locator('.user-profile .username').textContent();
  const uiEmail = await page.locator('.user-profile .email').textContent();
  
  // 验证UI显示与数据库一致
  expect(uiUsername.trim()).toBe(testUser.username);
  expect(uiEmail.trim()).toBe(testUser.email);
});

});
});
人工智能技术学习交流群
伙伴们,对AI测试、大模型评测、质量保障感兴趣吗?我们建了一个 「人工智能测试开发交流群」,专门用来探讨相关技术、分享资料、互通有无。无论你是正在实践还是好奇探索,都欢迎扫码加入,一起抱团成长!期待与你交流!👇

image

高级技巧:处理异步数据写入
在某些情况下,数据库写入可能是异步的。下面是一个带重试机制的验证方法:

// utils/asyncVerification.js
exportasyncfunction verifyWithRetry(assertionFn, maxAttempts = 5, delay = 1000) {
let lastError;

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await assertionFn();
console.log(验证在第 ${attempt} 次尝试中成功);
return;
} catch (error) {
lastError = error;
if (attempt < maxAttempts) {
console.log(第 ${attempt} 次尝试失败,${delay}ms后重试...);
awaitnewPromise(resolve => setTimeout(resolve, delay));
}
}
}

thrownewError(验证失败,${maxAttempts}次尝试后: ${lastError.message});
}

// 在测试中的使用
test('验证异步数据写入', async () => {
// ... 执行某些操作 ...

await verifyWithRetry(
async () => {
const users = await db.query('SELECT * FROM orders WHERE user_id = $1', [userId]);
if (users.length === 0) {
thrownewError('订单尚未创建');
}
},
5, // 最多尝试5次
1000// 每次间隔1秒
);
});
最佳实践与注意事项
测试数据隔离:始终使用唯一标识(如时间戳、UUID)创建测试数据,避免测试间冲突。

清理策略:

test.afterEach(async () => {
// 清理本次测试创建的数据
await cleanupTestData();
});
连接池管理:避免为每个测试创建新连接,合理使用连接池。

敏感信息处理:永远不要在代码中硬编码数据库凭证,使用环境变量或密钥管理服务。

生产数据保护:确保测试不会在生产数据库上运行。在配置中强制区分环境:

if (process.env.NODE_ENV === 'production') {
throw new Error('禁止在生产环境运行测试!');
}
常见问题排查
连接超时:检查数据库服务器是否可访问,防火墙设置是否正确。

数据不同步:考虑添加适当的等待时间或实现重试逻辑。

性能问题:避免在测试中执行大量数据库操作,考虑使用事务或测试数据库。

测试失败分析:当测试失败时,提供足够的信息:

// 不好的错误信息
throw new Error('用户不存在');

// 好的错误信息
throw new Error(用户 ${email} 不存在,当前数据库用户: ${JSON.stringify(allUsers)});
数据库断言为你的Playwright测试提供了完整的验证链条。通过将UI操作、API响应和数据库状态验证结合起来,你可以构建更加可靠和全面的自动化测试。记住,好的测试不仅能发现UI问题,还能捕获数据层的潜在缺陷。

实践这些模式时,根据你的具体应用架构调整实现细节。不同的数据库、不同的ORM可能需要不同的处理方式,但核心思想是相通的:确保你的应用在各个层面都按预期工作。

推荐学习
人工智能测试开发线下周末私教训练营马上开营啦!!!内容全面升级,AI测试专家带队,实战落地智能化测试!覆盖大模型应用、RAG测试用例生成、视觉自动化测试等核心内容。私教陪跑+闯关式学习,搭配大厂真实案例,助你快速掌握AI测试开发技能。

image

posted @ 2026-01-19 16:10  霍格沃兹测试开发学社  阅读(0)  评论(0)    收藏  举报