Backend Testing (pytest)
3.1 Manual first: curl or Postman
Before automating anything, run the test case by hand and record it in the Excel run sheet (section 2.6).
With curl (Command Prompt)
-i shows the status line and headers, and > saves everything as evidence:
curl -i -X POST "http://localhost:7071/api/HttpTriggerOcrEtaxAsync" -F "file=@C:\temp\invoice.pdf" -F "service=test-service" > evidence\API-OCRETAX-001.txt 2>&1Open the file, copy the status line and body into the Actual result column, and keep the file as evidence.
In PowerShell use curl.exe, not curl, because curl there is an alias for a
different command.
With Postman
- One collection per feature, one saved request per test case, named with the QA ID:
API-OCRETAX-001 — Submit valid PDF. - Put the host and any key in a Postman environment (
{{base_url}},{{function_key}}), never typed into the request, so nothing secret is shared when you export the collection. - After running, use Save Response → Save to a file for evidence.
- Postman’s Tests tab can check the response automatically, but our automated checks live in pytest, so keep Postman for exploring and manual runs.
What goes in the run sheet
| Expected result (before) | Actual result (after) |
|---|---|
| HTTP 202 | HTTP 202 |
tracking_id not empty | tracking_id = 8f2c-… |
status = Uploaded | status = Uploaded |
message = File uploaded and queued for OCR processing. | same |
Once the case passes manually and is worth repeating, turn it into a pytest test (sections 3.2–3.3). The asserts are the same lines as the expected result column.
3.2 Setup
Each test project has its own folder and its own venv, outside the app’s repo folder:
qa-ocr-tests\
├── requirements.txt pytest, requests
├── pytest.ini
└── tests\
├── conftest.py shared fixtures + Kiwi sync
└── test_http_trigger_upload.pypy -3.11 -m venv .venv
.venv\Scripts\activate.bat
python -m pip install -r requirements.txtAlways use python -m pip, not pip3, so packages go into the active venv.
Tests that should run automatically after every deploy live in the shared
qa-automation repo instead, one folder per product. See
Smoke Tests in CI/CD.
3.3 How a test looks
One Kiwi test case = one test function. The name carries the QA ID, and the marker carries the Kiwi case number:
@pytest.mark.kiwi_case(1)
def test_API_OCRETAX_001_submit_valid_pdf_returns_202_with_tracking_id(function_host, test_pdf):
with test_pdf.open("rb") as fh:
response = requests.post(
f"{function_host}/api/HttpTriggerOcrEtaxAsync",
files={"file": (test_pdf.name, fh, "application/pdf")},
data={"service": "test-service"},
timeout=60,
)
assert response.status_code == 202
body = response.json()
assert body["tracking_id"]
assert body["status"] == "Uploaded"Each assert is one line of the expected result, and its failure message says
what was expected and what came back.
3.4 What to check on every API
- Status code: the exact one (
202, not “any 2xx”). - Response body: the fields and values, not only the status code.
- Validation: missing or wrong input returns
400with a message that names the problem. - Auth: no key, wrong key, and wrong user are all rejected (in the Azure environment).
- Async flows: after the job finishes, check the final state (e.g.
status: Finishedin MongoDB) and that the callback arrived. - Duplicates: sending the same request twice doesn’t create a broken or duplicated record.
3.5 Preconditions: BLOCKED, not FAILED
If the thing under test isn’t running (e.g. func start is down) or test data is
missing, that’s not a product bug. Check preconditions in a fixture and fail
there, so Kiwi records the test as BLOCKED:
@pytest.fixture(scope="session")
def function_host():
try:
requests.get("http://localhost:7071", timeout=10)
except requests.exceptions.RequestException:
pytest.fail("Precondition not met: func start is not running", pytrace=False)
return "http://localhost:7071"3.6 Rules
- Never hard-code secrets. URLs, keys, and passwords come from environment variables.
- Each test creates its own data and doesn’t depend on another test having run.
- Test data is fake, never copied from production.
- Run against a real running system, not a mocked response. A test against a mock only proves the mock works.