01.02 — ISTQB vs pytest / Jest / Mocha#
The methodological observation#
Past the technical bet on the technical / non-technical barrier (see 01-flip-the-problem.md), there’s a second bet: the one on trade vocabulary.
The ISTQB and professional testers have spent decades building a precise, battle-tested vocabulary: test cycles, campaigns, test suites, test cases, test steps. A clear hierarchy, built to organize, trace and drive software quality.
Automation tools have largely ignored that legacy.
pytest, Jest, Mocha… they’re all hybrid mixes where testers have to learn to think like developers, and nobody really speaks the same language.
The architectural consequence#
Ocarina takes that vocabulary seriously and ports it straight into the class hierarchy.
| ISTQB vocabulary | Ocarina class | Source file |
|---|---|---|
| Test step | act() (user verb, returns ActionStart[TPOM]) | src/ocarina/dsl/testing_with_railway/constructors/create_act.py |
| Test case | Test[Driver] | src/ocarina/dsl/testing/oc_test.py |
| Test suite | TestSuite[Driver] | src/ocarina/dsl/testing/oc_test_suite.py |
| Campaign | TestCampaign[Driver] | src/ocarina/dsl/testing/oc_test_campaign.py |
| Cycle | TestCycle[Driver] | src/ocarina/dsl/testing/oc_test_cycle.py |
Not “decorative namespaces”. Every level has a distinct job (see ../02-ocarina/05-orchestration/):
| Level | Job |
|---|---|
act | One atomic action on a page. |
Test | Metadata (name, test_id, skipped) + scenario + pre/post fragments + watchers. |
TestSuite | Driver pool, parallelization, saturation, ID filtering, retry policy. |
TestCampaign | Sequence of suites with shared workers config. |
TestCycle | Smoke + main, mode applies to the smoke tests (fail-fast | wait-for-all). |
The break with pytest#
Ocarina is not a pytest plugin. The README says so flat out:
Ships its own test runner: Ocarina is NOT a pytest plugin.
Why?
- pytest vocabulary isn’t ISTQB. pytest has
test_function,parametrize,fixture,conftest. Internally coherent, but a different universe. Wrapping Ocarina as a pytest plugin would have forced translation — exactly what the philosophy refuses (see01-flip-the-problem.md). - Independence. A plugin depends on its host’s API. Ocarina wants to be auditable in an afternoon, no pytest knowledge required.
- Stance on fixtures. Ocarina swaps fixtures for closures + scenario fragments +
Effectforsetup/teardown. Stance: “poor man’s rich” — one mechanism, the closure, for every injection. One closure does what classical OO would pull off with fixtures + scopes + inheritance.
The break with Cucumber / Gherkin#
No .feature file, no step definition, no glue layer. The scenario is Python, directly executable, directly typed, directly refactorable. No permanent translation to maintain.
The “language” Ocarina exposes to scenarios isn’t a text DSL. It’s an embedded Python DSL:
return [
drive_page(
act(on_homepage, open_then_verify_homepage)
.failure(just_log_error("Failed to reach the homepage..."))
.success(log_success_with_current_url_and_take_screenshot("On the homepage!")),
act(on_homepage, click_book_call_page_cta)
.failure(just_log_error("Failed to click on the 'Book a call' CTA..."))
.success(just_log_success("Clicked on the 'Book a call' CTA!")),
),
drive_page(
act(on_book_a_call_page, verify_book_call_page)
.failure(just_log_error("Failed to verify the 'Book a call' page..."))
.success(log_success_with_current_url_and_take_screenshot("On the 'Book a call' page!")),
),
]Pure Python. No lexer, no third-party runtime, no translation. Typed too — the slightest mismatch is a mypy error (see ../02-ocarina/03-railway/02-action-chain-states.md).
Consequence on reporting#
The reporting sticks to ISTQB vocabulary too. pretty_print_results spits out:
Campaign
• Suite
> Test case 1
» PASSED
> Test case 2
» FAILED
→ Error message
⫸ At step 3Three indents, three levels: campaign, suite, case. The step only shows up as failure context (“At step 3”). No Python class name surfaces in the output — the end user sees trade vocabulary, not code.
ISTQB ↔ Ocarina glossary#
| ISTQB term | Ocarina term | Note |
|---|---|---|
| Test step | act() | Atomic action. |
| Test case | Test[Driver] | Wraps a scenario. |
| Test scenario | Scenario[Driver] | Made of a test_chain, a setup, a teardown, watchers. |
| Test suite | TestSuite[Driver] | Parallelized. |
| Test campaign | TestCampaign[Driver] | Sequential (between suites). |
| Test cycle | TestCycle[Driver] | Smoke + main, fail-fast or wait-for-all. |
| Smoke test | smoke_tests_campaigns= | Gate. |
| Setup | Scenario.setup: Effect | Effect. |
| Teardown | Scenario.teardown: Effect | Effect too. Always runs, errors swallowed. |
| Test report | pretty_print_results, generate_docx_proof, generate_json_results | Post-execution plugins. |
Link to Python’s evolving type system#
The Holy Book draws a sharp dependency: this vocabulary couldn’t have been embodied this rigorously before PEP 695 generics, because you need to parameterize TestSuite[Driver] with a precise driver type. From the Holy Book (chapter “First feedbacks”):
The recent evolution of Python’s type system is central to what makes Ocarina possible. Without being “the future” either: this is established science, but one that arrived cruelly late in our ecosystem.
That’s why the framework requires requires-python = ">=3.14" (see ../00-big-picture/02-stack-matrix.md).