TDD and Xray: Two Testing Levels, One Quality Picture
Vote for this post
Click the arrows to vote • 1 vote per logged in user
Login to Vote
TDD and Xray: Two Testing Levels, One Quality Picture
Teams that adopt both Test Driven Development and Jira with Xray frequently run into the same impasse. The developer has written forty unit tests before the pull request is opened. The Xray dashboard shows zero test coverage against the User Story. The developer and the Scrum Master are looking at the same sprint from opposite ends of a telescope – and both of them are correct.
The confusion is entirely understandable. Both TDD and Xray are associated with testing. Both are held up as markers of a mature engineering team. But they operate at entirely different levels of the quality picture, and conflating them produces one of two painful outcomes: developers writing slow, over-specified tests to satisfy Xray, or Xray records maintained by hand and drifting out of date within a fortnight. This article explains the difference, shows how each approach maps to the tools you are already using in PHP/Laravel and C# .NET, and proposes a practical three-layer architecture that connects them into a coherent picture without the overhead of maintaining both separately.

The testing pyramid: TDD unit tests live at the base – hundreds of them, millisecond-fast, invisible to Xray by design. User Story coverage belongs at the middle and upper layers, where feature tests and Gherkin scenarios operate.
What TDD Actually Tests
TDD is a software design discipline. The Red-Green-Refactor cycle – write a failing test, make it pass with the minimum possible code, then refactor with confidence – is not primarily a testing strategy. It is a design strategy that produces tests as a side effect. The failing test defines the interface you wish existed. The implementation then conforms to that specification. The tests are the contract, not the proof of completion.
The tests produced by TDD are unit tests. They test a single class, a single method, or a single interface in isolation, typically replacing any external dependency with a mock or stub. In PHP and Laravel, these live in tests/Unit/ and run with PHPUnit or Pest inside PHPStorm. In C# .NET, the equivalent tools are xUnit, NUnit, or MSTest, running inside Visual Studio Test Explorer or the dotnet test CLI. A mature TDD suite runs in under a second per test class and gives the developer near-instant feedback on every change.
Consider a User Story: "As a buyer, I want to add items to my cart so that I can purchase them later." The developer implementing this might produce TDD unit tests for: Cart::add() correctly updating item quantities; Cart::total() summing line items accurately; CartRepository::persist() saving state to the database; CartService::addItem() orchestrating the domain logic; and CartController::store() returning the correct HTTP response. Five classes, perhaps thirty individual test cases, all TDD, all critical to design quality – and all completely invisible to Xray unless you explicitly connect them. That invisibility is not a bug. It is by design.
it('calculates the correct total when tax is applied', function () {
$cart = new Cart();
$cart->add(new CartItem('Widget', 10.00, 2));
expect($cart->totalWithTax(0.20))->toBe(24.00);
});
it('rejects a negative quantity and throws an exception', function () {
$cart = new Cart();
expect(fn () => $cart->add(new CartItem('Widget', 10.00, -1)))
->toThrow(InvalidArgumentException::class);
});
These tests run in milliseconds in PHPStorm. They are the developer's design safety net. They are not User Story verification. Writing them does not mean the User Story has been tested – it means the classes that implement it have been designed correctly.
What Xray Actually Tracks
Xray is a test management platform embedded in Jira. Its job is not to run tests – it is to record that tests were run, connect those results to requirements, and give the whole team a traceable picture of quality coverage. When a Product Owner or release manager asks "how do we know User Story 142 was adequately tested?" – Xray is where the answer lives, not the IDE.
Xray supports four categories of test, each serving a different layer of the quality picture:
| Xray Test Type | How It Works | Best Used For |
|---|---|---|
| Manual | Numbered steps a QA analyst follows by hand, recording pass or fail at each step | Exploratory testing, UAT, edge cases that are hard to automate |
| Cucumber / BDD | Given/When/Then scenarios in Gherkin format, automated with Behat or SpecFlow | Acceptance criteria written in business language, automated and traceable |
| Generic | Free-form test definitions not constrained to a specific format | Security checks, performance benchmarks, exploratory results |
| Automated | Results imported from CI/CD pipelines via the Xray REST API or JUnit XML upload | Connecting existing PHPUnit, Pest, xUnit, or NUnit test runs to Jira stories |
Xray's model is built around requirements traceability. A User Story has zero or more Xray tests linked to it. A Test Plan groups tests for a release cycle. A Test Execution records an actual run and its results. When all linked tests pass, the story is considered verified. What Xray is not doing is telling a developer how to structure classes or drive design decisions. It cares whether the feature, as described in the User Story, has been verified to work – not how many interfaces were needed to implement it.
The Testing Pyramid: Why They Operate at Different Levels
The tension between TDD and Xray exists because TDD produces evidence at the wrong granularity for Xray to consume directly. A single User Story acceptance criterion – "a buyer can add an item to their cart" – is verified by an entire chain of TDD tests beneath it, not by any one of them individually. This is not a flaw in either tool. It reflects the testing pyramid, a foundational model in software quality that every serious QA approach is built around.
The pyramid has three distinct layers. At the base sit the unit tests: hundreds of them, fast, developer-written, TDD-driven, testing implementation correctness at the class and method level. In the middle sit the integration and feature tests: fewer in number, slower to run, testing multiple components working together at the application boundary. At the apex sit the acceptance and end-to-end tests: fewest of all, slowest of all, testing the system from the outside as a user would, expressed in business language. TDD operates at the base. Xray is most naturally home to the middle and upper layers. The problem most teams encounter is that no one builds the middle layer – they try to make unit tests serve as acceptance evidence, or they maintain Xray manually and abandon it within a sprint.
| Pyramid Layer | PHP / Laravel / PHPStorm | C# .NET / Visual Studio | Goes to Xray? |
|---|---|---|---|
| Unit (TDD) | PHPUnit or Pest – tests/Unit/ | xUnit, NUnit, or MSTest | No – stays in the IDE |
| Feature / Integration | Pest / PHPUnit HTTP tests – tests/Feature/ | ASP.NET TestServer integration tests | Yes – via JUnit XML import |
| Acceptance / BDD | Behat with Gherkin .feature files | SpecFlow with Gherkin .feature files | Yes – authored in Xray, results via API |
The First Bridge: Feature Tests at the User Story Level
The most practical connection point between TDD and Xray in PHP/Laravel is the feature test. Laravel ships with two test directories: tests/Unit/ for isolated class-level tests and tests/Feature/ for tests that make real HTTP requests through the full application stack. A feature test operates at the User Story level. It says: given this application state and this user action, the system responds in this specific way. One feature test per acceptance criterion on the User Story is the target.
it('allows an authenticated buyer to add a product to their cart', function () {
$buyer = User::factory()->create();
$product = Product::factory()->create(['price' => 10.00]);
$response = $this->actingAs($buyer)
->postJson('/api/cart/items', [
'product_id' => $product->id,
'quantity' => 2,
]);
$response
->assertStatus(201)
->assertJsonFragment(['cart_total' => 20.00]);
});
This test covers one User Story acceptance criterion end to end. It is not testing a class in isolation – it is testing a behaviour that a buyer can actually perform, through the real controller, service, and repository chain. It reads like an acceptance criterion because it is one. And unlike a unit test, it can be exported to Xray.
PHPUnit and Pest both produce JUnit-compatible XML with a single flag. For C# .NET, xUnit, NUnit, and MSTest all support the same output format via the dotnet test logger. This XML file is what you POST to the Xray REST API from your CI/CD pipeline after every merge to main.
# PHP / Laravel (Pest or PHPUnit) ./vendor/bin/pest --log-junit storage/test-results/junit.xml # C# .NET (xUnit, NUnit, or MSTest) dotnet test --logger "junit;LogFilePath=test-results/junit.xml"
With a single Xray API call in a GitHub Actions or GitLab CI step, these results are posted to Jira. The User Story shows green automated coverage. No one maintains it manually. The CI/CD pipeline does the connecting.
The Second Bridge: Gherkin Scenarios Authored in Xray
For teams who want User Story acceptance criteria to live in Jira rather than in the codebase, BDD with Gherkin is the most powerful option available. Xray supports Gherkin natively as a first-class test type. You write the scenario directly in the Xray test record. Xray stores it as a .feature file. Your CI/CD pipeline pulls it down, executes it with Behat (PHP) or SpecFlow (C# .NET), and reports results back to Xray automatically via the API. The User Story shows verified coverage in business language, and no developer had to maintain an Xray record by hand.
Feature: Shopping Cart
Scenario: Authenticated buyer adds a product to the cart
Given I am logged in as a buyer
And the product "Widget" is available at a price of 10.00
When I add 2 units of "Widget" to my cart
Then the cart total should be 20.00
And the response status should be 201
A Product Owner or business analyst can read this scenario without any knowledge of PHP or C#. It is a specification, not a test script. The developer writes the corresponding Behat step definitions in PHP or SpecFlow bindings in C# – these are the methods that translate each Given/When/Then line into executable application code. The scenario in Xray becomes living documentation: if the application behaviour changes and breaks the scenario, the Xray Test Execution automatically records a failure against the linked User Story. There is no drift, because the automation enforces itself.
This approach also solves a problem that plagues manual Xray tests. A manual test written six months ago may describe behaviour the application no longer supports. The QA team updates it only when someone notices the discrepancy – often after a failed release. An automated Gherkin scenario fails loudly the moment the behaviour changes, long before it reaches production. It is not documentation that describes the system; it is documentation that verifies the system.

The BDD pipeline: acceptance scenarios authored in Xray are pulled as .feature files by Behat or SpecFlow, executed against the application, and results reported back automatically. The User Story shows verified coverage without anyone touching Jira manually.
The Proposed Architecture: Three Connected Layers
The solution to the TDD-versus-Xray dilemma is not to choose between them. It is to accept that they were always designed for different layers of the same pyramid, and to build the middle layer that most teams skip. Here is how the three layers connect in a working sprint.
Layer One – Unit Tests (TDD)
PHP: PHPUnit or Pest in tests/Unit/, run in PHPStorm. C#: xUnit, NUnit, or MSTest in Visual Studio.
Granularity: class and method level. Speed: milliseconds per suite. Thirty or more tests per User Story is normal and correct.
This layer does not go to Xray. It is the developer's design safety net – fast, granular, and entirely owned by the development team.
Layer Two – Feature Tests
PHP: Pest or PHPUnit HTTP tests in tests/Feature/. C#: ASP.NET TestServer integration tests.
One test per User Story acceptance criterion. Runs the full application stack. Speed: seconds per test in CI/CD.
This layer goes to Xray via JUnit XML. The CI/CD pipeline exports and imports automatically on every merge. No manual Xray maintenance required.
Layer Three – BDD Scenarios
PHP: Behat with .feature files. C#: SpecFlow with .feature files. Language: Gherkin (Given/When/Then).
Scenarios authored directly in Xray by QA or BA. Readable by Product Owners without any code knowledge.
This layer lives in Xray and returns to Xray. Results reported via API after each staging environment run. Living documentation that enforces itself.
To see how this works in a sprint: User Story 142, "As a buyer, I want to add items to my cart," is picked up. The developer writes thirty TDD unit tests across five classes in PHPStorm using Pest. These run in milliseconds and stay in tests/Unit/. The developer also writes three feature tests in tests/Feature/ – one per acceptance criterion on the story. When the pull request merges, the CI/CD pipeline runs all tests, exports JUnit XML, and POSTs to Xray. User Story 142 in Jira shows three automated tests, all green – without anyone opening Jira during development. Separately, the QA team has authored three Gherkin scenarios directly in Xray. Behat executes them against the staging environment after deployment. The Product Owner opens the story and sees business-readable acceptance evidence alongside automated coverage – both layers visible in Jira, neither maintained manually.
Three layers, one quality picture. TDD keeps the codebase clean. Feature tests verify User Story behaviour and connect to Xray automatically via CI/CD. BDD scenarios give stakeholders readable, self-enforcing evidence in Jira.
Signs You Have Got This Right – and Signs You Have Not
The Three-Layer Test: Healthy Signals vs. Warning Signs
- TDD unit tests are being manually mapped to Xray tests one by one, creating hundreds of Xray records that add noise rather than traceability
- Developers are writing Gherkin in PHPStorm to satisfy Xray, slowing down the Red-Green-Refactor cycle and making unit tests brittle and verbose
- Xray shows zero automated coverage because JUnit XML is never exported from the CI/CD pipeline after test runs
- User Stories are marked Done in Jira before any feature test covers their acceptance criteria
- Behat or SpecFlow scenarios duplicate the PHPUnit or xUnit feature tests, running identical assertions twice at double the pipeline cost
- Manual Xray tests drift out of date because they are updated separately from the code that implements the feature
- TDD unit tests live exclusively in the IDE, run in under a second per suite, and never appear in Xray individually
- One feature test per User Story acceptance criterion exists in tests/Feature/, runs on CI, and imports to Xray automatically on every merge
- Product Owners and QA analysts can read Xray scenarios without opening the IDE or reading a line of PHP or C#
- Xray shows green coverage against User Stories through automated imports – no one updated it manually
- When a feature changes and breaks a Gherkin scenario, Xray automatically records a failed execution against the User Story before it reaches production
- The developer's Red-Green-Refactor cycle is completely unaffected by Xray – TDD stays fast and clean at the bottom of the pyramid
TDD produces test evidence as a side effect of driving better design. Xray consumes test evidence as input to requirements traceability. They operate at different altitudes of the same quality pyramid and were never meant to compete. Build the middle layer – one feature test per acceptance criterion, exported to Xray automatically by CI/CD – and the dilemma dissolves. Unit tests stay fast and granular at the bottom. Feature tests bridge the gap to Jira in the middle. Gherkin gives stakeholders readable, self-enforcing proof at the top. You get clean code, traceable coverage, and a Jira board that reflects reality without anyone maintaining it by hand.
Further Reading
The tools and concepts in this article each have authoritative documentation worth reading. The Xray REST API documentation covers JUnit XML import, Gherkin scenario management, and CI/CD integration in detail. Martin Fowler's writing on the testing pyramid and TDD remains the clearest articulation of why these layers exist and why they must not be collapsed into each other.
Leave a Comment