The quality report
The Driving Test Centre is a small two-module project: candidates, vehicle categories, a fee schedule injected into an application, and three tests that only try a little of it. Read domain.ek9 - a constrained CandidateAge (17 to 70) and isMinimumAge, which checks it. To play it in the IDE: Help > Lessons > Quality and reports…, or ek9 -ide -lesson quality-report.
Start from
The code the lesson starts from: copy it into a file of your own and follow the steps.
domain.ek9
#!ek9 <?- Driving Test Centre - a candidate's age. REQUIREMENT: a candidate is old enough to book a driving test from age 17 (inclusive) up to 70 (inclusive) - the Centre does not test anyone outside that range at all. -?> defines module quality.centre.domain defines constant MINIMUM_AGE <- 17 defines type CandidateAge as Integer constrain as >= 17 and <= 70 defines function // REQUIREMENT: eligible for a test from MINIMUM_AGE (inclusive) - the Centre's own minimum. isMinimumAge() -> age as CandidateAge <- rtn as Boolean: age > MINIMUM_AGE //EOF
vehicles.ek9
#!ek9 <?- Driving Test Centre - vehicle categories, in their own file (same module as domain.ek9, which a module may span) so the Generate Edge-Case Tests… lesson can scope its whole-file run to domain.ek9 alone - just CandidateAge and isMinimumAge - without these four trivial classes. -?> defines module quality.centre.domain defines trait Vehicle category() as pure <- rtn as String? defines class Car with trait of Vehicle override category() as pure <- rtn as String: "car" default operator ? Motorcycle with trait of Vehicle override category() as pure <- rtn as String: "motorcycle" default operator ? Lorry with trait of Vehicle override category() as pure <- rtn as String: "lorry" default operator ? QuadBike with trait of Vehicle override category() as pure <- rtn as String: "quad-bike" default operator ? //EOF
scoring.ek9
#!ek9 <?- Driving Test Centre - theory test scoring, in its own file (same module as domain.ek9) so the Generate Edge-Case Tests… lesson can scope its whole-file run to domain.ek9 alone, without this function's require/division in the same scope. -?> defines module quality.centre.domain defines function // REQUIREMENT: a candidate's average theory test score across every attempt made so far - // attempts is always at least one (a candidate who has never sat the test has no average yet). averageScore() -> totalMarks as Float attempts as Float <- rtn as Float? require attempts > 0.0 rtn: totalMarks / attempts //EOF
service.ek9
#!ek9 <?- Driving Test Centre - the booking service: a fee schedule, injected into an application, used by a program. Two modules (quality.centre.service depends on quality.centre.domain) for the quality report's Module Dependencies page; AbstractFeeSchedule/StandardFeeSchedule for its Type Hierarchy and DI Wiring pages. -?> defines module quality.centre.service references quality.centre.domain::CandidateAge defines constant JUNIOR_AGE <- 25 SENIOR_AGE <- 60 defines class BookingService schedule as AbstractFeeSchedule! quote() -> age as CandidateAge category as String <- rtn as Money: schedule.feeFor(age, category) default operator ? defines component AbstractFeeSchedule as abstract feeFor() as abstract -> age as CandidateAge category as String <- rtn as Money? default operator ? // REQUIREMENT: the test fee depends on the candidate's age band and the vehicle category - // three age bands across four categories, the Centre's full published fee schedule. StandardFeeSchedule is AbstractFeeSchedule override feeFor() -> age as CandidateAge category as String <- rtn as Money: 0.00#GBP isCar <- category == "car" isMotorcycle <- category == "motorcycle" isLorry <- category == "lorry" isQuadBike <- category == "quad-bike" isJunior <- age <= JUNIOR_AGE isSenior <- age <= SENIOR_AGE if isCar and isJunior rtn: 34.00#GBP else if isCar and isSenior rtn: 40.00#GBP else if isCar rtn: 38.00#GBP else if isMotorcycle and isJunior rtn: 28.00#GBP else if isMotorcycle and isSenior rtn: 32.00#GBP else if isMotorcycle rtn: 29.00#GBP else if isLorry and isJunior rtn: 60.00#GBP else if isLorry and isSenior rtn: 65.00#GBP else if isLorry rtn: 62.00#GBP else if isQuadBike and isJunior rtn: 24.00#GBP else if isQuadBike and isSenior rtn: 27.00#GBP else if isQuadBike rtn: 26.00#GBP default operator ? defines application CentreApp register StandardFeeSchedule() as AbstractFeeSchedule defines program QuoteFee() with application of CentreApp stdout <- Stdout() service <- BookingService() candidate <- CandidateAge(30) fee <- service.quote(candidate, "car") stdout.println(`Fee: ${fee}`) //EOF
dev/tests.ek9
#!ek9 <?- Driving Test Centre - a FEW hand-written tests, deliberately leaving real gaps: isMinimumAge is only tried at one interior age (never the boundary, where the off-by-one lives), averageScore is only tried with ordinary marks (never the require/division edge), and only Car's category() is tried at all - Motorcycle, Lorry, QuadBike and the whole fee schedule are untested. Coverage, input variety and the budget bars all show what is missing. -?> defines module quality.centre.tests references quality.centre.domain::averageScore quality.centre.domain::CandidateAge quality.centre.domain::Car quality.centre.domain::isMinimumAge defines program @Test AdultIsEligible() candidate <- CandidateAge(30) assert isMinimumAge(candidate) @Test CarCategoryIsCar() car <- Car() assert car.category() == "car" @Test AverageOfTwoAttempts() assert averageScore(150.0, 2.0) == 75.0 //EOF
Step 1: Run the quality report
Run > Quality Report (Shift+Cmd+R) compiles the project, runs its three tests, and writes the same report ek9 -t6 writes - opened in your own browser when you play this for real; this lesson suppresses that so the IDE keeps the focus. Choose Quality Report.
Step 2: The pass/fail pill and Quality at a Glance
On index.html: the header's pill reads "All 3 tests passed" - the three hand-written tests, nothing wrong with them. Quality at a Glance tells a different story: Verification shows three rings - coverage 15.3% (lines that ran), driven 6.6% (input classes actually tried), asserted 6.6% (of those, how many an assertion checked). The CENTRE number is asserted, not coverage: a line running proves nothing if nothing checked what it did.
Risk profile: 18 constructs - 6 measured (exact, from a typed input space), 2 approximate (a bare String parameter, like feeFor's category - tighten the type to measure it), 10 coverage-only (no input to vary at all). Contracts: 4 obligations across 3 constructs - 2 discharged by construction (CandidateAge's own bounds), 2 unproven - averageScore's require and division, the worst construct named right there.
Step 3: Coverage Detail
Coverage Detail: Methods 16.7% (3/18), Lines 15.5% (11/71), Branches 0.0% (0/12) - branches is the hand-written tests' real gap, since none of them chooses between anything. Code Quality Metrics, just above it: Complexity max 27, 18 of 45 headroom - StandardFeeSchedule.feeFor, more than half the hard limit on its own. The next lesson reads that bar on its own.
Step 4: A module, then a file
Follow quality.centre.domain in the Modules table at the foot of index.html: its own page repeats Glance/Metrics/Coverage for just this module, then lists its Source Files - domain.ek9, scoring.ek9, vehicles.ek9 - and every Function & Method, each with its own CC/I/H/C tokens.
Follow domain.ek9 from there: the source itself, highlighted, a check or cross on every line that ran or did not - isMinimumAge's own line is covered (the three hand-written tests call it once), but only ONE of the ages that line could see was ever tried.
Step 5: DI Wiring
The header's three links - DI Wiring, Module Dependencies, Type Hierarchy - are pages of their own. DI Wiring: CentreApp is HEALTHY, registering StandardFeeSchedule as AbstractFeeSchedule; its one injection site, BookingService.schedule, is ticked - every injected field has something registered for it.
The result
That is the quality report: the pass/fail pill is about tests, the Verification rings beside it are about proof - asserted is the one that matters, because a line can run (covered) with nothing checking what it did. The module and file pages repeat the same CC/I/H/C tokens the editor's own complexity badges show, down to the construct. AI assistants get the same numbers from ek9_run_quality_checks, without ever opening a page.
Where this lesson comes from
- Answers -
ek9 -qand the IDE's Answers tab: 321, 811. - For AI assistants - the MCP tool
ek9_run_quality_checks.
In the EK9 reference: Compile-Time Code Quality Enforcement






