API CRUD 操作
这些示例展示了如何在 REST API 上测试 CRUD 操作。
CRUD 指的是数据库中的基本操作:创建 (Create)、读取 (Read)、更新 (Update) 和删除 (Delete)。我们可以将这些操作映射到 REST API 中的 HTTP 方法
- 创建:用于创建新资源的 HTTP
POST
操作。 - 读取:用于检索资源的 HTTP
GET
。 - 更新:用于更改现有资源的 HTTP
PUT
或PATCH
。 - 删除:用于移除资源的 HTTP
DELETE
。
本文档包含两个示例,一个使用核心 k6 API(k6/http
和 checks
),另一个展示了较新的 API httpx
和 k6chaijs
)。
测试步骤
在 setup() 阶段,我们为 QuickPizza 创建一个用户。然后我们检索并返回一个 bearer 令牌,用于验证后续的 CRUD 请求。
在 VU 阶段实现的步骤如下
- 创建新资源,一个披萨评分。
- 读取评分列表。
- 更新评分的星级(例如更新为 5 星),并读取该评分以确认更新操作。
- 删除评分资源。
核心 k6 API 示例
import http from 'k6/http';
import { check, group, fail } from 'k6';
export const options = {
vus: 1,
iterations: 1,
};
// Create a random string of given length
function randomString(length, charset = '') {
if (!charset) charset = 'abcdefghijklmnopqrstuvwxyz';
let res = '';
while (length--) res += charset[(Math.random() * charset.length) | 0];
return res;
}
const USERNAME = `${randomString(10)}@example.com`; // Set your own email or `${randomString(10)}@example.com`;
const PASSWORD = 'secret';
const BASE_URL = 'https://quickpizza.grafana.com';
// Register a new user and retrieve authentication token for subsequent API requests
export function setup() {
const res = http.post(
`${BASE_URL}/api/users`,
JSON.stringify({
username: USERNAME,
password: PASSWORD,
})
);
check(res, { 'created user': (r) => r.status === 201 });
const loginRes = http.post(
`${BASE_URL}/api/users/token/login`,
JSON.stringify({
username: USERNAME,
password: PASSWORD,
})
);
const authToken = loginRes.json('token');
check(authToken, { 'logged in successfully': () => authToken.length > 0 });
return authToken;
}
export default function (authToken) {
// set the authorization header on the session for the subsequent requests
const requestConfigWithTag = (tag) => ({
headers: {
Authorization: `Bearer ${authToken}`,
},
tags: Object.assign(
{},
{
name: 'PrivateRatings',
},
tag
),
});
let URL = `${BASE_URL}/api/ratings`;
group('01. Create a new rating', () => {
const payload = {
stars: 2,
pizza_id: 1, // Pizza ID 1 already exists in the database.
};
const res = http.post(URL, JSON.stringify(payload), requestConfigWithTag({ name: 'Create' }));
if (check(res, { 'Rating created correctly': (r) => r.status === 201 })) {
URL = `${URL}/${res.json('id')}`;
} else {
console.log(`Unable to create rating ${res.status} ${res.body}`);
return;
}
});
group('02. Fetch my ratings', () => {
const res = http.get(`${BASE_URL}/api/ratings`, requestConfigWithTag({ name: 'Fetch' }));
check(res, { 'retrieve ratings status': (r) => r.status === 200 });
check(res.json(), { 'retrieved ratings list': (r) => r.ratings.length > 0 });
});
group('03. Update the rating', () => {
const payload = { stars: 5 };
const res = http.put(URL, JSON.stringify(payload), requestConfigWithTag({ name: 'Update' }));
const isSuccessfulUpdate = check(res, {
'Update worked': () => res.status === 200,
'Updated stars number is correct': () => res.json('stars') === 5,
});
if (!isSuccessfulUpdate) {
console.log(`Unable to update the rating ${res.status} ${res.body}`);
return;
}
});
group('04. Delete the rating', () => {
const delRes = http.del(URL, null, requestConfigWithTag({ name: 'Delete' }));
const isSuccessfulDelete = check(null, {
'Rating was deleted correctly': () => delRes.status === 204,
});
if (!isSuccessfulDelete) {
console.log('Rating was not deleted properly');
return;
}
});
}
httpx 和 k6chaijs 示例
import { describe, expect } from 'https://jslib.k6.io/k6chaijs/4.3.4.3/index.js';
import { Httpx } from 'https://jslib.k6.io/httpx/0.1.0/index.js';
import {
randomIntBetween,
randomItem,
randomString,
} from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';
export const options = {
// for the example, let's run only 1 VU with 1 iteration
vus: 1,
iterations: 1,
};
const USERNAME = `user${randomIntBetween(1, 100000)}@example.com`; // Set your own email;
const PASSWORD = 'secret';
const session = new Httpx({ baseURL: 'https://quickpizza.grafana.com' });
// Register a new user and retrieve authentication token for subsequent API requests
export function setup() {
let authToken = null;
describe(`setup - create a test user ${USERNAME}`, () => {
const resp = session.post(
`/api/users`,
JSON.stringify({
username: USERNAME,
password: PASSWORD,
})
);
expect(resp.status, 'User create status').to.equal(201);
expect(resp, 'User create valid json response').to.have.validJsonBody();
});
describe(`setup - Authenticate the new user ${USERNAME}`, () => {
const resp = session.post(
`/api/users/token/login`,
JSON.stringify({
username: USERNAME,
password: PASSWORD,
})
);
expect(resp.status, 'Authenticate status').to.equal(200);
expect(resp, 'Authenticate valid json response').to.have.validJsonBody();
authToken = resp.json('token');
expect(authToken, 'Authentication token').to.be.a('string');
});
return authToken;
}
export default function (authToken) {
// set the authorization header on the session for the subsequent requests
session.addHeader('Authorization', `Bearer ${authToken}`);
describe('01. Create a new rating', (t) => {
const payload = {
stars: 2,
pizza_id: 1, // Pizza ID 1 already exists in the database
};
session.addTag('name', 'Create');
const resp = session.post(`/api/ratings`, JSON.stringify(payload));
expect(resp.status, 'Rating creation status').to.equal(201);
expect(resp, 'Rating creation valid json response').to.have.validJsonBody();
session.newRatingId = resp.json('id');
});
describe('02. Fetch my ratings', (t) => {
session.clearTag('name');
const resp = session.get('/api/ratings');
expect(resp.status, 'Fetch ratings status').to.equal(200);
expect(resp, 'Fetch ratings valid json response').to.have.validJsonBody();
expect(resp.json('ratings').length, 'Number of ratings').to.be.above(0);
});
describe('03. Update the rating', (t) => {
const payload = {
stars: 5,
};
const resp = session.patch(`/api/ratings/${session.newRatingId}`, JSON.stringify(payload));
expect(resp.status, 'Rating patch status').to.equal(200);
expect(resp, 'Fetch rating valid json response').to.have.validJsonBody();
expect(resp.json('stars'), 'Stars').to.equal(payload.stars);
// read rating again to verify the update worked
const resp1 = session.get(`/api/ratings/${session.newRatingId}`);
expect(resp1.status, 'Fetch rating status').to.equal(200);
expect(resp1, 'Fetch rating valid json response').to.have.validJsonBody();
expect(resp1.json('stars'), 'Stars').to.equal(payload.stars);
});
describe('04. Delete the rating', (t) => {
const resp = session.delete(`/api/ratings/${session.newRatingId}`);
expect(resp.status, 'Rating delete status').to.equal(204);
});
}