<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Testing on Brian Faust</title><link>https://wimdy.works/tags/testing/</link><description>Recent content in Testing 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://wimdy.works/tags/testing/rss.xml" rel="self" type="application/rss+xml"/><item><title>Testing the Go Port Against PHP</title><link>https://wimdy.works/thoughts/make-cross-language-checks-explain-their-differences/</link><pubDate>Fri, 23 Oct 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/make-cross-language-checks-explain-their-differences/</guid><description>&lt;p&gt;A Go function can return the same failure code as the PHP function and still
behave differently in the application. Laravel might replace the exception
before the PHP resolver sees it. A database reader might fail while loading a
relationship that a direct unit test supplied as an already populated struct.
Neither difference appears in a test which compares only the final price.&lt;/p&gt;
&lt;p&gt;The rating engine&amp;rsquo;s migration checks therefore needed more than translated
assertions. They needed executable observations from PHP, a clear point at
which to compare them with Go, and enough detail to explain a disagreement.
Otherwise, “the implementations match” could mean little more than “both tests
contain the same assumption.”&lt;/p&gt;</description></item><item><title>Retire the Browser, Keep the Test Runner</title><link>https://wimdy.works/thoughts/retire-the-browser-keep-the-test-runner/</link><pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/retire-the-browser-keep-the-test-runner/</guid><description>&lt;p&gt;The Go port deliberately dropped the browser UI. The API tester was the awkward
exception: not its pages, but the program behind them. It could create a test
configuration, publish it, make real API calls, extract identifiers, wait for a
result and clean up afterward. Deleting its screen was straightforward.
Deleting all of that would have thrown away a useful part of the migration.&lt;/p&gt;
&lt;p&gt;Replacing it with a CLI also exposed distinctions the screen could make easy
to overlook. An HTTP 200 response could mean a failed test step. A failed run
could still be a successfully produced command result. A saved step record
didn&amp;rsquo;t mean its API call would never execute again. Those weren&amp;rsquo;t reasons to
redesign the tester during the port; they were behavior to understand before
moving its entry point.&lt;/p&gt;</description></item><item><title>A Rewrite Can Change Data Without Changing the Price</title><link>https://wimdy.works/thoughts/a-rewrite-can-change-data-without-changing-the-price/</link><pubDate>Thu, 01 Oct 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-rewrite-can-change-data-without-changing-the-price/</guid><description>&lt;p&gt;The Go history append preserved the existing JSON. That looked reasonable:
read a stored transition list, add the new transition, leave everything else
alone. Unfortunately, leaving everything else alone wasn&amp;rsquo;t what the PHP
implementation did.&lt;/p&gt;
&lt;p&gt;The shared idempotency updater in my rating engine read history through an
Eloquent array cast, appended a transition, and assigned the resulting value
back to the model. That round trip could turn an old nested &lt;code&gt;{}&lt;/code&gt; into &lt;code&gt;[]&lt;/code&gt;.
The Go translation preserved &lt;code&gt;{}&lt;/code&gt; instead. Both could complete the operation
and return the same price while storing different histories.&lt;/p&gt;</description></item><item><title>A PHP Test File Isn't One Migration Case</title><link>https://wimdy.works/thoughts/a-php-test-file-isnt-one-migration-case/</link><pubDate>Tue, 01 Sep 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-php-test-file-isnt-one-migration-case/</guid><description>&lt;p&gt;One PHP file contained two test bodies and eleven migration cases. Counting
it as one file would hide ten of them. Counting its two &lt;code&gt;it(...)&lt;/code&gt; declarations
would still miss most of the work. Copying the first assertion into a Go test
would look plausible and preserve almost nothing of the original matrix.&lt;/p&gt;
&lt;p&gt;The file covered customer lifecycle states in the pricing application. One
body tested eight transitions; another tested three sets of state predicates.
It was small enough to follow completely, which makes it a useful way into
the much larger problem: using an existing PHP suite to constrain a Go port
without pretending that translating test syntax proves equivalent behavior.&lt;/p&gt;</description></item><item><title>Is Your Benchmark Doing New Work?</title><link>https://wimdy.works/thoughts/is-your-benchmark-doing-new-work/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/is-your-benchmark-doing-new-work/</guid><description>&lt;p&gt;One of the rating engine&amp;rsquo;s early benchmark fixes made the results slower.
That was the improvement. The runner had reused request identities across
invocations, so later runs could return stored results instead of evaluating
new quotes. The endpoint was doing its job. The benchmark wasn&amp;rsquo;t measuring
the job I thought it was.&lt;/p&gt;
&lt;p&gt;This is an awkward mistake because the output can look entirely healthy:
successful HTTP responses, small latencies, no obvious application errors.
It is also worth dealing with before celebrating a faster
&lt;a href="https://wimdy.works/thoughts/stop-scanning-every-tariff-row-for-every-service/"&gt;index&lt;/a&gt;
 or
