测试概览
测试是数据库开发的关键环节,特别是在使用行级安全(RLS)策略等功能时。本指南提供了测试Supabase数据库的全面方法。
测试方法
使用 pgTAP 进行数据库单元测试
pgTAP 是一个用于 Postgres 的单元测试框架,可以测试:
- 数据库结构:表、列、约束
- 行级安全(RLS)策略
- 函数和存储过程
- 数据完整性
以下示例演示了如何为一个简单的待办事项应用设置和测试 RLS 策略:
-
创建一个启用 RLS 的测试表:
12345678910111213141516-- 创建简单的待办事项表create table todos (id uuid primary key default gen_random_uuid(),task text not null,user_id uuid references auth.users not null,completed boolean default false);-- 启用行级安全alter table todos enable row level security;-- 创建策略create policy "用户只能访问自己的待办事项"on todos for all -- 此策略适用于所有操作to authenticatedusing ((select auth.uid()) = user_id); -
设置测试环境:
12# 使用 supabase cli 为我们的策略创建新测试supabase test new todos_rls.test -
编写 RLS 测试:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455begin;-- 安装测试工具-- 为测试安装 pgtap 扩展create extension if not exists pgtap with schema extensions;-- 声明我们的测试套件将有4个测试用例select plan(4);-- 设置测试数据-- 创建 auth.users 条目insert into auth.users (id, email) values ('123e4567-e89b-12d3-a456-426614174000', 'user1@test.com'), ('987fcdeb-51a2-43d7-9012-345678901234', 'user2@test.com');-- 创建测试待办事项insert into public.todos (task, user_id) values ('用户1的任务1', '123e4567-e89b-12d3-a456-426614174000'), ('用户1的任务2', '123e4567-e89b-12d3-a456-426614174000'), ('用户2的任务1', '987fcdeb-51a2-43d7-9012-345678901234');-- 作为用户1set local role authenticated;set local request.jwt.claim.sub = '123e4567-e89b-12d3-a456-426614174000';-- 测试1: 用户1应该只能看到自己的待办事项select results_eq( 'select count(*) from todos', ARRAY[2::bigint], '用户1应该只能看到自己的2个待办事项');-- 测试2: 用户1可以创建自己的待办事项select lives_ok( $$insert into todos (task, user_id) values ('新任务', '123e4567-e89b-12d3-a456-426614174000'::uuid)$$, '用户1可以创建自己的待办事项');-- 作为用户2set local request.jwt.claim.sub = '987fcdeb-51a2-43d7-9012-345678901234';-- 测试3: 用户2应该只能看到自己的待办事项select results_eq( 'select count(*) from todos', ARRAY[1::bigint], '用户2应该只能看到自己的1个待办事项');-- 测试4: 用户2不能修改用户1的待办事项SELECT results_ne( $$ update todos set task = '被黑了!' where user_id = '123e4567-e89b-12d3-a456-426614174000'::uuid returning 1 $$, $$ values(1) $$, '用户2不能修改用户1的待办事项');select * from finish();rollback; -
运行测试:
123456supabase test dbpsql:todos_rls.test.sql:4: NOTICE: extension "pgtap" already exists, skipping./todos_rls.test.sql .. ok所有测试通过。文件数=1, 测试数=6, 0 wallclock secs ( 0.01 usr + 0.00 sys = 0.01 CPU)结果: 通过
应用层测试
通过应用代码进行测试可以提供端到端的验证。与使用 pgTAP 的数据库级测试不同,应用层测试无法使用事务来实现隔离。
应用层测试不应依赖干净的数据库状态,因为在每次测试前重置数据库会很慢,并且难以并行化测试。 相反,应通过为每个测试用例使用唯一的用户ID来设计独立的测试。
以下是一个使用 TypeScript 的示例,与上述 pgTAP 测试相对应:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104import { } from '@supabase/supabase-js'import { , , , } from 'vitest'import from 'crypto'('Todos RLS', () => { // 为当前测试套件生成唯一ID,避免与其他测试冲突 const = .() const = .() const = (..!, ..!) (async () => { // 设置当前测试套件专用的测试数据 const = (..!, ..!) // 使用唯一ID创建测试用户 await ...({ : , : `user1-${}@test.com`, : 'password123', // 我们希望用户无需邮件确认即可立即使用 : true, }) await ...({ : , : `user2-${}@test.com`, : 'password123', : true, }) // 创建初始待办事项 await .('todos').([ { : '用户1的任务1', : }, { : '用户1的任务2', : }, { : '用户2的任务1', : }, ]) }) ('应允许用户1仅查看自己的待办事项', async () => { // 以用户1身份登录 await ..({ : `user1-${}@test.com`, : 'password123', }) const { : } = await .('todos').('*') ().(2) ?.(() => { (.).() }) }) ('应允许用户1创建自己的待办事项', async () => { await ..({ : `user1-${}@test.com`, : 'password123', }) const { } = await .('todos').({ : '新任务', : }) ().() }) ('应允许用户2仅查看自己的待办事项', async () => { // 以用户2身份登录 await ..({ : `user2-${}@test.com`, : 'password123', }) const { : } = await .('todos').('*') ().(1) ?.(() => { (.).() }) }) ('应阻止用户2修改用户1的待办事项', async () => { await ..({ : `user2-${}@test.com`, : 'password123', }) // 尝试更新我们无权访问的待办事项 // 结果将是无操作 await .('todos').({ : '被黑了!' }).('user_id', ) // 重新以用户1身份登录验证其待办事项未被修改 await ..({ : `user1-${}@test.com`, : 'password123', }) // 获取用户1的待办事项 const { : } = await .('todos').('*') // 验证没有待办事项被修改为"被黑了!" ().() ?.(() => { (.)..('被黑了!') }) })})
测试隔离策略
对于应用级测试,可采用以下测试隔离方法:
- 唯一标识符:为每个测试套件生成唯一ID,防止数据冲突
- 测试后清理:如有必要,在
afterAll
或afterEach
钩子中清理创建的数据 - 隔离数据集:使用数据前缀或命名空间来分隔测试用例
持续集成测试
在CI流水线中设置自动化数据库测试:
- 创建GitHub Actions工作流
.github/workflows/db-tests.yml
:
1234567891011121314151617181920212223name: 数据库测试on: push: branches: [main] pull_request: branches: [main]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: 安装Supabase CLI uses: supabase/setup-cli@v1 - name: 启动Supabase run: supabase start - name: 运行测试 run: supabase test db
最佳实践
-
测试数据准备
- 使用事务开始和回滚确保测试隔离
- 创建覆盖边界条件的真实测试数据
- 在测试中使用不同用户角色和权限
-
RLS策略测试
- 测试创建、读取、更新、删除操作
- 使用不同用户角色测试:匿名和认证用户
- 测试边界条件和潜在的安全绕过
- 始终测试负面用例:用户不应该能执行的操作
-
CI/CD集成
- 在每个拉取请求上自动运行测试
- 在部署流水线中包含数据库测试
- 使用事务保持测试运行速度
实际案例
查看更复杂的真实世界数据库测试案例:
- 数据库测试示例仓库 - 测试RLS策略的生产级示例
- RLS指南与最佳实践
故障排除
常见问题及解决方案:
-
RLS导致的测试失败
- 确保设置了正确的角色
set local role authenticated;
- 验证JWT声明是否设置
set local "request.jwt.claims"
- 检查策略定义是否符合测试假设
- 确保设置了正确的角色
-
CI流水线问题
- 确认Supabase CLI已正确安装
- 确保在测试前运行了数据库迁移
- 检查是否使用事务正确隔离测试