Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Custom Functions & Code Reuse trong Collection

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn đang làm QA cho một startup fintech ở Sài Gòn. Collection Postman của team có 180 request. Trong 60 request đầu tiên, bạn viết một đoạn code nhỏ để tạo signature HMAC-SHA256 gửi kèm mỗi API call. Rồi một ngày, backend đổi thuật toán ký từ SHA256 sang SHA512. Bạn ngồi sửa... 60 chỗ. Sửa xong, chạy lại, phát hiện có 3 request bạn quên sửa vì chúng nằm trong folder khác. Regression bug xuất hiện, và bạn mất nguyên buổi chiều để truy vết.

Đây chính là cái giá của việc copy-paste code trong Postman. Khác với một dự án Node.js nơi bạn chỉ cần viết require('./utils/sign.js') một lần rồi dùng khắp nơi, Postman không có một module system thực thụ. Không có import, không có require cho file cục bộ của bạn, không có class được chia sẻ giữa các request. Mỗi Pre-request Script và Test Script chạy trong một sandbox riêng, ngắn ngủi, và "quên sạch" mọi thứ ngay khi request kết thúc.

Vậy làm sao để tái sử dụng code — hàm tạo signature, hàm parse response chuẩn, hàm validate định dạng — mà không phải dán đi dán lại? Bài này dạy bạn đúng chuyện đó: những kỹ thuật "lách" (workaround) thông minh để biến Postman thành một môi trường có thể tái sử dụng code như một thư viện thực thụ. Đây là kỹ năng phân biệt một người "dùng Postman" với một QA engineer xây được test suite bảo trì được cho hàng trăm request.

Khái niệm cốt lõi

Vấn đề gốc: sandbox không chia sẻ state qua code

Mỗi script trong Postman (pre-request hoặc test) được nạp vào một sandbox JavaScript mới. Bạn không thể định nghĩa một hàm mySign() ở request A rồi gọi nó trực tiếp ở request B — hàm đó biến mất khi request A chạy xong. Cách duy nhất để "mang" một thứ gì đó từ script này sang script khác là thông qua variables (biến của Postman): collection variables, environment variables, hoặc global variables. Và variables chỉ lưu được chuỗi và số, không lưu được hàm.

Đây là điểm mấu chốt cần khắc sâu: bạn không truyền hàm đi được, nhưng bạn truyền chuỗi đi được. Và JavaScript có eval() cùng new Function() để biến một chuỗi thành code chạy được. Toàn bộ nghệ thuật tái sử dụng code trong Postman xoay quanh mẹo này.

Approach 1: Helper qua collection variables + JSON.parse (cấu hình & dữ liệu)

Cách đơn giản nhất và an toàn nhất là tái sử dụng dữ liệu, không phải hàm. Bạn lưu một object cấu hình dưới dạng chuỗi JSON vào collection variable, rồi bất kỳ request nào cũng đọc và JSON.parse ra.

Trong một script (thường là request đầu tiên hoặc một request "setup"):

const config = {
    baseHeaders: { "X-Client": "qa-suite", "Accept-Language": "vi-VN" },
    retryLimit: 3,
    testUsers: {
        admin: "admin@vietnamcos.com",
        buyer: "buyer01@test.vn"
    }
};
pm.collectionVariables.set("appConfig", JSON.stringify(config));

Ở bất kỳ request nào khác:

const config = JSON.parse(pm.collectionVariables.get("appConfig"));
pm.request.headers.add({ key: "X-Client", value: config.baseHeaders["X-Client"] });

Ưu điểm: rõ ràng, dễ debug, không có rủi ro bảo mật. Nhược điểm: chỉ chia sẻ được dữ liệu tĩnh, chưa chia sẻ được logic.

Approach 2: Chia sẻ hàm dưới dạng chuỗi (pm.globals.set + eval)

Đây là kỹ thuật kinh điển. Bạn viết hàm dưới dạng chuỗi, lưu vào một variable, và mỗi request nạp lại bằng eval.

Trong Collection-level Pre-request Script (chạy trước MỌI request trong collection — đây là nơi vàng để đặt code chung):

const utils = {
    sign: function (payload, secret) {
        return CryptoJS.HmacSHA512(payload, secret).toString();
    },
    isValidVNPhone: function (phone) {
        return /^0(3|5|7|8|9)[0-9]{8}$/.test(phone);
    },
    now: function () { return Date.now(); }
};

// Chuyển từng hàm thành chuỗi rồi gom vào một object nguồn let src = "utils = {};"; for (let key in utils) { src += utils.${key} = ${utils[key].toString()};; } pm.collectionVariables.set("utilsSource", src);

Ở Pre-request hoặc Test Script của request bất kỳ:

let utils;
eval(pm.collectionVariables.get("utilsSource"));

const signature = utils.sign(pm.request.body.raw, pm.environment.get("secret")); pm.expect(utils.isValidVNPhone("0912345678")).to.be.true;

