skip to content

In the Maven Surefire report schema (`surefire-test-report.xsd`), which attributes must every `<testsuite>` element carry, and what does that schema actually guarantee about how those counters relate to the `<testcase>` elements inside it?

level: middleimportance: must knowfreq 62%

answer

  1. five attributes, none of them optional
  2. counts asserted, never cross-checked
  3. timing is the optional part
  4. no declared count of passes
  5. arithmetic is the reader's problem

basics

~20 s

Every <testsuite> must carry @name, @tests, @errors, @skipped and @failures; @time and @timestamp are optional. The schema only requires those attributes to be present - it never makes their values agree with the <testcase> elements below.

solid answer

~40 s

The Maven Surefire report schema (`surefire-test-report.xsd`, which declares `version="3.0.2"`) requires five attributes on `<testsuite>`: `@name`, `@tests`, `@errors`, `@skipped` and `@failures`. `@time`, `@timestamp`, `@version`, `@group` and `@flakes` are optional, so a conforming file always gives you four counts and a suite name, and may tell you nothing about when it ran. The catch is that the counters are ordinary attributes: nothing ties `@tests` to the number of `<testcase>` children, or `@failures` to the number of cases carrying a `<failure>`. A header claiming ten tests above nine cases validates cleanly. Consumers therefore split into those that trust the header and those that recount the children, and the two can disagree about one file. Note also that no attribute counts passes - a green number is always derived.

code

xml · 9 lines
xml
<testsuite name="com.example.CartTest" tests="3" errors="0" skipped="1" failures="1" time="0.412">
  <testcase name="addsItem" classname="com.example.CartTest" time="0.104"/>
  <testcase name="rejectsNegativeQuantity" classname="com.example.CartTest" time="0.087">
    <failure message="expected 0 but was -1" type="java.lang.AssertionError">stack trace text</failure>
  </testcase>
  <testcase name="appliesCoupon" classname="com.example.CartTest" time="0.221">
    <skipped/>
  </testcase>
</testsuite>

go deeper

for a junior

Be able to name the five attributes a <testsuite> must carry - @name, @tests, @errors, @skipped, @failures - and say that @time and @timestamp are optional extras.

for a middle

Explain that the schema types and requires attributes but cannot tie @tests to the number of <testcase> children, so a file with contradictory counters still validates cleanly.

for a senior

Show you have met the consequence in production: two tools reading one file and publishing different totals, and a pass count that is always computed because the format declares no attribute for it.

for a principal

Own the call across the organisation: whether header counters or counted children are the contract your gates read, and how a writer that disagrees with itself gets detected rather than argued about.

## What the suite element is A JUnit-XML result file written to the Maven Surefire report schema (`surefire-test-report.xsd`, which declares `version="3.0.2"`) has one `<testsuite>` element at its root, with every `<testcase>` from that suite nested inside it. `<testsuite>` is the file's header: it names the suite and states, as attributes, how many tests it is reporting and how many of those it counts as failures, errors and skips. A report can render a summary row from that one opening tag without parsing another byte, which is exactly why the counters are there at all. ## The attributes the schema requires Five attributes are required on `<testsuite>`; everything else is optional. | attribute | required? | what it carries | |---|---|---| | `@name` | yes | the suite's name | | `@tests` | yes | how many tests the suite claims to report | | `@errors` | yes | how many it counts as errors | | `@skipped` | yes | how many it counts as skipped | | `@failures` | yes | how many it counts as failures | | `@time` | no | a duration, typed `xs:float` | | `@timestamp` | no | a start time, typed `xs:dateTime` | | `@version`, `@group`, `@flakes` | no | further optional attributes | Two things follow immediately. First, **the four counters are never missing**. A suite with nothing to report still writes `errors="0" skipped="0" failures="0"`, so a reader never has to tell "there were none" apart from "the writer did not say". Second, **there is no attribute that counts passing tests**. The schema declares none, so every green number in every report built on this format is *computed* -- almost always `@tests` minus the other three -- and it inherits whatever is wrong with them. ## What the schema does not guarantee This is the part that separates people who have read the schema from people who have read about it. The schema constrains which attributes exist, which of them are required, and which child elements may appear in which order. It constrains nothing about the relationship between an attribute's value and the elements beside it. Specifically, it does not require: - that a counter even be numeric -- the four counts are declared as string-typed attributes; - that `@tests` equal the number of `<testcase>` children; - that `@failures` equal the number of cases carrying a `<failure>` child; - that `@tests` be at least `@failures` plus `@errors` plus `@skipped`; - that two files written by the same run agree with each other in any way at all. A document whose header says `tests="10"` above nine `<testcase>` elements is **valid**. A validating parser accepts it, because a schema of this shape types attributes and constrains structure and then stops; it has no vocabulary for arithmetic across elements. The only thing that ever notices the contradiction is the program reading the file. ## Two readers, one file, two totals Because the file states the same quantity twice -- once in the header, once implicitly through the children -- every consumer has to pick a side, usually without ever documenting which: - A **header-trusting** reader takes `@tests`, `@failures`, `@errors` and `@skipped` straight off the opening tag. It is fast, it streams, and it reproduces any arithmetic mistake in the writer verbatim. - A **child-counting** reader ignores the counters and classifies each `<testcase>` itself. It cannot be misled by the header, but it must parse the whole document, and it disagrees with the header whenever it classifies a case differently from the writer that produced it. Neither behaviour is wrong, and the format cannot arbitrate between them. That is the mundane reason two dashboards fed the same artefact can report different totals for the same run while both work exactly as designed. ## Working with the counters 1. **Decide which side is your contract** -- the header or the children -- and write that decision down where the pipeline's owners can find it. 2. **Compare the two at ingest.** Recount the children once, hold that against the header, and treat a mismatch as a defect in the writer rather than as data to be reconciled. 3. **Never present a derived pass count as though it were declared.** If `@tests` minus the other three goes negative or exceeds the number of cases present, surface that instead of clamping it to something plausible. 4. **Do not infer timing from the counters.** `@time` and `@timestamp` are optional and may simply be absent; a missing duration is not a zero duration. The counters are a summary the writer asserts, not a fact the format checks. Read them as a claim, verify the claim once at the boundary, and everything downstream stays honest.

  • A writer emits `failures="2"` but only one `<testcase>` carries a `<failure>` child. What does validating that file against the schema report?
    Nothing. The counters are plain attributes, and a schema of this kind cannot relate one element's attribute value to the number of sibling elements, so the document is valid. Only the consumer ever notices, and which number it shows depends entirely on whether it trusts the header or recounts the children.
  • Given the attributes this schema declares, where does a report's count of passing tests come from?
    It is always derived, because no attribute counts passes. A report computes it, almost always as `@tests` minus `@failures`, `@errors` and `@skipped`. That subtraction inherits every inconsistency in the header, which is how a report ends up showing an impossible or negative pass count when a writer's arithmetic is off.

saying these in an interview costs you the question

  • Assumes @tests excludes skipped or failed cases
  • Thinks schema validation catches wrong counter values
  • Believes @time and @timestamp are always present
  • Expects an attribute that counts passing tests
  • Says @failures must equal the number of <failure> children