API Testing · Python Developers
Postman vs Old Tools — What Changed for Python Developers
Quick summary — what actually changed
Postman did not replace Python. It replaced the manual, copy-paste API testing loop that most Python developers were stuck in. Old workflow: write a curl command, paste it into a terminal, check the response by eye, then re-write the same request inside a `requests` script and a `unittest` file. Modern workflow: define the request once in a Postman collection, run it in the UI, export it to code when you need it, and run the same collection through Newman on every pull request.
In this guide you will learn:
- The old world — how Python developers tested APIs before Postman.
- What Postman changed — collections, environments and reusable requests.
- What still works — curl, requests and pytest are not dead.
- The hybrid workflow — where Postman stops and Python code begins.
- The modern CI loop — Newman, pre-request scripts and environment files.
SECTION 01The old world — how Python developers tested APIs
Before Postman (or before most teams adopted it), API testing for a Python developer meant juggling three disconnected tools:
| Old tool | What you used it for | The friction it created |
|---|---|---|
| curl | One-off requests from the terminal | Long, unreadable strings; no reuse |
| requests (Python) | Scripted API calls | Every test was code; slow to explore |
| unittest / pytest | Assertions and CI runs | Duplicated the same request in multiple files |
| Swagger UI | Reading API docs | Read-only; cannot chain or save requests |
| Notepad / Post-it | Remembering headers and tokens | Tokens expired; nobody knew the right values |
# The old way — test a POST endpoint with curl
curl -X POST https://api.example.com/v1/orders \
-H "Authorization: Bearer eyJhbGciOi..." \
-H "Content-Type: application/json" \
-H "X-Request-Id: 550e8400-e29b-41d4-a716-446655440000" \
-d '{"productId": 123, "quantity": 2}'
# Then copy the response.
# Then change the token because it expired.
# Then repeat for every endpoint.
# Then repeat again for staging.
# This is where most of the day went.
# Not testing logic — managing strings.
# The old way — same endpoint, but in Python
import requests
token = "eyJhbGciOi..." # where does this come from?
base = "https://api.example.com"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
}
payload = {"productId": 123, "quantity": 2}
response = requests.post(
f"{base}/v1/orders",
json=payload,
headers=headers,
timeout=10,
)
assert response.status_code == 201
order_id = response.json()["id"]
# Now you need the same request again in your test file.
# And again for the staging environment.
# And again for the auth flow.
# Three copies. Three drift points. Three bugs.
SECTION 02What Postman actually changed
Postman did not invent API testing. It made the same request define-once, use-everywhere. That one change is the whole story:
| Feature | What it replaced | Why Python developers care |
|---|---|---|
| Collections | Copy-pasted curl and requests code | One definition, reused everywhere |
| Environments | Hardcoded tokens and base URLs | Switch dev → staging → prod safely |
| Pre-request scripts | Manual login and token refresh | Auth runs automatically before every request |
| Tests tab | Ad-hoc print statements | Assertions live with the request |
| Code generation | Hand-writing requests in Python | One click → `requests` or `httpx` snippet |
| Newman | Running tests only manually | Same collection runs in CI |
# What a request looks like inside a Postman collection
# (this is saved in a single JSON file you can commit)
{
"name": "Create Order",
"request": {
"method": "POST",
"header": [
{ "key": "Authorization", "value": "Bearer {{token}}" },
{ "key": "Content-Type", "value": "application/json" }
],
"url": {
"raw": "{{base_url}}/v1/orders",
"host": ["{{base_url}}"],
"path": ["v1", "orders"]
},
"body": {
"mode": "raw",
"raw": "{\"productId\": 123, \"quantity\": 2}"
}
}
}
# {{token}} and {{base_url}} are variables.
# They come from the environment, not from this file.
# Change environment → the same request hits dev or staging.
# No code changes. No drift.
// Pre-request script — runs automatically before every request
// This is what used to be a manual "get a token" step
const baseUrl = pm.environment.get("base_url");
pm.sendRequest({
url: `${baseUrl}/auth/token`,
method: "POST",
header: { "Content-Type": "application/json" },
body: {
mode: "raw",
raw: JSON.stringify({
client_id: pm.environment.get("client_id"),
client_secret: pm.environment.get("client_secret")
})
}
}, function (err, res) {
if (err) {
console.error(err);
return;
}
pm.environment.set("token", res.json().access_token);
});
// Tests tab — assertions live with the request
pm.test("Status is 201", function () {
pm.response.to.have.status(201);
});
pm.test("Response has an order id", function () {
const body = pm.response.json();
pm.expect(body).to.have.property("id");
pm.environment.set("order_id", body.id);
});
// That is the whole shift.
// Token refresh and assertions used to be a Python file.
// Now they live inside the collection.
SECTION 03Old tools that still work
Experienced Python developers did not throw away their old tools. They kept the ones that still earn their place:
| Old tool | Still useful? | Where it fits today |
|---|---|---|
| curl | Yes — for quick checks | Reproducing a bug report; sharing a single command |
| requests | Yes — for automation | Complex logic, retries, file uploads, auth flows |
| httpx | Yes — for async | Async APIs, concurrent calls |
| pytest + requests | Yes — for CI tests | End-to-end and integration tests |
| Swagger UI | Yes — for docs | Reading what the API actually accepts |
| Copy-pasting into Notepad | No — replaced by Postman | Environment variables and collections |
# Everything in this file is still valid and still useful
# Postman did not delete any of it
import os
import requests
import pytest
BASE = os.environ.get("BASE_URL", "https://api.example.com")
TOKEN = os.environ.get("API_TOKEN")
def auth_headers():
return {"Authorization": f"Bearer {TOKEN}"}
def test_create_order():
payload = {"productId": 123, "quantity": 2}
response = requests.post(
f"{BASE}/v1/orders",
json=payload,
headers=auth_headers(),
timeout=10,
)
assert response.status_code == 201
assert "id" in response.json()
# Run with: pytest test_orders.py -v
# This is still the right tool for CI tests.
# Postman is not going to replace it.
Where Python code still beats Postman, every time:
☐ Complex business logic in the test
→ "retry three times if status is 429"
→ "loop over 1000 ids and assert each"
☐ File uploads with real multipart handling
→ requests makes this trivial
→ Postman can do it but is clumsy for parametrised files
☐ Database verification after the API call
→ pytest fixture queries Postgres directly
→ Postman has no DB access without extra setup
☐ Async and concurrent testing
→ httpx + asyncio tests 500 endpoints in parallel
→ Postman UI is single-request
☐ Very fine-grained timing and concurrency
→ time.perf_counter around requests
→ Postman is not a load-testing tool
Rule of thumb:
Postman explores, Python asserts.
Postman is the fastest way to find a bug.
Python is the fastest way to prove it is fixed — on every build.
SECTION 04Postman vs Python code — when to use each
The honest answer for a Python developer is: both. Here is the split that most experienced teams actually follow:
| Task | Use Postman | Use Python |
|---|---|---|
| Exploring a new API | Yes — fastest | Slower — code first |
| Sharing a request with a teammate | Yes — a collection link | Slower — code review needed |
| Reproducing a bug report | Yes — save and export | Yes — for CI regression test |
| Testing a complex auth flow | Yes — pre-request scripts | Yes — for permanent test |
| Testing 500 endpoints in parallel | No — not designed for it | Yes — httpx + asyncio |
| Verifying DB state after a call | No — no DB access | Yes — pytest + SQLAlchemy |
| Running tests on every PR | Yes — via Newman | Yes — via pytest in CI |
# Postman has a "Code" button on every request.
# Click it → choose Python → requests
# You get this, ready to paste into a pytest file:
import requests
url = "https://api.example.com/v1/orders"
payload = {"productId": 123, "quantity": 2}
headers = {
"Authorization": "Bearer YOUR_TOKEN",
"Content-Type": "application/json",
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)
# That is the whole bridge.
# Explore in Postman → generate the Python → assert in pytest.
# No copy-pasting a curl command and fixing the quotes.
When Python is the right choice (not Postman):
Situation 1 — Parameterised data
☐ You have a CSV of 200 test cases
☐ pytest runs each row as a test
☐ Postman is clumsy with large parametrised files
Situation 2 — Retry and timing
☐ "retry on 429 with exponential backoff"
☐ Postman pre-request scripts can do it, but
the code becomes unmaintainable
Situation 3 — Real load
☐ 500 parallel requests to measure p99
☐ Postman is a single-request client
☐ httpx + asyncio (or locust) is the right tool
Situation 4 — Cross-service verification
☐ Call the API, then check the DB
☐ Call the API, then check a Kafka topic
☐ Postman has no native DB or Kafka support
Rule: Postman for one-off and exploration.
Python for logic, data and scale.
SECTION 05The modern hybrid workflow
Here is the workflow that modern Python teams actually use. It starts in Postman and ends in pytest + Newman — both in CI:
| Step | Tool | Output |
|---|---|---|
| 1. Explore | Postman | Requests that actually work |
| 2. Save | Postman collection | A shared, versioned collection |
| 3. Generate | Postman "Code" button | Python requests snippet |
| 4. Assert | pytest | Permanent regression tests |
| 5. Smoke | Newman | Fast API contract check on every PR |
| 6. Monitor | Postman Monitors | Nightly production health check |
# A realistic day for a Python developer
Morning
☐ New endpoint added to the API
☐ Open Postman
☐ Send a request, check the JSON
☐ Save it to the "orders" collection
☐ Add 3 tests in the Tests tab
- 201 status
- order id present
- total price correct
Afternoon
☐ Click "Code" → Python → requests
☐ Paste into test_orders.py
☐ Change hardcoded values to pytest fixtures
☐ Run pytest locally
End of day
☐ Push branch
☐ CI runs Newman on the collection
☐ CI runs pytest on the assertions
☐ Both green → PR merges
This is the workflow real teams use today.
It uses both tools, not one.
What actually gets committed to the repo:
api-tests/
├── postman/
│ ├── orders.postman_collection.json
│ ├── auth.postman_collection.json
│ ├── dev.postman_environment.json
│ └── staging.postman_environment.json
├── tests/
│ ├── test_orders.py
│ ├── test_auth.py
│ └── conftest.py
├── .github/workflows/
│ └── api-tests.yml
└── README.md (how to run both)
Rules:
☐ Never commit a real token — use environment files with placeholders
☐ Never commit a Postman cloud export with secrets
☐ Use Postman's export → sanitise → commit
☐ Keep collection names stable — CI references them
If a reviewer cannot run your tests from a fresh clone,
the collection is not ready to be committed.
SECTION 06Newman, environments and CI runs
Newman is what makes a Postman collection a real test suite instead of a scratchpad. It runs the same collection from the command line, which means any CI system can run it on every pull request:
| Step | Command | What it does |
|---|---|---|
| Install Newman | npm install -g newman |
CLI runner for collections |
| Run a collection | newman run orders.json -e dev.json |
Executes every request and test |
| Report output | --reporters cli,json |
Machine-readable results for CI |
| Fail the build | --bail |
Stops on first failure |
| Switch env | -e staging.json |
Same collection, different target |
# Run a Postman collection from the command line
# This is what used to require a separate Python script
npm install -g newman
newman run postman/orders.postman_collection.json \
-e postman/dev.postman_environment.json \
--reporters cli,json \
--reporter-json-export reports/newman.json
# Or with an inline env override
newman run postman/orders.postman_collection.json \
--env-var "base_url=https://staging.example.com" \
--env-var "token=$STAGING_TOKEN"
# Exit code 0 = all tests passed
# Exit code 1 = at least one test failed
# That is all CI needs to fail a build.
# .github/workflows/api-tests.yml
name: API Tests
on:
pull_request:
branches: [main]
jobs:
newman:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Newman
run: npm install -g newman
- name: Run collection
env:
BASE_URL: ${{ secrets.STAGING_BASE_URL }}
API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
run: |
newman run postman/orders.postman_collection.json \
--env-var "base_url=$BASE_URL" \
--env-var "token=$API_TOKEN" \
--reporters cli,json \
--reporter-json-export newman-report.json
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: newman-report
path: newman-report.json
# Every pull request runs the collection.
# No separate test code. No drift between what you tested
# in Postman and what CI tests.
SECTION 07Test yourself — Postman vs old tools quiz
Five questions. No sign-up.
0 / 5Pick an answer to see why it is right or wrong.
SECTION 08Frequently asked questions
Does Postman replace Python requests?
No. Postman is best for exploring APIs, sharing requests with teammates, and running collections in CI via Newman. Python's requests (or httpx) is still the right tool for complex logic, parametrised tests, database verification and load testing.
What is the biggest change for Python developers?
The biggest change is reuse. Instead of writing the same request in curl, in a requests script and in a pytest file, you define it once in a Postman collection, use it in the UI, generate the Python code from it, and run the same collection through Newman in CI.
Is curl still useful in 2026?
Yes. curl is still the fastest way to reproduce a bug report or share a single reproducible request. It fits perfectly in Slack messages, bug tickets and documentation, and Postman can export a request as a curl command for exactly that purpose.
What is Newman and why does it matter?
Newman is the command-line runner for Postman collections. It lets you run the same collection you tested in the UI from any CI system (GitHub Actions, GitLab CI, Jenkins), which means the tests you wrote in Postman actually gate every pull request.
Should I keep pytest tests if I already have a Postman collection?
Yes, in most cases. Postman collections are great for API contract and smoke tests. Pytest is better for anything that needs real logic — parametrised data, database checks, retries, concurrency and precise assertions. They complement each other.
What should I never commit to a Postman collection?
Never commit real tokens, API keys or client secrets. Use environment files with placeholder values and inject real values via CI secrets at run time. A committed token is a leaked token.
Does Postman support Python-style assertions?
Postman tests run JavaScript (Chai assertions), not Python. If you need Python assertions on the response, use the "Code" button to export the request as Python and put the assertions in pytest. Postman also lets you call a Python script indirectly via pre-request scripts hitting an endpoint.
What is the honest bottom line?
Postman did not replace Python development — it replaced the manual, duplicated loop of curl → requests → pytest. The modern Python developer explores in Postman, generates Python code from it, asserts in pytest, and runs both pytest and Newman in CI. The tools are complementary, not competitive.
SECTION 09Related reads
Classroom & online · Delhi NCR
Master API testing the way modern teams do it
Our API Testing Course teaches Postman, collections, pre-request scripts, Newman in CI, and the Python pytest side that still matters — real projects, real pipelines.
₹15,500 onwards- Postman collections & environments
- Newman + GitHub Actions
- pytest + requests automation
- Weekday & weekend batches