Vì Collection-level Pre-request Script chạy TRƯỚC mọi request, bạn chỉ cần đặt phần "định nghĩa utilsSource" một lần ở đó, và mọi request con tự động có sẵn chuỗi để eval. Đây là mô phỏng gần nhất với một "thư viện dùng chung" trong Postman.

Approach 3: Gán hàm trực tiếp vào biến toàn cục của sandbox

Một biến thể tinh gọn hơn: trong Collection Pre-request Script, gán thẳng hàm vào global scope của sandbox. Vì Collection Pre-request chạy ngay trước request, các hàm này còn "sống" trong cùng lượt thực thi:

// Collection Pre-request Script
globalThis.formatVND = function (n) {
    return new Intl.NumberFormat('vi-VN').format(n) + ' đ';
};

Sau đó trong request:

console.log(formatVND(1500000)); // "1.500.000 đ"

Lưu ý quan trọng: cách này chỉ hoạt động khi hàm được gán ở Collection Pre-request và dùng ở Pre-request/Test của cùng request đó, vì Postman làm mới sandbox giữa các request. Nó tiện cho các hàm dùng ngay, nhưng kém bền hơn Approach 2 nếu bạn cần dùng chéo qua nhiều bước phức tạp.

Khi nào chọn cách nào

  • Chia sẻ cấu hình/dữ liệu → Approach 1 (JSON.parse). An toàn nhất.
  • Chia sẻ logic phức tạp, tái dùng khắp collection → Approach 2 (chuỗi + eval trong Collection Pre-request).
  • Chia sẻ hàm tiện ích nhẹ, dùng ngay trong request → Approach 3 (globalThis).

Tình huống thực tế

Ví dụ 1: Tiki — chuẩn hóa signature cho 200+ request

Một team QA giả định tại một sàn TMĐT lớn (kiểu Tiki) có collection 210 request gọi Internal API, mỗi request cần header X-Signature = HMAC của timestamp + method + path. Ban đầu mỗi người tự viết đoạn ký riêng, dẫn đến 4 phiên bản code khác nhau, trong đó 1 phiên bản dùng sai thứ tự nối chuỗi.

Họ refactor theo Approach 2: đưa một hàm buildSignature(method, path, ts, secret) vào Collection Pre-request Script dưới dạng signSource. Mỗi request chỉ còn 3 dòng: eval chuỗi, gọi hàm, gán header. Khi backend đổi thuật toán ký, họ chỉ sửa 1 chỗ trong Collection Pre-request. Thời gian sửa từ "nửa buổi chiều dò 210 request" xuống còn 5 phút. Bài học: một điểm sửa duy nhất (single source of truth) là mục tiêu tối thượng của tái sử dụng code — kể cả khi phải dùng mẹo eval.

Ví dụ 2: Startup fintech VN — validate số điện thoại và mã ngân hàng dùng chung

Một fintech ở Hà Nội test API mở tài khoản, cần kiểm tra ở ~40 request rằng số điện thoại đúng định dạng VN (đầu số 03/05/07/08/09) và mã ngân hàng nằm trong danh sách napas hợp lệ. Thay vì nhét regex vào 40 test script, họ gom isValidVNPhoneisNapasBank vào một object validators chia sẻ qua collection variable. Khi Ngân hàng Nhà nước cấp thêm đầu số mới, họ cập nhật regex ở đúng một nơi.

Điểm hay là họ còn lưu danh sách mã ngân hàng (dữ liệu) bằng Approach 1 và hàm kiểm tra (logic) bằng Approach 2 — kết hợp cả hai. Bài học: phân biệt rạch ròi dữ liệu và logic giúp mỗi loại được bảo trì đúng cách; đừng hard-code danh sách 40 ngân hàng vào trong hàm.

Ví dụ 3: Grab-style super-app — hàm parse response chuẩn hóa

Một team ở Singapore/VN test hệ thống microservices nơi mỗi service trả về envelope khác nhau: chỗ thì {data, meta}, chỗ thì {result, pagination}. Họ viết một hàm unwrap(response) chuẩn hóa mọi envelope về một hình dạng thống nhất, chia sẻ qua Approach 2. Nhờ đó, test script ở tầng trên không cần biết service nào trả kiểu gì — chỉ cần const body = utils.unwrap(pm.response.json()). Khi thêm service thứ 12 với envelope lạ, họ chỉ mở rộng hàm unwrap. Bài học: tái sử dụng code không chỉ tiết kiệm gõ phím, mà còn tạo một lớp trừu tượng giúp test bền vững trước thay đổi của backend.

Hướng dẫn từng bước