&lt;a href="https://wimdy.works/thoughts/a-cache-hit-still-has-a-payload/"&gt;cache read&lt;/a&gt;
. A replay can skip the
calculation whose improvement you&amp;rsquo;re trying to measure.&lt;/p&gt;</description></item><item><title>The Order of Charges Changes the Price</title><link>https://wimdy.works/thoughts/the-order-of-charges-changes-the-price/</link><pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/the-order-of-charges-changes-the-price/</guid><description>&lt;p&gt;Two row orders can produce the same €35 quote while disagreeing about the
fuel charge. In one, fuel is €2.70; in the other, it&amp;rsquo;s €2.40. A minimum-charge
top-up hides the difference in the total. Assert only that total and the test
passes either way.&lt;/p&gt;
&lt;p&gt;The difference comes from when the fuel row sees a handling charge. The
percentage reads the components already built, not a final basket reconstructed
after every row has run. Scope also limits which of those components it can
use. A shipment percentage doesn&amp;rsquo;t automatically include parcel charges just
because they contribute to the same quote.&lt;/p&gt;</description></item><item><title>A Design Is Good for a Change, Not in Isolation</title><link>https://wimdy.works/thoughts/a-design-is-good-for-a-change-not-in-isolation/</link><pubDate>Wed, 01 May 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-design-is-good-for-a-change-not-in-isolation/</guid><description>&lt;p&gt;Two delivery-option implementations return the same quotes.&lt;/p&gt;
&lt;p&gt;One has a &lt;code&gt;DeliveryService&lt;/code&gt; interface, a class for each service, and a tagged
Laravel registry. The other has a validated rule table and one evaluator. The
registry looks extensible. The table looks compact.&lt;/p&gt;
&lt;p&gt;Which design is better?&lt;/p&gt;
&lt;p&gt;You cannot answer yet. “Extensible” names a possibility, not a requirement.
“Compact” counts structure, not the decisions a future change will disturb.&lt;/p&gt;
&lt;p&gt;Apply a locker-delivery requirement and the table absorbs the change in one
policy definition. Reset to the original baseline, add capacity-aware same-day
delivery, and the table has to grow a behavior interface, a collaborator, and
new failure semantics. Its old advantage belonged to one kind of change.&lt;/p&gt;</description></item><item><title>A Characterization Test Is a Boundary, Not a Recording</title><link>https://wimdy.works/thoughts/a-characterization-test-is-a-boundary-not-a-recording/</link><pubDate>Mon, 01 Apr 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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, its current behavior goes 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://wimdy.works/thoughts/a-surviving-mutant-is-a-question/</link><pubDate>Fri, 01 Mar 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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. That distinction is the whole problem: the stronger suite is better,
but the dashboard makes it look half finished.&lt;/p&gt;</description></item><item><title>An Interface Does Not Make a Fake Substitutable</title><link>https://wimdy.works/thoughts/shared-contract-tests-make-substitutability-measurable/</link><pubDate>Thu, 01 Feb 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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://wimdy.works/thoughts/a-mock-fortress-tests-a-call-script/</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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. Five mocks can look impressively precise
while carefully excluding the code most likely to be wrong.&lt;/p&gt;</description></item><item><title>Choose the Test Boundary from the Defect</title><link>https://wimdy.works/thoughts/choose-the-test-boundary-from-the-defect/</link><pubDate>Fri, 01 Dec 2023 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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://wimdy.works/thoughts/coverage-tells-you-what-ran/</link><pubDate>Wed, 01 Nov 2023 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/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. It did its job. The mistake is
turning a comforting number into an answer to 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. That sounds like a small distinction until a green suite and a perfect
score ship the wrong price.&lt;/p&gt;</description></item><item><title>Construction Should Not Hide Durable Writes</title><link>https://wimdy.works/thoughts/construction-should-not-hide-durable-writes/</link><pubDate>Tue, 01 Sep 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/construction-should-not-hide-durable-writes/</guid><description>&lt;p&gt;A test called a model factory&amp;rsquo;s &lt;code&gt;make()&lt;/code&gt; method. The model itself was never
saved. Two related records were.&lt;/p&gt;
&lt;p&gt;The factory computed the model&amp;rsquo;s default address and account fields by creating
real database rows. A test which only needed an in-memory user therefore changed
shared state before it reached its first assertion.&lt;/p&gt;
&lt;p&gt;A surrounding test transaction usually hid the damage by rolling it back.
Tests without that transaction left the rows behind, while another test used
&lt;code&gt;TRUNCATE&lt;/code&gt; for cleanup, committed the MySQL transaction, and made earlier writes
survive the rollback. The method returned an unpersisted object, but its name
still gave the wrong account of what had happened.&lt;/p&gt;</description></item><item><title>Global State Makes Local Reasoning Impossible</title><link>https://wimdy.works/thoughts/global-state-makes-local-reasoning-impossible/</link><pubDate>Sat, 01 Aug 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/global-state-makes-local-reasoning-impossible/</guid><description>&lt;p&gt;A repository test created a shipment two days before &lt;code&gt;now&lt;/code&gt;, then counted
shipments between the start and end of the current month. It passed most days.
During the first two days of a month, “two days ago” belonged to the previous
month and the count became zero.&lt;/p&gt;
&lt;p&gt;Nothing in the test name mentioned a calendar boundary, and nothing in the
method signature mentioned time. The same test logic produced a different
result because its generated data moved with one more input: the instant at
which the test ran. Freezing the clock in the middle of the month stabilized
the example. It did not make the dependency local.&lt;/p&gt;</description></item><item><title>Dependencies Should Be Visible at Construction</title><link>https://wimdy.works/thoughts/dependencies-should-be-visible-at-construction/</link><pubDate>Mon, 01 Jun 2020 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/dependencies-should-be-visible-at-construction/</guid><description>&lt;p&gt;This line created a database record:&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;span class="sr-only" data-copy-status role="status" aria-live="polite"&gt;&lt;/span&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="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UserFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="na"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="na"&gt;create&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="s1"&gt;&amp;#39;external_id&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;123&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;It also performed an HTTP request.&lt;/p&gt;
&lt;p&gt;The factory did not mention a client. The model did not accept one. A
synchronous observer noticed the external identifier, prepared a profile
export, and resolved a connector from global framework state. The connector
then looked up a remote account.&lt;/p&gt;
&lt;p&gt;In tests without a fake, that lookup reached DNS or the network and retried
three times with exponential backoff. Tests which had run in under five
seconds took more than thirty. The slow test was annoying, but it was only the
visible symptom. A local-looking construction operation had acquired an
undeclared remote dependency.&lt;/p&gt;</description></item><item><title>A Rewrite Needs a Behavior Ledger</title><link>https://wimdy.works/thoughts/a-rewrite-needs-a-behavior-ledger/</link><pubDate>Wed, 01 May 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-rewrite-needs-a-behavior-ledger/</guid><description>&lt;p&gt;A desktop rewrite had screens for shipments, reports, settings, and logs. If we
had tracked the work as a feature checklist, all four could have been marked
complete and everyone could have gone home feeling productive.&lt;/p&gt;
&lt;p&gt;The old application also had behavior no screen list would reveal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a failed temporary-file cleanup must not turn a submitted print into a
failure, or the next run may print the label again;&lt;/li&gt;
&lt;li&gt;the first run treats older unprinted records differently from later runs;&lt;/li&gt;
&lt;li&gt;setup unlocks when a credential setting exists, not when its value is
non-empty; and&lt;/li&gt;
&lt;li&gt;a visible report search box does nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of those rules protect operators. One is an awkward contract. One is
probably a dead feature. Calling all of them “parity” hides the decision we
actually need to make. A rewrite needs a behavior ledger: a finite record of
what must survive, what will change deliberately, what may be dropped, and what
evidence closes each decision.&lt;/p&gt;</description></item><item><title>A Characterization Test Is Not a Product Decision</title><link>https://wimdy.works/thoughts/a-characterization-test-is-not-a-product-decision/</link><pubDate>Fri, 01 Mar 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-characterization-test-is-not-a-product-decision/</guid><description>&lt;p&gt;A label setting behaved differently in test and production. Some customers
had come to depend on the difference even though the product already let them
choose the label size they wanted.&lt;/p&gt;
&lt;p&gt;A characterization test could record both defaults. It could tell us which
callers would notice if we made them equal. It could not answer the important
question: should the difference survive?&lt;/p&gt;
&lt;p&gt;Keeping it would protect customers who had never configured the setting.
Removing it would give every environment one rule and make the customer&amp;rsquo;s
choice explicit. The same observation supported arguments for either response.
Only one response matched the product we wanted to operate.&lt;/p&gt;</description></item><item><title>A Characterization Suite Is Scoped to a Change</title><link>https://wimdy.works/thoughts/a-characterization-suite-is-scoped-to-a-change/</link><pubDate>Fri, 01 Feb 2019 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-characterization-suite-is-scoped-to-a-change/</guid><description>&lt;p&gt;A booking endpoint had more than thirty characterization cases. They covered
successful bookings, validation failures, wallet rules, locks, pending work,
email routing, and several provider response shapes.&lt;/p&gt;
&lt;p&gt;The next refactor removed one ambient HTTP dependency from the booking
services. The suite looked broad enough. It was not broad in the direction of
the change.&lt;/p&gt;
&lt;p&gt;One service read the global request to persist the original payload. Replacing
that read with the existing booking data object would keep ordinary responses
green while silently storing normalized values where operators expected the
raw input. The suite already protected a test-mode short circuit. It needed a
new case for the two representations.&lt;/p&gt;</description></item><item><title>A Capability Interface Is Smaller Than a Vendor Client</title><link>https://wimdy.works/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</link><pubDate>Sat, 01 Dec 2018 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/a-capability-interface-is-smaller-than-a-vendor-client/</guid><description>&lt;p&gt;A controller needs to register an order webhook. Instead, it constructs a
vendor SDK connection, accesses a dynamic resource property, calls its creation
method, names the remote event, chooses response fields, and catches the SDK&amp;rsquo;s
base exception. That is a lot of vendor vocabulary for one product decision.&lt;/p&gt;
&lt;p&gt;The test is no better. It has to intercept construction of that concrete
connection and mock a dynamic property chain. Two useful behavior tests ended
up skipped with the same explanation: the code needed an interface and
dependency injection.&lt;/p&gt;</description></item><item><title>Test Cannot Prove Production When It Runs Different Rules</title><link>https://wimdy.works/thoughts/test-cannot-prove-production-when-it-runs-different-rules/</link><pubDate>Sat, 01 Oct 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/test-cannot-prove-production-when-it-runs-different-rules/</guid><description>&lt;p&gt;A label-layout change passed in test and disrupted document distribution in
production. The same revision was deployed to both environments and the input
looked equivalent. Our test still could not predict what customers received.&lt;/p&gt;
&lt;p&gt;We had built that disagreement on purpose. Test loaded one default and
production loaded another because some users had never configured the layout
they wanted. The option existed; our environment branch kept compensating for
the omission. A new recommendation passed against the test rule while
production quietly ran a different product decision.&lt;/p&gt;</description></item><item><title>Production Bugs Can Live in the Data, Not the Code</title><link>https://wimdy.works/thoughts/production-bugs-can-live-in-the-data-not-the-code/</link><pubDate>Mon, 01 Feb 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/production-bugs-can-live-in-the-data-not-the-code/</guid><description>&lt;p&gt;A delivery integration returned a label, but the application could not save
it. The same persistence path had passed with the fixtures used elsewhere.&lt;/p&gt;
&lt;p&gt;The failed label was not malformed. It was simply larger than the database
column could hold after Base64 encoding.&lt;/p&gt;
&lt;p&gt;That distinction changes the investigation. Comparing deployed revisions
would find nothing because the revision was not the variable. Repeating a
familiar fixture would also find nothing because the fixture did not cross the
same storage boundary. The investigation had treated “same code” as “same
experiment” while leaving the input that decided the result uncontrolled.&lt;/p&gt;</description></item><item><title>Reproduce the Failure Before Repairing It</title><link>https://wimdy.works/thoughts/reproduce-the-failure-before-repairing-it/</link><pubDate>Fri, 01 Jan 2016 00:00:00 +0000</pubDate><guid>https://wimdy.works/thoughts/reproduce-the-failure-before-repairing-it/</guid><description>&lt;p&gt;Submit two different browser error reports. The application accepts both
requests, sends both reports, and records the same tracing identifier for each.&lt;/p&gt;
&lt;p&gt;Nothing crashed during submission. No assertion failed. The defect appears
later, when someone searches the logs and finds two executions presented as
one trace.&lt;/p&gt;
&lt;p&gt;The tempting response is to replace the identifier generator immediately. The
collision already points in that direction. But a plausible diagnosis is not a
reproduction. Before changing the generator, we need a check which states what
was observed, what should differ, and which surrounding machinery does not
matter.&lt;/p&gt;</description></item></channel></rss>