<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Test-Design on Brian Faust</title><link>https://vacua.dev/tags/test-design/</link><description>Recent content in Test-Design on Brian Faust</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate><atom:link href="https://vacua.dev/tags/test-design/rss.xml" rel="self" type="application/rss+xml"/><item><title>A Characterization Test Is a Boundary, Not a Recording</title><link>https://vacua.dev/a-characterization-test-is-a-boundary-not-a-recording/</link><pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/a-characterization-test-is-a-boundary-not-a-recording/</guid><description>&lt;p&gt;An undocumented postage calculator needs to be split apart. Before touching
it, we put its current behavior under test.&lt;/p&gt;
&lt;p&gt;One suite stores the returned quote, every intermediate calculation, the order
of the diagnostic fields, and the sequence in which four pricing rules run.
Another asserts the caller&amp;rsquo;s result across weight, insurance, destination, and
discount boundaries.&lt;/p&gt;
&lt;p&gt;Both suites are green. Both characterize the current implementation. They do
not buy the same freedom to change it.&lt;/p&gt;</description></item><item><title>A Surviving Mutant Is a Question, Not a Score</title><link>https://vacua.dev/a-surviving-mutant-is-a-question/</link><pubDate>Fri, 01 Mar 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/a-surviving-mutant-is-a-question/</guid><description>&lt;p&gt;Two PHPUnit suites are green. The first checks a known product. The second also
checks that a missing product throws &lt;code&gt;ProductNotFound&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Run two mutations against them. The extra assertion kills the missing-exception
defect, exactly as intended. Yet a 100% mutation-score gate rejects both suites.
The stronger suite finishes at 50% because a second mutation changes no behavior
for any input the method accepts.&lt;/p&gt;
&lt;p&gt;The gate has calculated its number correctly. It has not interpreted the
survivor.&lt;/p&gt;</description></item><item><title>An Interface Does Not Make a Fake Substitutable</title><link>https://vacua.dev/shared-contract-tests-make-substitutability-measurable/</link><pubDate>Thu, 01 Feb 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/shared-contract-tests-make-substitutability-measurable/</guid><description>&lt;p&gt;An HTTP product catalogue and an in-memory fake both implement this interface:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-php" data-lang="php"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ProductCatalogue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sd"&gt;/** @throws ProductNotFound */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;priceCents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Both return &lt;code&gt;10000&lt;/code&gt; for &lt;code&gt;CHAIR-BLACK&lt;/code&gt;. Both make the checkout test green. Both
pass PHP&amp;rsquo;s signature checks.&lt;/p&gt;
&lt;p&gt;Now seed one divergence. When the product is absent, the HTTP adapter throws
&lt;code&gt;ProductNotFound&lt;/code&gt; while the fake returns zero. The interface remains satisfied,
and the familiar checkout test still passes because it never asks for a
missing SKU.&lt;/p&gt;</description></item><item><title>A Mock Fortress Tests a Call Script</title><link>https://vacua.dev/a-mock-fortress-tests-a-call-script/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://vacua.dev/a-mock-fortress-tests-a-call-script/</guid><description>&lt;p&gt;Two tests produce the same checkout quote. One builds five Mockery doubles,
specifies every argument, fixes their global call order, and makes a serializer
mock return the expected JSON. The other resolves the use case through
Laravel&amp;rsquo;s container, replaces one HTTP catalogue, and asserts the JSON emitted
by the real policies and serializer.&lt;/p&gt;
&lt;p&gt;Both are green. That tells us they agree with today&amp;rsquo;s code. It does not tell us
what either test will reject tomorrow.&lt;/p&gt;</description></item><item><title>Choose the Test Boundary from the Defect</title><link>https://vacua.dev/choose-the-test-boundary-from-the-defect/</link><pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate><guid>https://vacua.dev/choose-the-test-boundary-from-the-defect/</guid><description>&lt;p&gt;Four test boundaries protect the same reservation flow. Then four defects
arrive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a quantity of zero becomes valid;&lt;/li&gt;
&lt;li&gt;a database migration loses a uniqueness constraint;&lt;/li&gt;
&lt;li&gt;an inventory provider renames &lt;code&gt;available&lt;/code&gt; to &lt;code&gt;stock&lt;/code&gt;; and&lt;/li&gt;
&lt;li&gt;browser JavaScript submits an empty string instead of the entered quantity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Calling all four tests “feature tests” would tell us almost nothing. Putting
them into a pyramid would tell us how many tests we hope to own, but not which
one can catch each defect.&lt;/p&gt;</description></item><item><title>Coverage Tells You What Ran, Not What Your Tests Would Catch</title><link>https://vacua.dev/coverage-tells-you-what-ran/</link><pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate><guid>https://vacua.dev/coverage-tells-you-what-ran/</guid><description>&lt;p&gt;Here are two PHPUnit suites for the same shipping-fee rule. Both report 100%
line coverage. Both also report 100% branch and path coverage. One suite lets
an off-by-one defect escape; the other catches it.&lt;/p&gt;
&lt;p&gt;Nothing is wrong with the coverage report. The mistake is asking it a question
it cannot answer.&lt;/p&gt;
&lt;p&gt;Coverage can tell us which executable structure a test run visited. It cannot
tell us whether the chosen inputs and assertions would notice a defect that
matters. The distinction sounds modest. It changes how a coverage target
should be used.&lt;/p&gt;</description></item><item><title>Test Process State, Not Message Choreography</title><link>https://vacua.dev/test-process-state-not-message-choreography/</link><pubDate>Sun, 01 Oct 2023 00:00:00 +0000</pubDate><guid>https://vacua.dev/test-process-state-not-message-choreography/</guid><description>&lt;p&gt;A strict mock test expects five calls in order:&lt;/p&gt;
&lt;figure class="code-figure"&gt;
 &lt;div class="code-block" data-copy-code&gt;
 &lt;button class="copy-code" type="button" aria-label="Copy code"&gt;copy&lt;/button&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;load process
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;check message ID
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dispatch ReserveInventory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;save process
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;record inbox&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Every expectation passes. Unfortunately, &lt;code&gt;save process&lt;/code&gt; writes the old
&lt;code&gt;awaiting_payment&lt;/code&gt; state. The command says inventory should be reserved while
the durable process still says it is waiting for payment.&lt;/p&gt;
&lt;p&gt;Now fix the persistence and move &lt;code&gt;record inbox&lt;/code&gt; before &lt;code&gt;save process&lt;/code&gt; inside
the same database transaction. The committed process, inbox, and outbox rows
are identical. The strict test fails because two calls changed places.&lt;/p&gt;</description></item></channel></rss>