Xây một "thư viện dùng chung" cho collection của bạn:

  • Mở Collection Pre-request Script. Click vào tên collection → tab "Scripts" (hoặc "Pre-request Script" ở bản Postman cũ). Đây là nơi code chạy trước mọi request.
  • Viết các hàm tiện ích thành một object. Ví dụ utils = { sign, unwrap, formatVND, isValidVNPhone }. Viết như code JavaScript bình thường, test kỹ từng hàm.
  • Serialize object thành chuỗi nguồn. Lặp qua từng hàm, dùng .toString() để lấy mã nguồn, nối thành một chuỗi utilsSource và lưu bằng pm.collectionVariables.set("utilsSource", src).
  • Ở đầu mỗi request cần dùng, thêm 2 dòng: let utils; eval(pm.collectionVariables.get("utilsSource"));. Sau đó gọi hàm tự nhiên: utils.sign(...).
  • Kiểm chứng. Chạy một request bất kỳ, thêm console.log(typeof utils.sign) và mở Postman Console (View → Show Postman Console). Nếu thấy "function", thư viện đã nạp thành công.
  • Ghi tài liệu. Thêm comment ở Collection Pre-request liệt kê các hàm có sẵn và chữ ký của chúng, để đồng đội biết dùng gì mà không phải đọc mã.

Lỗi thường gặp & mẹo

  • Closure không được giữ lại. Khi bạn .toString() một hàm, bạn chỉ lấy phần thân — mọi biến bên ngoài mà hàm tham chiếu (closure) sẽ mất. Vì vậy hàm chia sẻ phải "tự đủ": mọi thứ nó cần phải nằm trong tham số hoặc gọi qua pm.*. Đừng viết hàm dựa vào biến cục bộ định nghĩa ở nơi khác.
  • eval là con dao hai lưỡi. Chỉ eval chuỗi do CHÍNH BẠN tạo ra, không bao giờ eval dữ liệu đến từ response API hay input người dùng — đó là lỗ hổng thực thi mã. Trong ngữ cảnh QA test suite nội bộ thì eval chấp nhận được, nhưng phải kỷ luật về nguồn của chuỗi.
  • Quên nạp thư viện trước khi gọi. Nếu thấy lỗi utils is not defined, gần như chắc chắn bạn quên dòng eval(...) ở đầu script, hoặc request này nằm ngoài collection chứa Collection Pre-request.
  • pm.globals vs pm.collectionVariables. Ưu tiên collectionVariables để chuỗi nguồn "đi theo" collection khi export/chia sẻ. Dùng pm.globals chỉ khi bạn muốn chia sẻ across nhiều collection, nhưng nhớ global dễ bị "ô nhiễm" và khó truy vết.
  • Đừng lạm dụng. Nếu logic của bạn phức tạp đến mức phải viết hàng trăm dòng hàm chia sẻ, đó là tín hiệu nên cân nhắc chuyển sang test code-based. Postman tái sử dụng code tốt cho tiện ích vừa phải, không thay được một codebase test đúng nghĩa.
  • Mẹo versioning: thêm utils._version = "1.3" vào object và log nó ra console. Khi debug, bạn biết chắc mình đang chạy đúng phiên bản thư viện đã cập nhật, tránh cảnh "đã sửa mà sao vẫn lỗi" do collection variable cũ còn nằm trong environment.

Bài tập thực hành

  • Tạo một collection mới 3 request. Trong Collection Pre-request Script, định nghĩa object utils với 2 hàm: formatVND(n) (định dạng tiền Việt) và isValidVNPhone(phone). Serialize và lưu vào utilsSource.
  • Ở request thứ nhất, eval và gọi console.log(utils.formatVND(2500000)) — xác nhận thấy "2.500.000 đ" trong Postman Console.
  • Ở request thứ hai, viết một test script dùng utils.isValidVNPhone để assert số "0987654321" là hợp lệ và "0123456789" là không hợp lệ.
  • Dùng Approach 1: lưu một object config chứa danh sách 3 email test user dưới dạng JSON vào collection variable, rồi ở request thứ ba JSON.parse ra và dùng một email để set biến currentUser.
  • Thử thách: thêm utils._version rồi cố tình sửa formatVND mà KHÔNG chạy lại request setup. Quan sát xem giá trị cũ có còn không, và rút ra kết luận về vòng đời của collection variable.

Tóm tắt

Postman không có module system thật, nhưng bạn hoàn toàn có thể tái sử dụng code bằng ba kỹ thuật: (1) chia sẻ dữ liệu qua collection variables + JSON.parse, (2) chia sẻ hàm dưới dạng chuỗi .toString() rồi eval — đặt phần định nghĩa ở Collection Pre-request Script để mọi request thừa hưởng, và (3) gán hàm vào globalThis cho các tiện ích dùng ngay. Chìa khóa tư duy: bạn không truyền được hàm, nhưng truyền được chuỗi, và chuỗi có thể trở lại thành code. Mục tiêu tối thượng là single source of truth — mỗi mẩu logic chỉ tồn tại đúng một nơi để khi thay đổi, bạn sửa một chỗ thay vì săn lùng khắp hàng trăm request. Nhưng hãy tỉnh táo: eval chỉ dùng với chuỗi tự tạo, closure sẽ mất khi serialize, và khi thư viện phình quá lớn thì đó là dấu hiệu nên nghĩ tới test code-based. Nắm vững bài này, collection của bạn sẽ chuyển từ mớ copy-paste dễ vỡ thành một bộ test có kiến trúc, bảo trì được, và đáng tin cậy.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi