<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kuniwak</title>
    <description>The latest articles on DEV Community by Kuniwak (@kuniwak).</description>
    <link>https://dev.to/kuniwak</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3073117%2F9b48e011-e30f-44f6-844d-edbafc126b4b.png</url>
      <title>DEV Community: Kuniwak</title>
      <link>https://dev.to/kuniwak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kuniwak"/>
    <language>en</language>
    <item>
      <title>Debugging BLE macros on Nature Remo was such an ordeal that I built a Swift development environment</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Mon, 28 Apr 2025 02:40:36 +0000</pubDate>
      <link>https://dev.to/kuniwak/debugging-ble-macros-on-nature-remo-was-such-an-ordeal-that-i-built-a-swift-development-environment-g9e</link>
      <guid>https://dev.to/kuniwak/debugging-ble-macros-on-nature-remo-was-such-an-ordeal-that-i-built-a-swift-development-environment-g9e</guid>
      <description>&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;I have prepared an environment for developing a subset of &lt;a href="https://github.com/NordicSemiconductor/Android-nRF-Connect/blob/54ed2a491567c18c9de91556efb511b9b0bc3ec8/documentation/Macros/README.md" rel="noopener noreferrer"&gt;Android nRF Connect&lt;/a&gt; BLE macros on macOS, iOS, and other Apple platforms.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Kuniwak" rel="noopener noreferrer"&gt;
        Kuniwak
      &lt;/a&gt; / &lt;a href="https://github.com/Kuniwak/swift-ble-macro" rel="noopener noreferrer"&gt;
        swift-ble-macro
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      BLE (Bluetooth Low Energy) Macros for Swift
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;swift-ble-macro&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This is a simple Swift library that allows you to easily create nRF Connect BLE macros &lt;sup&gt;&lt;a href="https://github.com/Kuniwak/swift-ble-macro#user-content-fn-1-6db3a8ac06424d927a441840a40121a4" id="user-content-fnref-1-6db3a8ac06424d927a441840a40121a4" rel="noopener noreferrer"&gt;1&lt;/a&gt;&lt;/sup&gt; for your iOS/iPadOS/macOS/watchOS/visionOS/tvOS app
It is a wrapper around &lt;a href="https://developer.apple.com/documentation/corebluetooth" rel="nofollow noopener noreferrer"&gt;Core Bluetooth&lt;/a&gt; that allows you to
create macros for BLE devices. It is designed to easily develop Nature Remo compatible BLE macros&lt;sup&gt;&lt;a href="https://github.com/Kuniwak/swift-ble-macro#user-content-fn-2-6db3a8ac06424d927a441840a40121a4" id="user-content-fnref-2-6db3a8ac06424d927a441840a40121a4" rel="noopener noreferrer"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Add the following to your &lt;code&gt;Package.swift&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight highlight-source-swift notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-k"&gt;package&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;url&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;https://github.com/Kuniwak/swift-ble-macro.git&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; from&lt;span class="pl-kos"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-s"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;&lt;span class="pl-s"&gt;"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Products&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEMacroEasy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the easy interface to execute BLE macros.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEMacro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the BLE macro XML parser.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEMacroCompiler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the BLE macro compiler that compile BLE macro XML to BLE macro IR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLECommand&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the BLE macro IR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEInterpreter&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the BLE macro interpreter that interprets BLE macro IR.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEInternal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the utilities for BLE macros.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BLEModel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the convenient state machines for BLE.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Supported Macros&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Currently, only the following BLE macro XML elements…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Kuniwak/swift-ble-macro" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;BLEMacroEasy&lt;/span&gt;

&lt;span class="c1"&gt;// You can find your iPhone’s UUID by running the commands below in Terminal:&lt;/span&gt;
&lt;span class="c1"&gt;// $ git clone https://github.com/Kuniwak/swift-ble-macro&lt;/span&gt;
&lt;span class="c1"&gt;// $ cd swift-ble-macro&lt;/span&gt;
&lt;span class="c1"&gt;// $ swift run ble discover&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;myIPhoneUUID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;uuidString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"********-****-****-****-************"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;myMacro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;contentsOf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"https://ble-macro.kuniwak.com/iphone/battery-level.xml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;macroXMLString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myMacro&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;myIPhoneUUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// This handler is called every time a value is read from the peripheral.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;batteryLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;batteryLevel&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;%"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Besides the ability to &lt;strong&gt;run BLE macros&lt;/strong&gt; on macOS/iOS/…, the project also provides a CLI that helps you &lt;em&gt;develop&lt;/em&gt; those macros.&lt;br&gt;
For the macro-execution API, see the &lt;a href="https://github.com/Kuniwak/swift-ble-macro?tab=readme-ov-file" rel="noopener noreferrer"&gt;README&lt;/a&gt;.&lt;br&gt;
The CLI supports device scanning, macro validation, macro execution, and an interactive REPL:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Scan BLE devices&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ble discover
&lt;span class="go"&gt;00000000-0000-0000-0000-000000000000    Example Device 1    -78
11111111-1111-1111-1111-111111111111    Example Device 2    -47
22222222-2222-2222-2222-222222222222    Example Device 3    -54
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Abort scanning with Ctrl+C&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run a BLE macro&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ble run path/to/your/ble-macro.xml &lt;span class="nt"&gt;--uuid&lt;/span&gt; 00000000-0000-0000-0000-000000000000
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run a BLE macro interactively&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ble repl &lt;span class="nt"&gt;--uuid&lt;/span&gt; 00000000-0000-0000-0000-000000000000
&lt;span class="go"&gt;connecting...
connected

(ble) ?
write-command, w, wc            Write to a characteristic without a response
write-descriptor, wd            Write to a descriptor
write-request, req              Write to a characteristic with a response
read, r                         Read from a characteristic
discovery-service, ds           Discover services
discovery-characteristics, dc   Discover characteristics
discovery-descriptor, dd        Discover descriptors
q, quit                         Quit the REPL

(ble) dc
180A 2A29 read
180A 2A24 read
D0611E78-BBB4-4591-A5F8-487910AE4366 8667556C-9A37-4C91-84ED-54EE27D90049 write/write/notify/extendedProperties
9FA480E0-4967-4542-9390-D343DC5D04AE AF0BADB1-5B99-43CD-917A-A77BC549E3CC write/write/notify/extendedProperties
180F 2A19 read/notify
1805 2A2B read/notify
1805 2A0F readk

(ble) r 180F 2A19
58
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The BLE macros I have written are published at &lt;a href="https://github.com/Kuniwak/ble-macro" rel="noopener noreferrer"&gt;Kuniwak/ble-macro&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;Nature Remo recently gained a Bluetooth Low Energy (BLE) macro feature (&lt;a href="https://engineering.nature.global/entry/introduce-device-extension-macro" rel="noopener noreferrer"&gt;official announcement&lt;/a&gt;).&lt;br&gt;
If you own a compatible model (Nature Remo 3 or Nature Remo mini 2 family), you can control BLE devices directly from your Remo.&lt;br&gt;
I therefore started writing BLE macros so that our three full-colour Philips Hue bulbs could be driven from Remo (I have always had a faint admiration for scenes like &lt;a href="https://vimeo.com/124436622" rel="noopener noreferrer"&gt;this video&lt;/a&gt;, hence the extravagant colours).&lt;/p&gt;

&lt;p&gt;The macros supported by Remo are a subset of those used by &lt;a href="https://github.com/NordicSemiconductor/Android-nRF-Connect/blob/54ed2a491567c18c9de91556efb511b9b0bc3ec8/documentation/Macros/README.md" rel="noopener noreferrer"&gt;Android nRF Connect&lt;/a&gt;.&lt;br&gt;
Macros are written in XML. For example, to turn a Hue bulb on in daylight-white you can write:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;macro&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"hue-daylight-white"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"BRIGHTNESS_HIGH"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;assert-service&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Hue"&lt;/span&gt; &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"932c32bd-0000-47a2-835a-a8d455b859dd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;assert-characteristic&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Combined"&lt;/span&gt; &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"932c32bd-0007-47a2-835a-a8d455b859dd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"WRITE"&lt;/span&gt; &lt;span class="na"&gt;requirement=&lt;/span&gt;&lt;span class="s"&gt;"MANDATORY"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/assert-characteristic&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/assert-service&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;write&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Set to Daylight White"&lt;/span&gt;
           &lt;span class="na"&gt;characteristic-uuid=&lt;/span&gt;&lt;span class="s"&gt;"932c32bd-0007-47a2-835a-a8d455b859dd"&lt;/span&gt;
           &lt;span class="na"&gt;service-uuid=&lt;/span&gt;&lt;span class="s"&gt;"932c32bd-0000-47a2-835a-a8d455b859dd"&lt;/span&gt;
           &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0101010201fe0302fa0005020100"&lt;/span&gt;
           &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"WRITE_REQUEST"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/macro&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With BLE macros you instruct read/write operations against &lt;em&gt;services&lt;/em&gt; and &lt;em&gt;characteristics&lt;/em&gt;.&lt;br&gt;
For well-known devices you can often find the details thanks to &lt;a href="https://gist.github.com/shinyquagsire23/f7907fdf6b470200702e75a30135caf3" rel="noopener noreferrer"&gt;specifications reverse-engineered by third parties&lt;/a&gt;, but bringing a hand-written macro to a “working on Remo” state still requires a fair bit of trial and error. &lt;strong&gt;And that trial and error was utterly painful.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;According to the documentation, Android users can rely on the &lt;strong&gt;nRF Connect for Mobile&lt;/strong&gt; app, which supports everything from recording to running BLE macros, making development easy.&lt;br&gt;
Unfortunately I had no Android device at hand.&lt;br&gt;
There is an iOS version by the same developer, but it &lt;em&gt;does not&lt;/em&gt; include the macro feature.&lt;br&gt;
In the beginning I was stuck in a loop of feeding a macro to Remo, hitting a barely-described error, tweaking the XML by guesswork, and trying again…&lt;/p&gt;

&lt;p&gt;Because this cycle was so fruitless, I decided to build an environment with functionality similar to Android’s nRF Connect for Mobile.&lt;br&gt;
That is how &lt;a href="https://github.com/Kuniwak/swift-ble-macro" rel="noopener noreferrer"&gt;Kuniwak/swift-ble-macro&lt;/a&gt; was born.&lt;br&gt;
Although it offers only a subset of features compared with nRF Connect, it is more than enough to develop BLE macros that run on Remo. Feel free to give it a try!&lt;/p&gt;

&lt;p&gt;When developing macros it is also important to capture what traffic an existing app is exchanging with the BLE device.&lt;/p&gt;
&lt;h1&gt;
  
  
  BLE-macro Growing Pains
&lt;/h1&gt;

&lt;p&gt;During my adventures with Hue and Switchbot I ran into all sorts of issues; let me document them here for posterity.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hue returns an &lt;em&gt;insufficient encryption&lt;/em&gt; error
&lt;/h2&gt;

&lt;p&gt;Hue bulbs cannot be operated unless they are paired.&lt;br&gt;
Attempting to read/write from a non-paired central results in an &lt;em&gt;insufficient encryption&lt;/em&gt; error.&lt;br&gt;
Only the first device that performs pairing gets registered (there &lt;em&gt;is&lt;/em&gt; a workaround, explained below).&lt;br&gt;
For example, if you connect using the official Hue app first, any subsequent device (e.g. Remo or Google Nest mini) will be rejected.&lt;/p&gt;

&lt;p&gt;You can work around this by opening &lt;strong&gt;Hue app › Settings › Voice assistant › Google Home › Make discoverable&lt;/strong&gt;.&lt;br&gt;
This puts the bulb into a discoverable state for a very short time (about a minute).&lt;br&gt;
Programmatically, the same effect can be achieved by writing &lt;code&gt;0x01&lt;/code&gt; to characteristic &lt;code&gt;97fe6561-2004-4f62-86e9-b71ee2da3d22&lt;/code&gt; of service &lt;code&gt;0000fe0f-0000-1000-8000-00805f9b34fb&lt;/code&gt; (see the &lt;a href="https://github.com/Kuniwak/ble-macro/blob/master/public/hue/make-discoverable.xml" rel="noopener noreferrer"&gt;make-discoverable BLE macro&lt;/a&gt;).&lt;/p&gt;
&lt;h2&gt;
  
  
  No clear factory-reset procedure after a failed Hue pairing
&lt;/h2&gt;

&lt;p&gt;If your Hue bulb gets paired to, say, a Google Nest mini &lt;em&gt;before&lt;/em&gt; you manage to pair it with the official app or a Mac, the &lt;em&gt;insufficient encryption&lt;/em&gt; problem prevents any further operation, including factory reset, because that too is done over BLE.&lt;/p&gt;

&lt;p&gt;Some guides propose toggling the power repeatedly to reset the bulb — for example &lt;a href="https://www.reddit.com/r/Hue/comments/vznv5q/hue_bulb_hardware_reset_only_bulb_required/" rel="noopener noreferrer"&gt;this discussion&lt;/a&gt;.&lt;br&gt;
However, this did not work for my bulb (model LCA009, firmware v1.116.3).&lt;br&gt;
What &lt;em&gt;did&lt;/em&gt; work was cutting the power at the wall switch, then turning it back on; for a brief moment and at very close range the official Hue app could see the bulb again, from which I was able to perform a factory reset.&lt;/p&gt;
&lt;h2&gt;
  
  
  Switchbot does not respond
&lt;/h2&gt;

&lt;p&gt;Switchbot devices can also be controlled via BLE, but they are finicky.&lt;br&gt;
For firmware v6.6 you can operate them with the macro shared in the developer community on Discord, but v6.3 ignores the same commands.&lt;br&gt;
After some trial and error I discovered that, on v6.3, subscribing to notifications on characteristic &lt;code&gt;cba20003-224d-11e6-9fb8-0002a5d5c51b&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; writing &lt;code&gt;0x01&lt;/code&gt; to &lt;code&gt;cba20002-224d-11e6-9fb8-0002a5d5c51b&lt;/code&gt; makes the arm move.&lt;br&gt;
Here is a macro that works on v6.3:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;macro&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"switchbot-push"&lt;/span&gt; &lt;span class="na"&gt;icon=&lt;/span&gt;&lt;span class="s"&gt;"PLAY"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;assert-service&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Switchbot"&lt;/span&gt; &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20d00-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;assert-characteristic&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Push"&lt;/span&gt; &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20002-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"WRITE"&lt;/span&gt; &lt;span class="na"&gt;requirement=&lt;/span&gt;&lt;span class="s"&gt;"MANDATORY"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/assert-characteristic&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;assert-characteristic&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Configuration"&lt;/span&gt; &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20003-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;property&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"NOTIFY"&lt;/span&gt; &lt;span class="na"&gt;requirement=&lt;/span&gt;&lt;span class="s"&gt;"MANDATORY"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;assert-cccd&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/assert-characteristic&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/assert-service&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;write-descriptor&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Enable notifications"&lt;/span&gt;
                      &lt;span class="na"&gt;characteristic-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20003-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
                      &lt;span class="na"&gt;service-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20d00-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
                      &lt;span class="na"&gt;uuid=&lt;/span&gt;&lt;span class="s"&gt;"00002902-0000-1000-8000-00805f9b34fb"&lt;/span&gt;
                      &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"0100"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;write&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Write 0x570100"&lt;/span&gt;
           &lt;span class="na"&gt;characteristic-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20002-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
           &lt;span class="na"&gt;service-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20d00-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
           &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"570100"&lt;/span&gt;
           &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"WRITE_REQUEST"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;wait-for-notification&lt;/span&gt; &lt;span class="na"&gt;description=&lt;/span&gt;&lt;span class="s"&gt;"Wait for notification"&lt;/span&gt;
                           &lt;span class="na"&gt;characteristic-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20003-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
                           &lt;span class="na"&gt;service-uuid=&lt;/span&gt;&lt;span class="s"&gt;"cba20d00-224d-11e6-9fb8-0002a5d5c51b"&lt;/span&gt;
                           &lt;span class="na"&gt;timeout=&lt;/span&gt;&lt;span class="s"&gt;"5000"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/macro&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Products and References I Found Helpful
&lt;/h1&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jnross" rel="noopener noreferrer"&gt;
        jnross
      &lt;/a&gt; / &lt;a href="https://github.com/jnross/Bluetility" rel="noopener noreferrer"&gt;
        Bluetility
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Bluetooth Low Energy browser, an open-source alternative to LightBlue for OS X
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Bluetility&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Bluetility is a general-purpose Bluetooth Low-Energy utility for Mac OS X.  It scans for advertising peripherals, provides a interface to browse a connected peripheral's services and characteristics, and allows characteristic values to be read, written, and subscribed
&lt;a rel="noopener noreferrer" href="https://github.com/jnross/Bluetility/bluetility_screenshot.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fjnross%2FBluetility%2Fbluetility_screenshot.png" alt="Bluetility Screenshot" width="1085"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Installation&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Manual&lt;/h3&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;Download the latest release:  &lt;a href="https://github.com/jnross/Bluetility/releases/latest/download/Bluetility.app.zip" rel="noopener noreferrer"&gt;https://github.com/jnross/Bluetility/releases/latest/download/Bluetility.app.zip&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Extract the downloaded archive.&lt;/li&gt;
&lt;li&gt;Move Bluetility.app into your /Applications folder.  Or don't!&lt;/li&gt;
&lt;li&gt;Open Bluetility.app.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Using Homebrew&lt;/h3&gt;

&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;brew install --cask bluetility
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Features&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Scan for nearby advertising peripherals&lt;/li&gt;
&lt;li&gt;Sort peripherals by received signal strength&lt;/li&gt;
&lt;li&gt;View advertising data via tooltip on Devices list&lt;/li&gt;
&lt;li&gt;Browse services and characteristics of connected peripheral&lt;/li&gt;
&lt;li&gt;Subscribe to characteristic notifications&lt;/li&gt;
&lt;li&gt;Read/Write characteristic values&lt;/li&gt;
&lt;li&gt;View log of characteristic read/writes, logs may be saved as CSV&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Motivation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Bluetility is inspired by &lt;a href="https://itunes.apple.com/us/app/lightblue/id639944780?mt=12" rel="nofollow noopener noreferrer"&gt;LightBlue&lt;/a&gt;, a free bluetooth utility published by &lt;a href="https://punchthrough.com/" rel="nofollow noopener noreferrer"&gt;Punch Through Design&lt;/a&gt;.  Bluetility was created to resolve issues in this tool, and add missing features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support copy/paste via Cmd+C and Cmd+V&lt;/li&gt;
&lt;li&gt;Sort peripherals by received…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jnross/Bluetility" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/OpenWonderLabs" rel="noopener noreferrer"&gt;
        OpenWonderLabs
      &lt;/a&gt; / &lt;a href="https://github.com/OpenWonderLabs/SwitchBotAPI-BLE" rel="noopener noreferrer"&gt;
        SwitchBotAPI-BLE
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      SwitchBot BLE open API
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;SwitchBotAPI-BLE&lt;/h1&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/OpenWonderLabs/SwitchBotAPI-BLE#device-type" rel="noopener noreferrer"&gt;Device Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/OpenWonderLabs/SwitchBotAPI-BLE#uuid-update-notes" rel="noopener noreferrer"&gt;UUID Update Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/bot.md" rel="noopener noreferrer"&gt;Bot BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/colorbulb.md" rel="noopener noreferrer"&gt;Color Bulb BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/contactsensor.md" rel="noopener noreferrer"&gt;Contact Sensor BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/curtain.md" rel="noopener noreferrer"&gt;Curtain BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/curtain3.md" rel="noopener noreferrer"&gt;Curtain 3 BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/ledstriplight.md" rel="noopener noreferrer"&gt;LED Strip Light BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/meter.md" rel="noopener noreferrer"&gt;Meter BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/motionsensor.md" rel="noopener noreferrer"&gt;Motion Sensor BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/plugmini.md" rel="noopener noreferrer"&gt;Plug Mini BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/devicetypes/lock.md" rel="noopener noreferrer"&gt;Lock BLE open API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Device Types&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Device Type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bot&lt;/td&gt;
&lt;td&gt;H (0x48)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meter&lt;/td&gt;
&lt;td&gt;T (0x54)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Humidifier&lt;/td&gt;
&lt;td&gt;e (0x65)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curtain&lt;/td&gt;
&lt;td&gt;c (0x63)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Curtain 3&lt;/td&gt;
&lt;td&gt;{(0x7B)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion Sensor&lt;/td&gt;
&lt;td&gt;s (0x73)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contact Sensor&lt;/td&gt;
&lt;td&gt;d (0x64)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Color Bulb&lt;/td&gt;
&lt;td&gt;u (0x75)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LED Strip Light&lt;/td&gt;
&lt;td&gt;r (0x72)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smart Lock&lt;/td&gt;
&lt;td&gt;o (0x6F)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plug Mini&lt;/td&gt;
&lt;td&gt;g (0x67)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meter Plus&lt;/td&gt;
&lt;td&gt;i (0x69)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;The device type is in the service data of SCAN_RSP.&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service data&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Byte: 0&lt;/td&gt;
&lt;td&gt;Enc type&lt;/td&gt;
&lt;td&gt;Bit[7] NC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Byte: 0&lt;/td&gt;
&lt;td&gt;Dev Type&lt;/td&gt;
&lt;td&gt;Bit [6:0] – Device Type&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;UUID Update Notes&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;From Bot V6.4, Curtain V4.6, Meter V2.7,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Company ID&lt;/code&gt; ( &lt;code&gt;ADV_IND&lt;/code&gt; - &lt;code&gt;Manufacture Data&lt;/code&gt; ) modified from &lt;code&gt;0x0059&lt;/code&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/OpenWonderLabs/SwitchBotAPI-BLE" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>programming</category>
      <category>swift</category>
      <category>bluetooth</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 8) - One Step Beyond</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Mon, 28 Apr 2025 02:15:14 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-8-one-step-beyond-55k6</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-8-one-step-beyond-55k6</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Every form of software testing we have examined so far &lt;strong&gt;samples&lt;/strong&gt; the input space. Any inputs that fall outside the sample are never exercised, creating risk.&lt;/li&gt;
&lt;li&gt;In some projects this risk is unacceptable. In such cases, &lt;strong&gt;formal methods&lt;/strong&gt;—which allow exhaustive verification—become an attractive option.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Challenges of Traditional Software Testing
&lt;/h1&gt;

&lt;p&gt;To conclude this series, we will spend two installments looking at technologies that go &lt;strong&gt;one step beyond&lt;/strong&gt; traditional software testing.&lt;/p&gt;

&lt;p&gt;In Part 1 &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;“Fundamental Concepts of Software Testing”&lt;/a&gt; and Part 2 &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58"&gt;“Software Testing and Logical Expressions,”&lt;/a&gt; we explained that software testing of &lt;em&gt;functional&lt;/em&gt; systems means confirming that, for &lt;strong&gt;every&lt;/strong&gt; input satisfying the pre-conditions, the output&lt;br&gt;
satisfies the post-conditions. Exhaustively exercising every possible input is, however, prohibitively expensive.&lt;/p&gt;

&lt;p&gt;Consequently, as discussed in Part 5 &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-5-how-to-select-test-cases-4h8o"&gt;“How to Select Test Cases,”&lt;/a&gt; we finish testing after examining only a &lt;strong&gt;subset&lt;/strong&gt; of inputs, then &lt;em&gt;extrapolate&lt;/em&gt; the overall risk from that subset’s results. Inputs that were &lt;strong&gt;not&lt;/strong&gt; selected remain untested, and defects may&lt;br&gt;
well lurk there. Most of us have triggered a software failure at some point in daily life; almost invariably that failure was caused by an input never chosen as a test sample.&lt;/p&gt;

&lt;p&gt;Failures caused by out-of-sample inputs are not rare. In other words, traditional sampling-based testing deliberately trades lower cost for a non-trivial amount of risk—and we should stay aware of that fact.&lt;/p&gt;

&lt;p&gt;Sometimes a project simply &lt;strong&gt;cannot&lt;/strong&gt; take that risk—for example, aerospace, transportation, or medical systems demanding &lt;em&gt;high reliability&lt;/em&gt;. Even where the reliability bar is lower, Part 3 &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-3-the-significance-and-strategy-of-shift-left-testing-42ag"&gt;“The Significance and Strategy of Shift-Left Testing”&lt;/a&gt; showed that the earlier we detect defects, the cheaper they are to remove. From a cost&lt;br&gt;
perspective alone, a project may therefore prefer heavier early-stage testing.&lt;/p&gt;

&lt;p&gt;When you want &lt;strong&gt;exhaustive&lt;/strong&gt; verification rather than sampling, &lt;strong&gt;formal methods&lt;/strong&gt; are invaluable. Across this two-part finale we will start with a single sample in a traditional test, gradually increase the number of test cases, and eventually arrive at formal methods that cover &lt;strong&gt;all&lt;/strong&gt; inputs. Along the way we will observe&lt;br&gt;
what we gain—and what we lose.&lt;/p&gt;
&lt;h1&gt;
  
  
  What Happens as We Add Test Cases ?
&lt;/h1&gt;
&lt;h2&gt;
  
  
  When There Is Only &lt;em&gt;One&lt;/em&gt; Test Case
&lt;/h2&gt;

&lt;p&gt;Let’s begin with just &lt;strong&gt;one&lt;/strong&gt; test case, i.e. one input/output pair.&lt;br&gt;
In traditional &lt;strong&gt;Example-Based Testing (EBT)&lt;/strong&gt; we supply a concrete input and the expected output, then compare the actual output:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Suppose we have our own natural-number type—&lt;code&gt;ZERO&lt;/code&gt;, &lt;code&gt;ONE&lt;/code&gt;, &lt;code&gt;TWO&lt;/code&gt;, …—and an&lt;br&gt;
&lt;code&gt;add&lt;/code&gt; function. To verify that 1 + 1 = 2, an EBT looks like &lt;em&gt;Listing 1&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Listing 1. First test case for the natural-number addition&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Verify that our custom addition returns 2 for 1 + 1&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;when given 1 + 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Provide concrete inputs&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// Provide the expected output&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TWO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// Compare actual and expected; fail if they differ, succeed if they match&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, checking only 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;1+11 + 1&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 is hardly reassuring, so we add more cases.&lt;/p&gt;
&lt;h2&gt;
  
  
  When There Are &lt;em&gt;Two&lt;/em&gt; Test Cases
&lt;/h2&gt;

&lt;p&gt;To verify 1 + 0 = 1 as well, we might simply add another almost identical block, as in &lt;em&gt;Listing 2&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 2. Two separate test cases&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Original test case&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;when given 1 and 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TWO&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Additional input goes in its own test case&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;when given 1 and 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ZERO&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this approach needs roughly eight lines for every added case—verbose and error-prone. Instead, we can share the common scaffolding by supplying the input/expected pairs in &lt;strong&gt;table form&lt;/strong&gt;, as in &lt;em&gt;Listing 3&lt;/em&gt;. Such tests are called &lt;strong&gt;Parameterized Tests&lt;/strong&gt; or &lt;strong&gt;Table-Driven Tests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 3. Converted to a parameterized test&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Table of input and expected output&lt;/span&gt;
  &lt;span class="c1"&gt;// lhs = left-hand side, rhs = right-hand side&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TWO&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZERO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ONE&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; + &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`returns &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lhs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rhs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each extra test case costs just &lt;em&gt;one&lt;/em&gt; new line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Column: &lt;strong&gt;Eager Tests&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;You often see code that tries multiple inputs &lt;strong&gt;within a single test case&lt;/strong&gt;—the anti-pattern known as an &lt;em&gt;Eager Test&lt;/em&gt;. It has two problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If the &lt;em&gt;first&lt;/em&gt; assertion fails, later assertions never run, so diagnostic information is limited.&lt;/li&gt;
&lt;li&gt;Bundling multiple inputs under one case blurs the test’s intent, because the case name can no longer describe exactly what is being verified.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The recommended style is one input → one test case, as shown here.&lt;/p&gt;

&lt;h2&gt;
  
  
  If We Keep Increasing the Number of Test Cases
&lt;/h2&gt;

&lt;p&gt;How many cases are enough to &lt;em&gt;trust&lt;/em&gt; our addition? Certainly more than two. To identify good cases we would like to use the &lt;strong&gt;On–Off Point method&lt;/strong&gt;, introduced in Part 5 &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-5-how-to-select-test-cases-4h8o"&gt;“How to Select Test Cases.”&lt;/a&gt;&lt;br&gt;
However, that method assumes (1) the implementation performs a top-level branch with no loops/recursion afterwards—an assumption our &lt;code&gt;add&lt;/code&gt; &lt;strong&gt;does not&lt;/strong&gt; meet. In such situations &lt;strong&gt;Property-Based Testing (PBT)&lt;/strong&gt; is more suitable.&lt;/p&gt;

&lt;p&gt;Recap: PBT automatically generates &lt;strong&gt;large numbers&lt;/strong&gt; of inputs. Because the inputs are generated, we can’t specify &lt;em&gt;concrete&lt;/em&gt; expected outputs. Instead we supply a &lt;strong&gt;relationship&lt;/strong&gt; that &lt;em&gt;must hold&lt;/em&gt; between each input and its output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 4. Skeleton of a property-based test&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns ???&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;valuesGen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// generator picks some natural number&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;???&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// concrete expectation unavailable&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, we replace explicit outputs with &lt;em&gt;properties&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-4-fundamentals-and-strategies-of-unit-testing-237g"&gt;Part 4&lt;/a&gt; suggested using the &lt;strong&gt;post-condition&lt;/strong&gt; as that property. Table 1 restates the specification of &lt;code&gt;add&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 1. Specification of &lt;code&gt;add&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Left operand&lt;/th&gt;
&lt;th&gt;Right operand&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Unfortunately, expressing this relation without using “add two natural numbers” is difficult—yet that is exactly what we are &lt;em&gt;implementing&lt;/em&gt;. So we fall back to a simpler property: the &lt;strong&gt;commutative law&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 5. Verifying that &lt;code&gt;add(x, 0)&lt;/code&gt; equals &lt;code&gt;x&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;valuesGen&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// generator picks x and y&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// commutativity&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// In practice valuesGen runs many times with different inputs.&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This alone is &lt;strong&gt;not&lt;/strong&gt; sufficient—an implementation that mistakenly performs multiplication would still pass—so we add more relations (&lt;em&gt;Listing 6&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 6. Additional properties for addition&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and 0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns x&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and y + 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns (x + y) + 1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and y&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;returns the same as y + x (commutativity)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;given x and y, then adding z to the result&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;matches adding x to (y + z) (associativity)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* … */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// valuesGen runs many variations.&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automatic generation lets us exercise &lt;em&gt;far&lt;/em&gt; more inputs than hand-written cases ever could. What we &lt;strong&gt;lose&lt;/strong&gt; is concreteness: we must discover and encode &lt;em&gt;abstract&lt;/em&gt; input/output relationships—a skill that takes practice.&lt;/p&gt;

&lt;p&gt;Even here the space is not fully covered; the generated values, though numerous, are still a tiny subset of all natural numbers. One extension is to &lt;strong&gt;shrink the input domain&lt;/strong&gt; until exhaustive testing becomes feasible (e.g. by imposing a maximum value). But sometimes we cannot lower that maximum. That is where &lt;strong&gt;formal methods&lt;/strong&gt; shine.&lt;/p&gt;

&lt;h1&gt;
  
  
  What Are Formal Methods ?
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Formal methods&lt;/strong&gt; are mathematically based techniques for specifying, developing, and verifying software and hardware. The field is vast and rapidly evolving; we cannot cover everything here. Instead, we will demonstrate a &lt;em&gt;proof assistant&lt;/em&gt;, one category of formal-methods tools.&lt;/p&gt;

&lt;h1&gt;
  
  
  Exhaustive Testing with a Proof Assistant
&lt;/h1&gt;

&lt;p&gt;We will use the proof assistant &lt;strong&gt;Isabelle&lt;/strong&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; to exhaustively verify the wide input space.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;proof assistant&lt;/strong&gt; lets you define programs (like an ordinary language) &lt;em&gt;and&lt;/em&gt; prove mathematical properties about them. Crucially, logical quantifiers such as “for &lt;strong&gt;all&lt;/strong&gt; x” allow statements about &lt;strong&gt;every&lt;/strong&gt; possible input—transcending the limits of traditional and property-based tests.&lt;/p&gt;

&lt;p&gt;Consider the Pythagorean theorem: for the legs 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;aa&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;bb&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 and hypotenuse 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;cc&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 of a right triangle, 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;a2+b2=c2a^2 + b^2 = c^2&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;c&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 &lt;strong&gt;always&lt;/strong&gt; holds. Checking a handful of triangles does not prove the universal claim, just as testing can never prove the absence of all defects. Mathematical proof &lt;strong&gt;does&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Isabelle
&lt;/h2&gt;

&lt;p&gt;We will translate the earlier JavaScript &lt;code&gt;add&lt;/code&gt; into Isabelle&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 1. JavaScript version of &lt;code&gt;add&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inputs are Zero or Succ.&lt;/span&gt;
&lt;span class="c1"&gt;// Zero represents 0; Succ(n) is n + 1.&lt;/span&gt;
&lt;span class="c1"&gt;// new Succ(new Zero()) is 1,&lt;/span&gt;
&lt;span class="c1"&gt;// new Succ(new Succ(new Zero())) is 2, and so on.&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Zero&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Succ&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;nat&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Base case: if b is 0 then a + 0 = a&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Recursive case: a + b = Succ(a + (b − 1))&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Succ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Listing 2. Isabelle version of &lt;code&gt;add&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="k"&gt;theory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scratch&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;imports&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;begin&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;datatype&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;myNat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Succ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;myNat&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;primrec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"myNat ⇒ myNat ⇒ myNat"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add a Zero     = a"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add a (Succ b) = Succ (add a b)"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;(* ... more to come ... *)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Proving Program Behaviour
&lt;/h2&gt;

&lt;p&gt;First, we replicate the property from PBT: 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;x+0=xx + 0 = x&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 3. Proof that 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;x+0=xx + 0 = x&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;0&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_Zero_eq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add x Zero = x"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;(* cursor here *)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isabelle reports (excerpt):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theorem add_Zero_eq:
add ?x Zero = ?x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see a &lt;strong&gt;failed&lt;/strong&gt; proof, change &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; (&lt;em&gt;Listing 5&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 5. Attempting (and failing) to prove 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;x+1=xx + 1 = x&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_One_eq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add x (Succ Zero) = x"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;(* cursor here *)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;theorem add_One_eq:
add ?x (Succ Zero) = ?x
Failed to finish proof⌂:
goal (1 subgoal):
    1.  False
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Was the claim false, or merely too hard for automation? Isabelle’s &lt;code&gt;try&lt;/code&gt;&lt;br&gt;
command helps distinguish the two, by invoking Nitpick, Quickcheck, and&lt;br&gt;
Sledgehammer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 7. Using &lt;code&gt;try&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_One_eq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add x (Succ Zero) = x"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kt"&gt;try&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trying “solve_direct”, “quickcheck”, “try0”, “sledgehammer”, and “nitpick”…
Nitpick found a counterexample:
Free variable:
x = Succ (Succ Zero)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nitpick found &lt;code&gt;x = 2&lt;/code&gt;, confirming the statement is &lt;em&gt;false&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Similarly we can prove the remaining properties (&lt;em&gt;Listing 9&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 9. Proving the remaining properties&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_Succ_eq&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add (Succ x) y = Succ (add x y)"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add x y = add y x"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_Zero_eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_Succ_eq&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;simp&lt;/span&gt;&lt;span class="o"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"add (add x y) z = add x (add y z)"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;z;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And because Isabelle provides &lt;em&gt;mathematical&lt;/em&gt; naturals, we can easily verify the &lt;strong&gt;post-condition&lt;/strong&gt; as well (&lt;em&gt;Listing 10&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 10. Proving the post-condition&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;denoteMyNat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"myNat ⇒ nat"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"denoteMyNat Zero     = 0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"denoteMyNat (Succ x) = 1 + denoteMyNat x"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kn"&gt;lemma&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="kp"&gt;shows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"denoteMyNat (add x y) = denoteMyNat x + denoteMyNat y"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;proof&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;thus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;induct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Succ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="k"&gt;thus&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;simp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;add_Succ_eq&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;qed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full Isabelle tutorial—e.g. &lt;em&gt;Programming and Proving in Isabelle/HOL&lt;/em&gt;&lt;sup id="fnref3"&gt;3&lt;/sup&gt;—&lt;br&gt;
is beyond our scope, but highly recommended.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Using natural-number addition as an example, we observed this progression:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number of test cases&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A handful&lt;/td&gt;
&lt;td&gt;Traditional example-based test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dozens&lt;/td&gt;
&lt;td&gt;Parameterized test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hundreds&lt;/td&gt;
&lt;td&gt;Property-based test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;All&lt;/strong&gt; inputs&lt;/td&gt;
&lt;td&gt;Formal methods&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Moving from stage ① to ② is easy and loses nothing.&lt;br&gt;
From ② to ③ we lose concrete inputs/outputs; from ③ to ④ we additionally need&lt;br&gt;
mathematical-proof skills. The lower we go, the &lt;strong&gt;more abstract&lt;/strong&gt; our description&lt;br&gt;
must become to keep maintenance feasible.&lt;/p&gt;

&lt;p&gt;Mastering formal methods takes training. Humans rarely write perfect logical&lt;br&gt;
statements or proofs on the first try—just as we rarely write bug-free code.&lt;br&gt;
Hence we &lt;strong&gt;must&lt;/strong&gt; verify that our own formulas and proofs match our intent.&lt;br&gt;
Understanding the messages produced by tools like Isabelle therefore requires&lt;br&gt;
a solid grounding in logic, and process skills akin to Test-Driven Development&lt;br&gt;
help enormously.&lt;/p&gt;

&lt;p&gt;I hope this article sparks your interest in formal methods.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://isabelle.in.tum.de/index.html" rel="noopener noreferrer"&gt;https://isabelle.in.tum.de/index.html&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;A rigorous correspondence would require a formal semantics for JavaScript, which is beyond our scope here. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://isabelle.in.tum.de/dist/Isabelle2024/doc/prog-prove.pdf" rel="noopener noreferrer"&gt;https://isabelle.in.tum.de/dist/Isabelle2024/doc/prog-prove.pdf&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>formalmethods</category>
      <category>isabelle</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 7) - Fundamentals and Strategies for Integration &amp; E2E Testing</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Sun, 27 Apr 2025 02:27:57 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-7-fundamentals-and-strategies-for-integration-e2e-24ef</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-7-fundamentals-and-strategies-for-integration-e2e-24ef</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests&lt;/strong&gt; can uncover &lt;em&gt;integration bugs&lt;/em&gt; caused by specification mismatches between components that unit tests cannot detect.
Their downsides are long execution times, flaky results, and high maintenance costs.&lt;/li&gt;
&lt;li&gt;To keep maintenance costs low, minimize the number of integration-test cases and compose them from &lt;em&gt;high-value&lt;/em&gt; test scenarios.&lt;/li&gt;
&lt;li&gt;Combine them with “autopilot”–style tests that require no explicit scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Characteristics of Integration &amp;amp; E2E Testing
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Integration testing&lt;/strong&gt; targets a group of components combined together.&lt;br&gt;
Unlike unit tests—where all dependencies are replaced with test doubles—integration tests run the real components.&lt;br&gt;
Because some of those components may have long processing times, test runs tend to be slow.&lt;br&gt;
If the components communicate over a network, disconnections or latency can also make results unstable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E2E (end-to-end) testing&lt;/strong&gt; exercises the entire system with &lt;em&gt;all&lt;/em&gt; components integrated.&lt;br&gt;
Since more components are involved, execution takes even longer, and E2E tests inherit the same flakiness issues.&lt;/p&gt;

&lt;p&gt;Despite these drawbacks, integration and E2E tests remain indispensable because they can detect &lt;strong&gt;integration bugs&lt;/strong&gt; earlier than system-level manual testing.&lt;br&gt;
An &lt;em&gt;integration bug&lt;/em&gt; is a defect arising from a mismatch between the specifications of two or more components.&lt;/p&gt;

&lt;p&gt;Example: Component A outputs a &lt;em&gt;string&lt;/em&gt;, while Component B expects an &lt;em&gt;integer&lt;/em&gt;.&lt;br&gt;
Each component individually satisfies its own specification, so their unit tests pass.&lt;br&gt;
But when they are wired together in a dynamically-typed language, a run-time type error can occur.&lt;br&gt;
Other examples include misunderstandings of a dependency’s API or unintended states/transitions when concurrently running components interact.&lt;/p&gt;

&lt;p&gt;Integration—and especially E2E—targets almost invariably hold state.&lt;br&gt;
As you combine more components, the state space explodes, so any practical test can explore only a tiny fraction of all possible states.&lt;/p&gt;

&lt;p&gt;Here are two keys to successful integration/E2E testing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cover the tiny fraction you &lt;em&gt;can&lt;/em&gt; test with high-value scenarios.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use scenario-free tests to coarsely cover the vast remainder of the state space.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We start with choosing high-value scenarios.&lt;/p&gt;
&lt;h1&gt;
  
  
  Column: Formal Specification Descriptions
&lt;/h1&gt;

&lt;p&gt;Because integration bugs stem from spec mismatches, they &lt;em&gt;could&lt;/em&gt; be caught during specification writing—&lt;em&gt;if&lt;/em&gt; specifications are written in a machine-checkable form.&lt;br&gt;
Such techniques are known as &lt;strong&gt;formal specification descriptions&lt;/strong&gt;.&lt;br&gt;
A concise explanation is beyond this article’s scope.&lt;/p&gt;
&lt;h1&gt;
  
  
  Choosing High-Value Scenarios
&lt;/h1&gt;

&lt;p&gt;In this article, a &lt;strong&gt;test scenario&lt;/strong&gt; consists of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a sequence of &lt;strong&gt;events&lt;/strong&gt; fed into the integrated system, and&lt;/li&gt;
&lt;li&gt;an oracle that judges the final state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An &lt;strong&gt;event&lt;/strong&gt; is anything that triggers a change of internal state—method calls, network messages, UI actions, and so on.&lt;br&gt;
Events also represent interaction: one event can simultaneously change several components (e.g., a request alters both client and server state).&lt;/p&gt;

&lt;p&gt;In short, a scenario executes events in a prescribed order/timing and then inspects the resulting state.&lt;br&gt;
If an exception or crash occurs during the sequence, the test fails.&lt;/p&gt;

&lt;p&gt;Below is a sample E2E scenario for a login screen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Login screen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;When a valid user name and password are entered and the Login button is pressed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;navigates to the Welcome screen&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Event: open the login page&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Event: type “kuniwak” into the user name field&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;sendKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;kuniwak&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Event: type “p4$SW0rD” into the password field&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input.password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;sendKeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;p4$SW0rD&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Event: click the Login button&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button.login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Assert navigation to a page whose &amp;lt;title&amp;gt; is “Welcome”&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;until&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;titleIs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Welcome&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;value&lt;/strong&gt; of a scenario is how much its success reduces failure risk.&lt;br&gt;
Higher-risk-reduction ⇒ higher value.&lt;/p&gt;

&lt;p&gt;High-value scenarios fall into two patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;High-frequency&lt;/strong&gt;: executed often in production (typical &lt;em&gt;happy paths&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High severity on failure&lt;/strong&gt;: even if rare, a defect would be catastrophic.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;High-frequency&lt;/em&gt; examples&lt;br&gt;
The login path of a service that every user must pass through is high-value because of sheer usage volume.&lt;br&gt;
Analyzing user behavior—e.g., with Google Analytics—helps identify such paths.&lt;br&gt;
Because user behavior evolves, repeat this analysis periodically.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;High-severity&lt;/em&gt; examples&lt;br&gt;
Payment failure flows rarely occur, but if they do—charging without delivering goods or vice versa—the impact is huge.&lt;br&gt;
Thus payment scenarios are likewise high-value.&lt;/p&gt;

&lt;p&gt;To identify high-severity cases, interview people who best understand how the system creates value—&lt;strong&gt;domain experts&lt;/strong&gt;—and enumerate what could go wrong.&lt;br&gt;
For an e-commerce site, two expert-provided scenarios might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User flow&lt;/strong&gt;: discover the site → find an item → add to cart → fill in details → pay → receive purchase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service flow&lt;/strong&gt;: procure popular items → store them → promote → ship according to user payments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the site fails at any step of either flow and no recovery is possible, the store ceases to function.&lt;br&gt;
Both flows therefore deserve E2E protection.&lt;/p&gt;

&lt;p&gt;Conversely, auxiliary features around high-value flows—e.g., wish-list management or viewing order history—&lt;em&gt;might&lt;/em&gt; be left untested if their failure is tolerable or operationally recoverable.&lt;br&gt;
But if a wish-list differentiates you from competitors, or customer-service costs are huge, these become high-value too.&lt;br&gt;
The assessment is case-by-case; interview as many domain experts as possible for a broad view.&lt;/p&gt;

&lt;p&gt;If experts are unavailable, risk-analysis techniques such as &lt;strong&gt;STAMP/STPA&lt;/strong&gt; can help.&lt;/p&gt;

&lt;p&gt;Selecting such scenarios lets you cut failure risk with few tests.&lt;br&gt;
But scenario-based tests are costly to maintain, so limit them to high-value paths and complement the rest with scenario-free tests.&lt;/p&gt;

&lt;h1&gt;
  
  
  Combining Scenario-Free Tests
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Scenario-free tests&lt;/strong&gt; include &lt;strong&gt;model checking&lt;/strong&gt; and &lt;strong&gt;property-based testing (PBT)&lt;/strong&gt;—especially &lt;strong&gt;fuzzing&lt;/strong&gt;.&lt;br&gt;
We outlined them in &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-4-fundamentals-and-strategies-of-unit-testing-237g"&gt;Part 4&lt;/a&gt; and detailed PBT in &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-5-how-to-select-test-cases-4h8o"&gt;Part 5&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model checking&lt;/strong&gt; exhaustively explores a system’s state space to verify properties.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property-based testing&lt;/strong&gt; auto-generates inputs while humans supply properties relating inputs and outputs.
When the only property is “no exception/crash,” the practice is called &lt;em&gt;fuzzing&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their advantage: even without concrete scenarios, they can &lt;em&gt;roughly&lt;/em&gt; test vast areas that high-value scenarios never visit.&lt;br&gt;
Example: a crawler that follows every link on a site and asserts no 5xx status or client-side error.&lt;br&gt;
It checks that reachable pages at least don’t error out—though it can’t verify you landed on the &lt;em&gt;intended&lt;/em&gt; page, so it is “coarser” than scenario-based E2E tests.&lt;/p&gt;

&lt;p&gt;A common pain point is wasting time revisiting already-seen states.&lt;br&gt;
Consider a linear sequence of N screens with &lt;em&gt;Next&lt;/em&gt; and &lt;em&gt;Back&lt;/em&gt; buttons (only one button at each end).&lt;br&gt;
A random tester that presses available buttons uniformly needs an expected 

&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;(N−1)2(N-1)^2&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;N&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mclose"&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 presses to reach the far end.&lt;/p&gt;

&lt;p&gt;For 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;N=10N = 10&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;N&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;10&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
, that is 81 presses.&lt;br&gt;
If the tester &lt;em&gt;never&lt;/em&gt; revisits a state, the expectation drops to 
&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;N−1=9N − 1 = 9&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;N&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;1&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;9&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
 — an 81 vs 9 difference.&lt;/p&gt;

&lt;p&gt;Thus, a key to efficient scenario-free testing is &lt;em&gt;state deduplication&lt;/em&gt;: avoid revisiting.&lt;br&gt;
For the link crawler, treat each URL as a state ID; record visited URLs and skip repeats.&lt;br&gt;
If no natural ID exists, you must add a mechanism to recognize and remember states.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 6) - Fundamentals of Design for Testability</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Sun, 27 Apr 2025 02:24:54 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-6-fundamentals-of-design-for-testability-34h1</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-6-fundamentals-of-design-for-testability-34h1</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;There are several patterns for achieving a design that is easy to test.
This article introduces two representative ones: the SOLID principles and extracting functional components.&lt;/li&gt;
&lt;li&gt;To learn design for testability, Test-Driven Development (TDD) is highly recommended.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview of Testability
&lt;/h1&gt;

&lt;p&gt;We explained that the heart of shift-left testing is the &lt;strong&gt;unit test&lt;/strong&gt;.&lt;br&gt;
Following the guidance of the test pyramid, you end up writing more unit tests than E2E / integration tests.&lt;/p&gt;

&lt;p&gt;However, unit-test cost is heavily affected by the design of the component under test, much more so than with E2E or integration tests.&lt;br&gt;
In a bad example, you might finish the production code in a few hours but spend days writing its unit tests.&lt;/p&gt;

&lt;p&gt;Consider the following &lt;code&gt;ImageFetcher&lt;/code&gt;, which downloads an image.&lt;br&gt;
&lt;strong&gt;Listing 1&lt;/strong&gt; shows a version whose unit tests are expensive for two reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 1: An example component with high unit-test cost (TypeScript)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageFetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// BAD: Uses a global state&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ImageFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ImageFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// BAD: The output of fetch cannot be controlled from test code&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ImageFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first problem is the global &lt;code&gt;cache&lt;/code&gt; state.&lt;br&gt;
Because the result of &lt;code&gt;fetchImage&lt;/code&gt; changes depending on the cache contents, the order in which tests run influences the outcome, making parallel execution difficult.&lt;br&gt;
You would have to exclude the &lt;code&gt;ImageFetcher&lt;/code&gt; tests from your parallel suite.&lt;/p&gt;

&lt;p&gt;The second problem is that the &lt;code&gt;fetch&lt;/code&gt; response is an &lt;strong&gt;indirect input&lt;/strong&gt; you cannot control.&lt;br&gt;
You would need to spin up an HTTP server that returns an image and tell &lt;code&gt;fetch&lt;/code&gt; to hit that URL.&lt;/p&gt;

&lt;p&gt;With just a bit of tweaking, we can slash the unit-test cost (&lt;strong&gt;Listing 2&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 2: The same code with tweaks that lower test cost&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageFetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// GOOD: cache is now an instance field, not global&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// GOOD: fetch can be swapped out for a stub&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;fetchFunc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first tweak moves &lt;code&gt;cache&lt;/code&gt; from a global variable to an instance field.&lt;br&gt;
If each test creates its own &lt;code&gt;ImageFetcher&lt;/code&gt;, execution order no longer matters and you can run the tests in parallel.&lt;/p&gt;

&lt;p&gt;The second tweak lets us inject a replacement for &lt;code&gt;fetch&lt;/code&gt; through the constructor.&lt;br&gt;
In tests you can supply a hand-written stub that returns a deterministic response; no need to run an HTTP server.&lt;br&gt;
In production you simply pass the real &lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Changing the production design in this way can dramatically lower unit-test cost.&lt;br&gt;
In this series, we will use the term &lt;strong&gt;design for testability&lt;/strong&gt; for designs that keep unit tests cheap to write and maintain.&lt;/p&gt;
&lt;h2&gt;
  
  
  Design for Testability
&lt;/h2&gt;

&lt;p&gt;There are principles you should follow to achieve testable designs.&lt;br&gt;
Components that adhere to the SOLID principles, for example, are usually easier to test than those that do not.&lt;/p&gt;
&lt;h3&gt;
  
  
  SOLID Principles and Testability
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;SOLID principles&lt;/strong&gt; is an acronym for five design principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;S&lt;/strong&gt; – Single Responsibility Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O&lt;/strong&gt; – Open/Closed Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L&lt;/strong&gt; – Liskov Substitution Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I&lt;/strong&gt; – Interface Segregation Principle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;D&lt;/strong&gt; – Dependency Inversion Principle&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Single Responsibility Principle
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Single Responsibility Principle&lt;/strong&gt; states that a component should have exactly one reason to change.&lt;br&gt;
When it is violated, multiple separable concerns become tangled together.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ImageFetcher&lt;/code&gt; in &lt;strong&gt;Listing 2&lt;/strong&gt; breaks this rule: one might change it either to alter HTTP-status handling or to tweak cache behavior.&lt;br&gt;
By splitting it as in &lt;strong&gt;Listing 3&lt;/strong&gt;, you get two components: one interpreting HTTP status codes and one adding caching.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 3: Refactoring Listing 2 to satisfy the Single Responsibility Principle&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Class that interprets HTTP status codes&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ImageFetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;fetchFunc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Class that adds caching to ImageFetcher&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CachedImageFetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;imgFetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ImageFetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When concerns are coupled, you typically need more test cases.&lt;br&gt;
If a function with &lt;strong&gt;N&lt;/strong&gt; branches is combined with one with &lt;strong&gt;M&lt;/strong&gt; branches, covering all paths requires &lt;strong&gt;M × N&lt;/strong&gt; cases, whereas separated components need only &lt;strong&gt;M + N&lt;/strong&gt;.&lt;br&gt;
With &lt;strong&gt;M = N = 10&lt;/strong&gt;, that’s 100 versus 20 — a big difference.&lt;/p&gt;
&lt;h4&gt;
  
  
  Open/Closed Principle
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Open/Closed Principle&lt;/strong&gt; says components should be &lt;strong&gt;open for extension but closed for modification&lt;/strong&gt;.&lt;br&gt;
A component that violates it forces you to modify existing tests whenever you extend behavior.&lt;br&gt;
One that obeys it lets you keep the old tests unchanged and add new tests only for the new component, lowering maintenance cost.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ImageFetcher&lt;/code&gt; from &lt;strong&gt;Listing 3&lt;/strong&gt; can be extended in an open/closed way.&lt;br&gt;
Suppose you need cache entries to expire after a period.&lt;br&gt;
Instead of editing &lt;code&gt;CachedImageFetcher&lt;/code&gt;, add a new &lt;code&gt;ExpirationCachedImageFetcher&lt;/code&gt; (&lt;strong&gt;Listing 4&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 4: Extending Listing 3 while honoring the Open/Closed Principle&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExpirationCachedImageFetcher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;imgFetcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ImageFetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;expirationMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;imgFetcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expirationMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Liskov Substitution Principle
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt; requires that instances of a derived type can replace instances of the base type without altering desirable properties.&lt;br&gt;
When satisfied, you can reuse the base type’s unit tests for the derived type, reducing test-development cost.&lt;/p&gt;
&lt;h4&gt;
  
  
  Interface Segregation Principle
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Interface Segregation Principle&lt;/strong&gt; says clients should not be forced to depend on methods they do not use.&lt;br&gt;
Violating it makes you supply test stubs for methods irrelevant to the test, increasing boilerplate and effort.&lt;/p&gt;
&lt;h4&gt;
  
  
  Dependency Inversion Principle
&lt;/h4&gt;

&lt;p&gt;The &lt;strong&gt;Dependency Inversion Principle&lt;/strong&gt; states that high-level components should not depend on low-level components but both should depend on abstractions.&lt;br&gt;
When violated, components often have indirect inputs you cannot control and indirect outputs you cannot observe from tests, pushing you toward expensive, brittle integration tests.&lt;/p&gt;

&lt;p&gt;The earlier &lt;code&gt;ImageFetcher&lt;/code&gt; that called &lt;code&gt;fetch&lt;/code&gt; directly (tagged &lt;strong&gt;BAD&lt;/strong&gt;) violates this principle.&lt;br&gt;
The version that receives &lt;code&gt;fetch&lt;/code&gt; from the outside (tagged &lt;strong&gt;GOOD&lt;/strong&gt;) satisfies it.&lt;/p&gt;
&lt;h4&gt;
  
  
  Want to Dig Deeper?
&lt;/h4&gt;

&lt;p&gt;We have only skimmed the definitions.&lt;br&gt;
For a thorough treatment, consult &lt;em&gt;Agile Software Development: Principles, Patterns, and Practices&lt;/em&gt; &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Separating State and Testability
&lt;/h3&gt;

&lt;p&gt;Designing a system so that &lt;strong&gt;functional&lt;/strong&gt; components are separated from stateful ones also improves testability.&lt;br&gt;
A functional component, by definition, returns the same output for the same input.&lt;br&gt;
As noted in &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;Part 1&lt;/a&gt;, its behavior can be represented as an input/output table.&lt;br&gt;
Non-functional components are &lt;strong&gt;reactive&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unit-testing reactive components is generally more expensive because you must coerce them into specific internal states.&lt;br&gt;
By isolating stateless functional parts, at least those parts become cheap to test.&lt;/p&gt;

&lt;p&gt;Consider &lt;code&gt;FizzBuzzCounter&lt;/code&gt; (&lt;strong&gt;Listing 5&lt;/strong&gt;), which increments a counter and prints the FizzBuzz result:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 5: The FizzBuzzCounter class&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Reactive component&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FizzBuzzCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mutableCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Printer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mutableCount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mutableCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Buzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FizzBuzzCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Printer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints 1&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints 2&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints Fizz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unit-testing &lt;code&gt;FizzBuzzCounter&lt;/code&gt; is costly: you must call &lt;code&gt;increment&lt;/code&gt; at least 15 times to see &lt;code&gt;"FizzBuzz"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can refactor it into a purely reactive counter, a functional &lt;code&gt;fizzBuzz&lt;/code&gt; function, and a display component (&lt;strong&gt;Listing 6&lt;/strong&gt;).&lt;br&gt;
Only &lt;code&gt;fizzBuzz&lt;/code&gt; is functional, so it is trivial to test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listing 6: Decomposing into more testable components&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Reactive component that only counts&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;EventTarget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Notify listeners of the state change&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Component that prints FizzBuzz whenever the counter changes&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FizzBuzzDisplay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Printer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;printer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Functional FizzBuzz&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Buzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FizzBuzzDisplay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Printer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints 1&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints 2&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// prints Fizz&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  How to Acquire Design-for-Testability Skills
&lt;/h1&gt;

&lt;p&gt;While we have introduced patterns for testable design, applying them consciously in practice is surprisingly hard.&lt;br&gt;
You need deliberate training, and &lt;strong&gt;Test-Driven Development (TDD)&lt;/strong&gt; is an excellent exercise.&lt;/p&gt;

&lt;p&gt;TDD is a development process where you write the test &lt;strong&gt;before&lt;/strong&gt; the implementation — the so-called &lt;strong&gt;test-first&lt;/strong&gt; approach — and iterate through refactoring (&lt;strong&gt;Figure 1&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw88eef9lfvbqblh6l69e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw88eef9lfvbqblh6l69e.png" width="426" height="80"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 1: Process of TDD&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because tests come first in TDD, you cannot write code that is difficult to test; the process forces you to discover testable designs.&lt;br&gt;
Think of TDD as a brace that keeps you in a design-for-testability posture.&lt;/p&gt;

&lt;p&gt;Start with a small codebase.&lt;br&gt;
In a large one, finding a testable design is far more difficult and discouraging.&lt;br&gt;
After you gain confidence, tackle bigger legacy systems — &lt;em&gt;Working Effectively with Legacy Code&lt;/em&gt; &lt;sup id="fnref2"&gt;2&lt;/sup&gt; is invaluable here.&lt;/p&gt;

&lt;p&gt;You can tighten the brace further by avoiding mock libraries and DI containers.&lt;br&gt;
Hand-write test doubles as needed and pass dependencies explicitly.&lt;br&gt;
This motivates you to keep components small and simple.&lt;/p&gt;

&lt;p&gt;Eschewing DI containers also discourages tests from depending on knowledge of indirect dependencies &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;br&gt;
When tests rely on such knowledge, implementation details leak into the tests and you risk false positives where tests fail even though the implementation meets the spec.&lt;/p&gt;

&lt;p&gt;Treat TDD as training wheels.&lt;br&gt;
Once design-for-testability has become second nature, feel free to take them off and adjust your development speed to the situation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Several patterns help you design code that is easy to test.
We covered two major ones: the SOLID principles and extracting functional components.&lt;/li&gt;
&lt;li&gt;To learn design for testability, practice Test-Driven Development.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;em&gt;Agile Software Development, Principles, Patterns, and Practices&lt;/em&gt; — Robert C. Martin ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Michael C. Feathers ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Better known as the Law of Demeter. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>tdd</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 5) - How to Select Test Cases</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Sat, 26 Apr 2025 07:00:44 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-5-how-to-select-test-cases-4h8o</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-5-how-to-select-test-cases-4h8o</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;p&gt;There are several approaches to sampling a specification.&lt;br&gt;
In this article we introduce two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A boundary-value approach (On–Off Point method)&lt;/li&gt;
&lt;li&gt;A property-based testing approach&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Methods for Sampling a Specification
&lt;/h1&gt;

&lt;p&gt;In previous installments we explained that confirming “the implementation satisfies the specification” often requires an enormous—frequently infinite—number of test cases.&lt;br&gt;
In practice, rather than checking every possible case, we sample the specification, verify behaviour for only a very small subset, and infer behaviour for the rest.&lt;br&gt;
Ways to sample a specification are also called &lt;strong&gt;test design techniques&lt;/strong&gt;, and many techniques have been proposed.&lt;/p&gt;

&lt;p&gt;This article introduces the boundary-value approach and the property-based testing approach&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Boundary-Value Approach (On–Off Point Method)
&lt;/h2&gt;

&lt;p&gt;The boundary-value approach, &lt;strong&gt;Boundary-Value Analysis (BVA)&lt;/strong&gt;, selects inputs called &lt;strong&gt;boundary values&lt;/strong&gt; as samples of the specification.&lt;br&gt;
A boundary value is an input at which the branch executed by the hypothetical implementation changes when it contains conditional statements such as &lt;code&gt;if&lt;/code&gt; or &lt;code&gt;switch&lt;/code&gt;.&lt;br&gt;
Experience tells us that many defects lurk at boundary values. A typical example is confusing &lt;code&gt;x &amp;lt; y&lt;/code&gt; with &lt;code&gt;x ≤ y&lt;/code&gt; or vice versa.&lt;br&gt;
In such cases the outputs of &lt;code&gt;x &amp;lt; y&lt;/code&gt; and &lt;code&gt;x ≤ y&lt;/code&gt; differ only when &lt;code&gt;x = y&lt;/code&gt; (here “=” means equality, not assignment). The input satisfying &lt;code&gt;x = y&lt;/code&gt; is exactly the boundary value.&lt;/p&gt;

&lt;p&gt;Certain assumptions must hold for the boundary-value approach to work as intended.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1. &lt;strong&gt;The hypothetical implementation has a top-level conditional branch and is followed only by simple processing that contains no loops or recursion.&lt;/strong&gt;
For example, the function &lt;code&gt;isAdult&lt;/code&gt; in &lt;strong&gt;Code 1&lt;/strong&gt;, which takes a non-negative age and returns &lt;code&gt;true&lt;/code&gt; if the age is an adult and &lt;code&gt;false&lt;/code&gt; otherwise, is a typical case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Code 1. Hypothetical implementation of &lt;code&gt;isAdult&lt;/code&gt;, which determines adulthood&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAdult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// NOTE: Written verbosely for clarity instead of “return 20 &amp;lt;= age”&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Placing this assumption keeps the number of samples required for testing to a feasible size.&lt;br&gt;
Conversely, when the processing includes recursion or loops, infinitely many boundary values arise, making it impractical to analyse them all.&lt;/p&gt;

&lt;p&gt;What does it mean for “infinitely many boundary values to arise”? Let us examine a counter-example that violates the first assumption. &lt;strong&gt;Code 2&lt;/strong&gt; shows the hypothetical implementation of a function that returns the &lt;em&gt;i&lt;/em&gt;-th Fibonacci number for inputs &lt;em&gt;i&lt;/em&gt; that satisfy the pre-condition &lt;code&gt;i ≥ 0&lt;/code&gt;.&lt;br&gt;
Like &lt;strong&gt;Code 1&lt;/strong&gt;, it begins with conditional branches, but because it contains recursion afterward, it does not satisfy assumption 1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 2. Hypothetical implementation of a Fibonacci function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 0&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 1&lt;/span&gt;
  &lt;span class="c1"&gt;// branch for i ≥ 2; contains recursion&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is easy to see that &lt;code&gt;i = 0&lt;/code&gt; and &lt;code&gt;i = 1&lt;/code&gt; are boundary values, but there are actually boundary values hidden in the recursive part for &lt;code&gt;i ≥ 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Consider &lt;strong&gt;Code 3&lt;/strong&gt;, which expands the recursion in &lt;strong&gt;Code 2&lt;/strong&gt; by one level.&lt;br&gt;
You can see that &lt;code&gt;i = 2&lt;/code&gt; and &lt;code&gt;i = 3&lt;/code&gt; are also boundary values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 3. One-level expansion of the recursion in the Fibonacci function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 0&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 1&lt;/span&gt;

  &lt;span class="c1"&gt;// expansion of fibonacchi(i - 1)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// branch for i = 1&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 2&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// expansion of fibonacchi(i - 2)&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// branch for i = 2&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// branch for i = 3&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;fibonacchi&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you continue unfolding processing that contains recursion or loops in this way, you see boundary values proliferate without bound. Hence assumption 1 is introduced so that the sample size remains manageable.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2. &lt;strong&gt;Every conditional expression (the part that decides the branch of an &lt;code&gt;if&lt;/code&gt;) in the hypothetical implementation is a linear inequality, or a logical OR, AND, or NOT of linear inequalities.&lt;/strong&gt;
Examples that satisfy this condition are &lt;code&gt;x = 0&lt;/code&gt;, &lt;code&gt;0 &amp;lt; x&lt;/code&gt;, &lt;code&gt;0 ≤ x &amp;amp;&amp;amp; x &amp;lt; 100&lt;/code&gt;, and &lt;code&gt;x = 0 || y = 0&lt;/code&gt;.
An example that does &lt;em&gt;not&lt;/em&gt; satisfy it is &lt;code&gt;i % 3 === 0&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Boundary Defects
&lt;/h3&gt;

&lt;p&gt;As stated above, many defects lurk at boundary values. Such defects are called &lt;strong&gt;boundary bugs&lt;/strong&gt;.&lt;br&gt;
For example, writing &lt;code&gt;x ≥ 0&lt;/code&gt; where &lt;code&gt;x &amp;gt; 0&lt;/code&gt; is required introduces a boundary bug.&lt;/p&gt;

&lt;p&gt;When the conditional expression is a single linear inequality and only one variable is involved, four kinds of boundary bugs are possible&lt;sup id="fnref2"&gt;2&lt;/sup&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shifted boundary&lt;/strong&gt; – e.g. writing &lt;code&gt;x &amp;gt; 0&lt;/code&gt; instead of &lt;code&gt;x &amp;gt; 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closure defect&lt;/strong&gt; – e.g. writing &lt;code&gt;x ≤ 0&lt;/code&gt; instead of &lt;code&gt;x &amp;lt; 0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing boundary&lt;/strong&gt; – forgetting to branch where a boundary should exist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraneous boundary&lt;/strong&gt; – introducing a branch where none is needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To uncover all four types, we select, for &lt;strong&gt;every&lt;/strong&gt; boundary in &lt;strong&gt;every&lt;/strong&gt; conditional branch of the implementation,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;an input exactly on the boundary, and&lt;/li&gt;
&lt;li&gt;the nearest input on either side of that boundary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Put differently, we &lt;strong&gt;“pick the two closest values that lead to different processing”&lt;/strong&gt;.&lt;br&gt;
This is &lt;strong&gt;Boundary-Value Analysis (the On–Off Point method)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Applied to the earlier &lt;code&gt;isAdult&lt;/code&gt; function, boundary-value analysis means testing the cases where &lt;code&gt;age&lt;/code&gt; is &lt;code&gt;19&lt;/code&gt; and &lt;code&gt;20&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Property-Based Testing
&lt;/h2&gt;

&lt;p&gt;We explained that boundary-value testing requires assumptions about the component’s implementation.&lt;br&gt;
Components that violate those assumptions cannot be tested this way.&lt;br&gt;
For such components I recommend adopting &lt;strong&gt;Property-Based Testing (PBT)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We have mentioned PBT in prior installments, but here is a refresher. In traditional &lt;strong&gt;Example-Based Testing (EBT)&lt;/strong&gt;, humans choose the inputs.&lt;br&gt;
In contrast, PBT automatically generates inputs. The human supplies a &lt;strong&gt;relationship that must hold between input and output&lt;/strong&gt;, and the output is checked against it.&lt;br&gt;
This relationship is typically a post-condition&lt;sup id="fnref3"&gt;3&lt;/sup&gt;. For the FizzBuzz function, for example, we might give the following post-conditions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the input is divisible by both 3 and 5, the result is &lt;code&gt;"FizzBuzz"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the input is divisible by 3 but not 5, the result is &lt;code&gt;"Fizz"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the input is divisible by 5 but not 3, the result is &lt;code&gt;"Buzz"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the input is divisible by neither 3 nor 5, the result is the decimal representation of the input.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementing the first case as a rudimentary property-based test yields &lt;strong&gt;Code 4&lt;/strong&gt;&lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 4. A naive property-based test for the &lt;code&gt;fizzBuzz&lt;/code&gt; function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;when the input is divisible by both 3 and 5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// collect failing inputs (counter-examples) here&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterexamples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// mechanically generate multiples of 3 and 5&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// if the output is not "FizzBuzz", add x to counterexamples&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// expect counterexamples to be empty&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// ... (other cases)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop tries 100 inputs, and it is easy to increase or decrease that number, so PBT lets us test far more inputs than traditional example-based tests.&lt;/p&gt;

&lt;p&gt;However, the automatically generated inputs often fail to satisfy conditions we actually want—most commonly, the pre-conditions.&lt;br&gt;
There are two approaches to eliminate such unwanted inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Implication approach&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom generator approach&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Implication Approach
&lt;/h3&gt;

&lt;p&gt;As explained in &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58"&gt;Part 2&lt;/a&gt;, &lt;strong&gt;implication&lt;/strong&gt; is a logical operator that takes two formulas.&lt;br&gt;
It is written “left ⇒ right” (some people use a double arrow).&lt;br&gt;
Its truth table is shown in &lt;strong&gt;Table 1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 1. Truth table of implication&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value of left&lt;/th&gt;
&lt;th&gt;Value of right&lt;/th&gt;
&lt;th&gt;Value of left ⇒ right&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key point is that the implication is &lt;strong&gt;true&lt;/strong&gt; whenever the left side evaluates to &lt;strong&gt;false&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this approach we put on the left side a formula that returns &lt;em&gt;true&lt;/em&gt; for the desired inputs and &lt;em&gt;false&lt;/em&gt; otherwise, and we put the test on the right side (&lt;strong&gt;Code 5&lt;/strong&gt;).&lt;br&gt;
For instance, if we wish to treat inputs divisible by 5 as invalid, we can set the left formula to &lt;code&gt;x % 5 === 0&lt;/code&gt;. Whenever the pre-condition is false, the entire implication evaluates to true, so the PBT always “passes.”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 5. Implementation using implication&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;when divisible by 3 but not by 5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// collect failing inputs (counter-examples) here&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;counterexamples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// mechanically generate multiples of 3&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// if also divisible by 5, treat as pass (the implication’s left side is false)&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// if the output is not "Fizz", add x to counterexamples&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// expect counterexamples to be empty&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This method is easier than writing a generator and works without relying on any library.&lt;br&gt;
However, because outputs are &lt;strong&gt;not&lt;/strong&gt; checked for inputs that do not satisfy the desired conditions, the actual number of evaluated samples can be smaller than intended.&lt;br&gt;
A serious problem arises when &lt;strong&gt;all&lt;/strong&gt; mechanically generated inputs violate the pre-condition. This situation is called a &lt;strong&gt;vacuous truth&lt;/strong&gt; (often simply &lt;em&gt;vacuous&lt;/em&gt;).&lt;br&gt;
If the inputs are vacuous, nothing is really tested. Therefore, when using implication, we should confirm that the generated inputs are not vacuous.&lt;/p&gt;

&lt;p&gt;Some PBT libraries offer a &lt;em&gt;filter&lt;/em&gt; function that redraws until it finds a sample that makes the predicate true, and they treat vacuous results as test failures.&lt;br&gt;
If available, such filter facilities should be used to avoid vacuity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Custom Generator Approach
&lt;/h3&gt;

&lt;p&gt;You can also implement your own generator. In PBT libraries, a &lt;em&gt;generator&lt;/em&gt; is a component that automatically produces inputs.&lt;br&gt;
When nothing is specified, most libraries choose a default generator that produces arbitrary values of the given type.&lt;br&gt;
If the standard generator produces unwanted inputs, replace it with another generator that yields only the desired ones.&lt;/p&gt;

&lt;p&gt;For instance, &lt;code&gt;fast-check&lt;/code&gt;&lt;sup id="fnref5"&gt;5&lt;/sup&gt;, a property-based testing library for JavaScript, allows you to implement a generator by combining existing ones, as in &lt;strong&gt;Code 6&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 6. Implementation using a generator&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fast-check&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;when divisible by both 3 and 5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// generator that produces positive integers divisible by 3 and 5&lt;/span&gt;
    &lt;span class="c1"&gt;// fc.nat() generates naturals; multiply by 3 and 5 to ensure divisibility&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// fc.assert throws if it finds a counter-example&lt;/span&gt;
      &lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// n is a value generated by gen&lt;/span&gt;

        &lt;span class="c1"&gt;// return true for success, false for failure&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, a brief note on writing your own generator. A generator for &lt;strong&gt;directed acyclic graphs (DAGs)&lt;/strong&gt;, for example, cannot easily be expressed as a combination of existing generators.&lt;br&gt;
One way is to start with an empty graph and repeatedly add either&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;a node with no parents, or&lt;/li&gt;
&lt;li&gt;a new node whose parents are one or more existing nodes chosen at random.&lt;/li&gt;
&lt;/ol&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;We omitted Equivalence Class Partitioning (ECP). Many definitions of ECP lack theoretical grounding, and those that do have a proper basis are too intricate to explain here. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;These are the one-dimensional cases. In two dimensions, &lt;em&gt;boundary tilt&lt;/em&gt; is added to the list (Software Testing Techniques, 2nd Edition, Boris Beizer). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;When the post-condition cannot be stated succinctly, we sometimes give simpler relationships. For instance, the post-condition for addition over naturals is cumbersome to express explicitly; instead, we often supply multiple simpler laws such as commutativity (swapping the first and second operands leaves the result unchanged). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;The code examples below are intentionally simple and eschew libraries. In practice you would use a dedicated PBT library. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://fast-check.dev/" rel="noopener noreferrer"&gt;https://fast-check.dev/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 4) - Fundamentals and Strategies of Unit Testing</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Sat, 26 Apr 2025 01:50:35 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-4-fundamentals-and-strategies-of-unit-testing-237g</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-4-fundamentals-and-strategies-of-unit-testing-237g</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In this series, &lt;strong&gt;unit testing&lt;/strong&gt; refers to replacing a component’s dependencies with test doubles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test doubles enable the test to &lt;strong&gt;control indirect inputs&lt;/strong&gt; and &lt;strong&gt;observe indirect outputs&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For reactive systems, we introduce three approaches: pursuing &lt;strong&gt;0-switch coverage&lt;/strong&gt;, &lt;strong&gt;property-based testing&lt;/strong&gt;, and &lt;strong&gt;model checking&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Unit Testing—The Heart of Shift-Left Testing
&lt;/h1&gt;

&lt;p&gt;The &lt;strong&gt;test pyramid&lt;/strong&gt; &lt;sup id="fnref1"&gt;1&lt;/sup&gt; is a well-known guideline that prescribes how many test cases should exist at each stage of the testing process.&lt;br&gt;
The lower the level of coupling, the more test cases you write.&lt;br&gt;
In practice, that means more component (integration) tests than end-to-end tests, and even more unit tests than component tests.&lt;br&gt;
Visualize unit tests as the base of the pyramid and E2E tests as its tip.&lt;br&gt;
Following this guideline yields fast, &lt;strong&gt;stable&lt;/strong&gt; &lt;sup id="fnref2"&gt;2&lt;/sup&gt; feedback.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Exactly Is a Unit Test?
&lt;/h2&gt;

&lt;p&gt;The term “unit test” is notoriously ambiguous.&lt;br&gt;
Some recent testing books avoid the muddled “unit vs. integration” dichotomy altogether and classify tests by &lt;strong&gt;test size&lt;/strong&gt; instead.&lt;br&gt;
That detour aside, in this series a &lt;strong&gt;unit test&lt;/strong&gt; is defined as a test that targets a single component while replacing its other dependencies with test doubles &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;In a purely functional language, a component is a single function; in an OO language, it is a single class.&lt;br&gt;
The more components you combine, the larger the state space becomes and the harder it is for people to understand the resulting spec and implementation.&lt;br&gt;
Because a unit test lives at the boundary between a component’s spec and its implementation, once either side grows complex, diagnosing failures becomes painfully time-consuming.&lt;br&gt;
Keeping both spec and implementation &lt;strong&gt;small enough for humans to grasp&lt;/strong&gt; is therefore the driving motivation behind unit tests.&lt;/p&gt;

&lt;p&gt;Another major benefit is speed: less code executes, so the tests run faster.&lt;/p&gt;

&lt;p&gt;A weakness of unit tests is that they cannot detect &lt;strong&gt;integration bugs&lt;/strong&gt;—failures caused by inconsistencies between components’ specs.&lt;br&gt;
Suppose component A expects an integer while component B returns the integer’s string representation.&lt;br&gt;
Each component individually satisfies its own spec, yet wiring them together causes a type error.&lt;br&gt;
A unit test would never see this.&lt;/p&gt;

&lt;p&gt;We said unit tests replace dependencies with test doubles. Why?&lt;br&gt;
To answer that we need the notion of &lt;strong&gt;indirect input/output&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Unit Tests and Indirect I/O
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indirect input&lt;/strong&gt;: input obtained via a dependency rather than a parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indirect output&lt;/strong&gt;: output sent to a dependency rather than returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together they are &lt;strong&gt;indirect I/O&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We will look at examples, starting with a component that has &lt;em&gt;no&lt;/em&gt; indirect I/O.&lt;/p&gt;
&lt;h3&gt;
  
  
  A Straightforward Component with No Indirect I/O
&lt;/h3&gt;

&lt;p&gt;The familiar &lt;em&gt;FizzBuzz&lt;/em&gt; function takes its input as a parameter and returns its output—nothing else.&lt;br&gt;
Hence it has no indirect I/O.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Buzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing it is trivial; no test doubles are needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;when given a multiple of 3 and 5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Component with Indirect Input
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Indirect input&lt;/strong&gt; is data a component fetches from a dependency, not from its parameters—current time, random numbers, database queries, etc.&lt;br&gt;
In the &lt;code&gt;greet&lt;/code&gt; function below, &lt;code&gt;Clock#getHour&lt;/code&gt; supplies the indirect input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Clock&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getHour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good evening!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good morning!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good afternoon!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good evening!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the test cannot dictate the time, it cannot predict the expected output.&lt;br&gt;
Enter the &lt;strong&gt;test stub&lt;/strong&gt;: a fake that injects tester-specified indirect input.&lt;/p&gt;

&lt;p&gt;First, refactor so that a &lt;code&gt;Clock&lt;/code&gt; can be injected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Clock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good evening!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good morning!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good afternoon!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good evening!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the test can supply a stub clock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;greet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;in the morning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;returns Good morning!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StubClock&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// always returns 9&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stub&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Good morning!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Component with Indirect Output
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Indirect output&lt;/strong&gt; is data sent to a dependency—writing to a DB, file, etc.&lt;br&gt;
To observe it, we use a &lt;strong&gt;test spy&lt;/strong&gt; that records what was sent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;writeHelloWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Testing it with a spy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writeHelloWorld&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;writes Hello, World! to the file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SpyFile&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;writeHelloWorld&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SpyFile&lt;/code&gt; stores whatever is written, so the test can assert against &lt;code&gt;spy.content&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components with Both Indirect Input and Output
&lt;/h3&gt;

&lt;p&gt;Sometimes a component has &lt;em&gt;both&lt;/em&gt;.&lt;br&gt;
A single test double would then need to both &lt;em&gt;control&lt;/em&gt; indirect input and &lt;em&gt;observe&lt;/em&gt; indirect output, making it bulky.&lt;br&gt;
Designs such as &lt;strong&gt;CQS (Command-Query Separation)&lt;/strong&gt; help avoid this by ensuring a component has only one or the other.&lt;br&gt;
We will revisit CQS later in the series.&lt;/p&gt;
&lt;h3&gt;
  
  
  Beware Overusing Test Doubles
&lt;/h3&gt;

&lt;p&gt;Stubs and spies add lines and can obscure intent.&lt;br&gt;
If you can avoid them—e.g., accept parameters instead of using a stub, return a value instead of writing to a spy—do so.&lt;br&gt;
Whenever you feel compelled to use one, ask whether indirect I/O is truly necessary &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  Unit Testing Reactive Systems
&lt;/h1&gt;

&lt;p&gt;All previous examples were &lt;em&gt;functional&lt;/em&gt;: outputs depend solely on inputs.&lt;br&gt;
&lt;strong&gt;Reactive systems&lt;/strong&gt; instead change state in response to interactions.&lt;/p&gt;

&lt;p&gt;Consider a &lt;code&gt;Flag&lt;/code&gt; that flips its boolean state when &lt;code&gt;toggle&lt;/code&gt; is called (see code 8 and figure 1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Flag&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Figure 1: State-transition diagram of &lt;code&gt;Flag&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcix46lki1wbdxwlqrxli.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcix46lki1wbdxwlqrxli.png" width="800" height="1327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tests might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Flag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;before any toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isEnabled is false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Flag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;after one toggle&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isEnabled is true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Flag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;after two toggles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isEnabled is false&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Flag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That covers three scenarios: 0, 1, and 2 toggles.&lt;/p&gt;

&lt;p&gt;Reactive systems often have &lt;strong&gt;vast or infinite&lt;/strong&gt; test-case spaces, so various strategies exist to tame them.&lt;br&gt;
We outline three.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;0-switch coverage&lt;/strong&gt;: Test every &lt;em&gt;direct&lt;/em&gt; state transition.&lt;br&gt;
&lt;code&gt;Flag&lt;/code&gt; has three: initial → false, false → true, and true → false.&lt;br&gt;
Our tests above already achieve 0-switch coverage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Property-based testing&lt;/strong&gt;: Programmatically generate many sequences of calls and assert a property holds for all.&lt;br&gt;
A sketch:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A naive implementation without a library—use a real PBT library in practice.&lt;/span&gt;
&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Flag&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;isEnabled is false after an even number of toggles, true otherwise&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

        &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;            &lt;span class="c1"&gt;// try many inputs&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// random length&lt;/span&gt;

            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Flag&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isEnabled&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deepStrictEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counterexamples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model checking&lt;/strong&gt;: Exhaustively explore states to detect deadlocks or assertion violations &lt;sup id="fnref5"&gt;5&lt;/sup&gt;.
A &lt;strong&gt;deadlock&lt;/strong&gt; is a state with no outgoing transitions &lt;sup id="fnref6"&gt;6&lt;/sup&gt;; an &lt;strong&gt;assertion violation&lt;/strong&gt; is an exception thrown by an embedded &lt;code&gt;assert&lt;/code&gt;.
Model checking is powerful but abstract, making results harder to interpret than 0-switch tests.
(A full code sample would require an external model checker, so it is omitted.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In this series, a unit test replaces a component’s dependencies with &lt;strong&gt;test doubles&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test doubles let tests &lt;strong&gt;control indirect inputs&lt;/strong&gt; and &lt;strong&gt;observe indirect outputs&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For reactive systems, we discussed three approaches: &lt;strong&gt;0-switch coverage&lt;/strong&gt;, &lt;strong&gt;property-based testing&lt;/strong&gt;, and &lt;strong&gt;model checking&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Some prefer the &lt;strong&gt;test trophy&lt;/strong&gt;, which argues that if component tests can run as fast as unit tests, you should write &lt;em&gt;more&lt;/em&gt; component tests than unit tests. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;em&gt;Stable&lt;/em&gt; means repeated runs of the same test always yield the same result (pass or fail). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;This definition does not draw a sharp line between unit and integration tests. In practice, you rarely stub &lt;em&gt;every&lt;/em&gt; dependency—simple utility functions inside the component are usually left real. Conversely, integration tests may employ some doubles. The distinction is therefore gradual, based on how many dependencies are replaced. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;We will revisit this in a later article about &lt;strong&gt;design for testability&lt;/strong&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;More formally, model checking also includes verifying temporal-logic properties and refinement relations. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Strictly speaking, you must exclude &lt;em&gt;normal termination&lt;/em&gt; states; although they also lack outgoing transitions, they are not considered deadlocks. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 3) - The Significance and Strategy of Shift-Left Testing</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Fri, 25 Apr 2025 00:19:29 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-3-the-significance-and-strategy-of-shift-left-testing-42ag</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-3-the-significance-and-strategy-of-shift-left-testing-42ag</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift-left testing&lt;/strong&gt; is a strategy that changes the development process so that defects are discovered earlier.&lt;/li&gt;
&lt;li&gt;The earlier a defect is detected, the lower its typical cost to fix.&lt;/li&gt;
&lt;li&gt;Detecting defects earlier requires running whatever tests are feasible at each stage as frequently as possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ODC analysis&lt;/strong&gt; lets you inspect and improve your shift-left testing efforts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Why Shift-Left Testing Matters
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Shift-left testing&lt;/strong&gt; is a strategy that moves the activities for finding defects—including testing—into earlier phases of the development process. Doing so is said to reduce the cost of fixing those defects.&lt;br&gt;
It is called &lt;em&gt;shift-left&lt;/em&gt; because timelines are usually drawn left-to-right; moving testing earlier means moving it leftward.&lt;/p&gt;

&lt;p&gt;Shift-left testing assumes the empirical rule of thumb that the sooner a defect is found, the cheaper it is to fix. Numerous sources cite that rule, but the evidence is mixed: some publications support it &lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;sup id="fnref2"&gt;2&lt;/sup&gt;, while others report that the effect appears in some projects but not in others &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Even though the rule is not rock-solid, the author believes—based on experience and the four reasons below—that the trend is real.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The shorter the time between defect injection and detection, the fewer unrelated changes are made in the meantime. The search space is smaller, so the fix costs less.&lt;/li&gt;
&lt;li&gt;When a defect is found shortly after it is introduced, the code has usually not yet been handed to other developers or testers; the original author can fix it alone, avoiding communication overhead.&lt;/li&gt;
&lt;li&gt;If a faulty component is used by other components before it is repaired, those users must also be fixed later. Catching the defect before any downstream code is written avoids cascading rework.&lt;/li&gt;
&lt;li&gt;Early detection shortens the overall project timeline. Figures &lt;strong&gt;1&lt;/strong&gt; and &lt;strong&gt;2&lt;/strong&gt; show Gantt charts for processes that discover defects at different times.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya1yeozh6xkmj30op1b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fya1yeozh6xkmj30op1b2.png" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 1: Gantt chart of a process where developers do &lt;em&gt;not&lt;/em&gt; test and rely on dedicated testers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjy4a5mnua0dr6p2bsq3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjy4a5mnua0dr6p2bsq3.png" width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 2: Gantt chart of a process where developers test their own work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Figure 2, developers can fix defects while development proceeds in parallel, shortening the idle time that blocks others in Figure 1. Viewed globally, the &lt;em&gt;lead time&lt;/em&gt; from project start to release is shorter, and shorter lead times mean lower costs.&lt;/p&gt;

&lt;p&gt;Throughout this series we will proceed on the premise that finding defects earlier tends to reduce the cost of fixing them.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to Achieve Shift-Left Testing
&lt;/h1&gt;

&lt;p&gt;To shift left, move verification activities as far upstream as possible so that defects surface sooner. That, in turn, means running all feasible checks at every stage.&lt;/p&gt;

&lt;p&gt;Table 1 summarizes typical verification techniques and the kinds of defects they can reveal (some are not “tests” in the narrow sense).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 1: Examples of defects each stage can uncover&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verification technique&lt;/th&gt;
&lt;th&gt;Representative defects it can detect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Syntax highlighting&lt;/td&gt;
&lt;td&gt;Syntax errors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static analysis (simple &lt;strong&gt;lint&lt;/strong&gt; checks)&lt;/td&gt;
&lt;td&gt;Variable mix-ups, incorrect API usage, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static checking (type checking)&lt;/td&gt;
&lt;td&gt;Type mismatches, wrong argument lists, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit testing&lt;/td&gt;
&lt;td&gt;Violations between component spec and implementation, e.g., wrong branch conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration testing&lt;/td&gt;
&lt;td&gt;Spec mismatches between integrated components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2E / system testing&lt;/td&gt;
&lt;td&gt;Spec mismatches across the whole system&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tester-driven verification&lt;/td&gt;
&lt;td&gt;Implementer misinterpretations of the spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure monitoring&lt;/td&gt;
&lt;td&gt;Defects missed by tests, spec flaws in unusual environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer reports&lt;/td&gt;
&lt;td&gt;Defects missed by monitoring, usability problems, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Crucially, &lt;strong&gt;no single technique finds every defect &lt;em&gt;and&lt;/em&gt; does so immediately&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Earlier stages detect problems sooner but miss some kinds of issues; later stages catch more kinds of issues but only after a longer delay. You therefore need a blended strategy: catch what you can early, and rely on later stages for the rest. The difference becomes clearer in a simulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Simulate
&lt;/h2&gt;

&lt;p&gt;Figures 3–6 arrange common verification stages from earlier (top) to later (bottom). For a given defect, draw an arrow downward; the arrow stops at the stage where the defect is caught (Figure 3).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk314d5vty58wr9yw7w9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftk314d5vty58wr9yw7w9.png" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 3: How to read the simulation diagrams (1)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If we have two defects caught at different stages (Figure 4), the arrow that stops higher up represents a defect found earlier—and therefore cheaper to fix—than the arrow that stops lower down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7bpikua04wqpvmblqnq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7bpikua04wqpvmblqnq.png" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 4: How to read the simulation diagrams (2)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now compare two development styles. One uses a language without static type checking, edits code in a plain text editor, and developers do no self-testing (Figure 5). The other uses a statically typed language, an IDE-like environment, and developers test their own code (Figure 6).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nd5yj090c0hv647qgq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9nd5yj090c0hv647qgq6.png" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 5: Process with plain text editing and no developer-run checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F398ewz3d7ftrj964peaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F398ewz3d7ftrj964peaj.png" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figure 6: Process with an IDE-like environment and developer-run checks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The latter clearly finds defects earlier, hence at lower cost. That illustrates why running every feasible test as soon as it becomes feasible is worthwhile.&lt;/p&gt;

&lt;p&gt;Note that we have discussed only a subset of defect-detection activities. Defects can also be introduced during requirements analysis, specification, and design; shift-left should target those phases too. &lt;sup id="fnref4"&gt;4&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;For example, during specification you can build a working &lt;em&gt;throwaway&lt;/em&gt; model (a &lt;strong&gt;prototype&lt;/strong&gt;) and exercise it in limited scenarios to uncover spec defects early. Paper sketches, design-tool prototypes (Figma &lt;sup id="fnref5"&gt;5&lt;/sup&gt;, Sketch &lt;sup id="fnref6"&gt;6&lt;/sup&gt;, Zeplin &lt;sup id="fnref7"&gt;7&lt;/sup&gt;, etc.), even block toys plus a camera for 3-D game terrain—any medium is fine as long as it answers the questions you have.&lt;/p&gt;

&lt;p&gt;Writing the spec in a rigorously defined language (&lt;strong&gt;formal specification&lt;/strong&gt;) also exposes unintended ambiguity early, as explained in &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58"&gt;Part 2&lt;/a&gt;. Taken together, these early activities enable shift-left testing.&lt;/p&gt;

&lt;p&gt;But teams often leave shift-left opportunities undiscovered. How can you spot them? One answer is &lt;strong&gt;ODC analysis&lt;/strong&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  ODC Analysis
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;ODC analysis&lt;/strong&gt; (Orthogonal Defect Classification) classifies every detected defect along four independent attributes: &lt;em&gt;type&lt;/em&gt;, &lt;em&gt;trigger&lt;/em&gt;, &lt;em&gt;source&lt;/em&gt;, and &lt;em&gt;impact&lt;/em&gt; (Table 2).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 2: The four ODC attributes&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;td&gt;Root cause category—e.g., wrong or missing configuration, incorrect comparison, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trigger&lt;/td&gt;
&lt;td&gt;Process that revealed the defect—code review, unit test, E2E test, and so on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;History of the faulty component—new, reused, modified, third-party, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Impact&lt;/td&gt;
&lt;td&gt;Kind of failure effect—usability, performance, reliability, deployability, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Suppose an E2E test of a web app finds that a page fails to open when the user name is &lt;code&gt;null&lt;/code&gt;, due to missing SQL escaping. The &lt;em&gt;type&lt;/em&gt; is “missing functionality,” and the &lt;em&gt;trigger&lt;/em&gt; is “E2E test.” Recording that information for every defect enables multifaceted analysis.&lt;/p&gt;

&lt;p&gt;For shift-left purposes we focus on &lt;em&gt;type&lt;/em&gt; and &lt;em&gt;trigger&lt;/em&gt;. From &lt;em&gt;type&lt;/em&gt; we infer the stage where the defect &lt;strong&gt;should&lt;/strong&gt; have been caught; from &lt;em&gt;trigger&lt;/em&gt; we see where it &lt;strong&gt;was&lt;/strong&gt; caught. If actual detection lags behind expectation, we revise the process. After running with the revised process for a while, we perform ODC analysis again to verify that detection has indeed shifted left. Thus ODC provides both insight for improvement and a way to measure the effects of process changes.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Capers Jones, &lt;em&gt;Applied Software Measurement: Global Analysis of Productivity and Quality&lt;/em&gt;, McGraw-Hill, April 2008. ISBN 978-0-07-150244-3 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Karl Wiegers &amp;amp; Joy Beatty, &lt;em&gt;Software Requirements&lt;/em&gt;, 3rd ed., Microsoft Press, 2013. ISBN 978-0-7356-7966-5 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Tim Menzies, William Nichols, Forrest Shull &amp;amp; Lucas Layman, “Are delayed issues harder to resolve? Revisiting cost-to-fix of defects throughout the lifecycle,” &lt;em&gt;Empirical Software Engineering&lt;/em&gt; 22 (4), 2017, 1903–1935. &lt;a href="https://doi.org/10.1007/s10664-016-9469-x" rel="noopener noreferrer"&gt;https://doi.org/10.1007/s10664-016-9469-x&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;In Parts &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;1&lt;/a&gt; and &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58"&gt;2&lt;/a&gt; we defined a &lt;em&gt;defect&lt;/em&gt; narrowly as “an input that makes implementation and specification disagree.” Here we use a broader definition: “an input that violates correctness &lt;em&gt;or&lt;/em&gt; validity,” so that requirement and specification problems also count as defects. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://www.figma.com/" rel="noopener noreferrer"&gt;https://www.figma.com/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;&lt;a href="https://www.sketch.com/" rel="noopener noreferrer"&gt;https://www.sketch.com/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;&lt;a href="https://zeplin.io/" rel="noopener noreferrer"&gt;https://zeplin.io/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 2) - Software Testing and Logical Expressions</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Wed, 23 Apr 2025 23:41:03 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-2-software-testing-and-logical-expressions-b58</guid>
      <description>&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;The conditions we need to check when testing functional programs can be expressed as logical formulae.&lt;/li&gt;
&lt;li&gt;Make it a habit to think about specifications and software tests in terms of logical expressions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Recap of Functional Program Testing from Part 1
&lt;/h1&gt;

&lt;p&gt;In this second installment we re-explain &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;Part 1&lt;/a&gt; from a different angle, so let’s quickly review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A functional program’s specification can be described as a table mapping some inputs to outputs, and the implementation’s meaning as a table mapping &lt;em&gt;all&lt;/em&gt; inputs to outputs.&lt;/li&gt;
&lt;li&gt;If, for every input listed in the specification, the output matches the implementation’s table, then the implementation satisfies the specification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These explanations are in fact natural-language translations of concepts originally described as logical formulae in computer-science literature.&lt;br&gt;
The goal of Part 2 is to let you understand those original formulae.&lt;/p&gt;

&lt;p&gt;Trying to explain logical notation without motivation can feel unsatisfying, so let’s first see &lt;em&gt;why&lt;/em&gt; it is worth using.&lt;/p&gt;
&lt;h1&gt;
  
  
  Why Logical Expressions?
&lt;/h1&gt;

&lt;p&gt;Behind specifications, implementations, semantics and software tests lies an important concept: &lt;strong&gt;verification of correctness&lt;/strong&gt;.&lt;br&gt;
In the sources we introduced, the core of verification is always described with logical formulae.&lt;br&gt;
Why did the authors choose formulae instead of plain language?&lt;/p&gt;

&lt;p&gt;There are two reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Natural language is ambiguous; one description may admit multiple interpretations.&lt;/li&gt;
&lt;li&gt;Logical notation makes it much easier to check the validity of an argument.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Consider the ambiguous sentence “I saw the man with the telescope.” — who has the telescope?&lt;br&gt;
When explanations allow multiple readings, different people may derive different conclusions.&lt;/p&gt;

&lt;p&gt;To avoid such tragedies we should use a language whose meaning is as precise as possible, and logical notation is a well-known candidate.&lt;/p&gt;

&lt;p&gt;Of course formulae cannot &lt;em&gt;eliminate&lt;/em&gt; every misunderstanding: a reader may misread symbols, or fail to map them to real-world concepts. Nevertheless, they cause far fewer misinterpretations than plain language.&lt;/p&gt;

&lt;p&gt;The field that studies logical expressions is &lt;strong&gt;symbolic logic&lt;/strong&gt;. Its literature covers both the meaning of formulae and formal proofs built from them — which brings us to the second advantage.&lt;/p&gt;

&lt;p&gt;With logical proofs, if you can start from known formulae and reach your claim by following the prescribed rules, your claim is proven correct.&lt;/p&gt;

&lt;p&gt;In short, plain language is ambiguous and makes validity hard to check. For critical core ideas that we &lt;em&gt;do not&lt;/em&gt; want misread, authors use logical expressions.&lt;/p&gt;

&lt;p&gt;Both &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;Part 1&lt;/a&gt; and Part 2 of this series are examples of that approach. For each step we:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Formulate an explanation as a logical expression that reduces the gap to traditional software-testing concepts.&lt;/li&gt;
&lt;li&gt;Verify that this expression follows from the literature’s formulae by a valid proof.&lt;/li&gt;
&lt;li&gt;Translate the expression into natural language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 lets the author confirm the translation’s correctness precisely &lt;sup id="fnref1"&gt;1&lt;/sup&gt; — a task that would be much harder had step 1 not been written as formulae.&lt;/p&gt;

&lt;p&gt;Because natural-language premises can be interpreted in multiple ways, judging the soundness of an argument written in plain text is hard. If both premises and conclusion are formulae, you only need to check that the premises hold and the proof follows the rules to be confident in the conclusion.&lt;/p&gt;
&lt;h1&gt;
  
  
  Sidebar: Specifications as Logical Formulae
&lt;/h1&gt;

&lt;p&gt;Naturally, precise logical expressions can describe program specifications themselves.&lt;/p&gt;

&lt;p&gt;If implementer and verifier interpret a spec differently, they are effectively working on different programs. Successful testing then becomes unlikely, leading to re-work, extra costs and delays.&lt;/p&gt;

&lt;p&gt;Specification notation must fit the system’s domain. Different notations carry different overheads and mitigate re-work to different degrees.&lt;br&gt;
For domains where errors after deployment are extremely costly (aerospace, medical, rail, finance), formal logic or specialised spec languages are often worth the effort. For a small e-commerce site or a game, that cost may be overkill.&lt;/p&gt;
&lt;h1&gt;
  
  
  Meaning of Logical Expressions
&lt;/h1&gt;

&lt;p&gt;Now that we know &lt;em&gt;why&lt;/em&gt; we use formulae, let’s learn just enough notation to follow Part 2. A comprehensive treatment would fill a textbook, so we’ll cover only what we need.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is a Logical Expression?
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;logical expression&lt;/em&gt; (or &lt;em&gt;formula&lt;/em&gt;) is a string of symbols conforming to a fixed grammar.&lt;br&gt;
Examples: &lt;code&gt;P ∧ Q&lt;/code&gt;, &lt;code&gt;∃x. ∀y. (R x y)&lt;/code&gt; &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Below we introduce just the constructs needed to understand testing-related formulae. For deeper study, see an introductory logic text.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building Blocks
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Conjunction (logical AND)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;P ∧ Q&lt;/code&gt; (“&lt;em&gt;P and Q&lt;/em&gt;”) is true iff both P and Q are true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 1: Truth table for conjunction&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Left&lt;/th&gt;
&lt;th&gt;Right&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Programmers can think of &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; / &lt;code&gt;and&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Implication
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;P ⟶ Q&lt;/code&gt; (“&lt;em&gt;if P then Q&lt;/em&gt;”) is false only when P is true and Q is false; otherwise true &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 2: Truth table for implication&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Left&lt;/th&gt;
&lt;th&gt;Right&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A chain &lt;code&gt;P ⟶ Q ⟶ R&lt;/code&gt; parses as &lt;code&gt;P ⟶ (Q ⟶ R)&lt;/code&gt;; everything but the rightmost is premise, the last is conclusion.&lt;/p&gt;
&lt;h3&gt;
  
  
  Predicate
&lt;/h3&gt;

&lt;p&gt;A &lt;em&gt;predicate&lt;/em&gt; returns a truth value. &lt;code&gt;P x&lt;/code&gt; denotes P applied to x.&lt;br&gt;
E.g. &lt;code&gt;white x&lt;/code&gt; is true iff &lt;em&gt;x&lt;/em&gt; is white.&lt;/p&gt;
&lt;h3&gt;
  
  
  Existential quantifier &lt;code&gt;∃&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;∃x. (P x)&lt;/code&gt; (“there exists an x such that P x”) is true iff &lt;em&gt;some&lt;/em&gt; x makes &lt;code&gt;P x&lt;/code&gt; true &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;For instance, &lt;code&gt;∃x. white_crow x&lt;/code&gt; is true because white crows exist.&lt;/p&gt;
&lt;h3&gt;
  
  
  Universal quantifier &lt;code&gt;∀&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;∀x. (P x)&lt;/code&gt; (“for all x, P x”) is true iff &lt;code&gt;P x&lt;/code&gt; is true for &lt;em&gt;every&lt;/em&gt; x &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;∀x. (crow x ⟶ black x)&lt;/code&gt; is false because there are white crows.&lt;/p&gt;

&lt;p&gt;Combining &lt;code&gt;∀&lt;/code&gt; with implication narrows the domain:&lt;br&gt;
&lt;code&gt;∀x. ((crow x) ⟶ (black x))&lt;/code&gt; reads “for all x that are crows, x is black.”&lt;/p&gt;

&lt;p&gt;A chain &lt;code&gt;∀x. ((P x) ⟶ (Q x) ⟶ (R x))&lt;/code&gt; means: for every x, if all premises hold, then R x holds.&lt;/p&gt;

&lt;p&gt;That concludes our crash course. We are ready to express testing concepts as formulae!&lt;/p&gt;
&lt;h1&gt;
  
  
  Logical Formulae for Software-Testing Concepts
&lt;/h1&gt;

&lt;p&gt;An essential notion behind testing is &lt;strong&gt;total correctness&lt;/strong&gt;.&lt;br&gt;
Software testing can be viewed as sampling inputs to find cases where total correctness is false (defects). &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;Part 1&lt;/a&gt; explained specification/implementation in those terms.&lt;/p&gt;

&lt;p&gt;Here we use formulae to define total correctness (and its components). We assume functional programs (one deterministic output per input).&lt;/p&gt;
&lt;h2&gt;
  
  
  What Is Total Correctness?
&lt;/h2&gt;

&lt;p&gt;A program is &lt;em&gt;totally correct&lt;/em&gt; iff it both terminates and returns an output listed in the spec for every input that the spec declares valid.&lt;/p&gt;

&lt;p&gt;Thus total correctness = termination + partial correctness.&lt;br&gt;
&lt;em&gt;Termination&lt;/em&gt;: the program never loops forever on any valid input.&lt;br&gt;
&lt;em&gt;Partial correctness&lt;/em&gt;: assuming it does terminate, the output matches the spec.&lt;/p&gt;

&lt;p&gt;To state this precisely we introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-condition&lt;/strong&gt; &lt;code&gt;pre i&lt;/code&gt;: whether input &lt;em&gt;i&lt;/em&gt; appears in the spec’s table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post-condition&lt;/strong&gt; &lt;code&gt;post i o&lt;/code&gt;: whether the pair (i,o) appears in that table.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Denotation Function
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;denotation&lt;/em&gt; function &lt;code&gt;denote prog i&lt;/code&gt; returns the output of program &lt;code&gt;prog&lt;/code&gt; on input &lt;code&gt;i&lt;/code&gt; if it terminates; otherwise undefined. Thus &lt;code&gt;denote&lt;/code&gt; is a partial function.&lt;/p&gt;

&lt;p&gt;With this we can state total correctness precisely.&lt;/p&gt;
&lt;h2&gt;
  
  
  Formula for Total Correctness
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;TC pre post prog&lt;/code&gt; (“Total Correctness”) means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For every input satisfying &lt;code&gt;pre&lt;/code&gt;, executing &lt;code&gt;prog&lt;/code&gt; terminates and the result satisfies &lt;code&gt;post&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="n"&gt;TC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;≡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∧&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;T&lt;/code&gt; is termination, &lt;code&gt;PC&lt;/code&gt; partial correctness.&lt;/p&gt;

&lt;h3&gt;
  
  
  Termination Predicate &lt;code&gt;T&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;≡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∀i.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∃o.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;True iff for every valid input &lt;code&gt;i&lt;/code&gt; there exists some output &lt;code&gt;o&lt;/code&gt; (i.e. prog terminates).&lt;/p&gt;

&lt;h3&gt;
  
  
  Partial Correctness Predicate &lt;code&gt;PC&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="n"&gt;PC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;≡&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∀i.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∀o.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;True iff for every valid input &lt;code&gt;i&lt;/code&gt;, if &lt;code&gt;prog&lt;/code&gt; terminates with &lt;code&gt;o&lt;/code&gt;, then &lt;code&gt;post i o&lt;/code&gt; holds.&lt;br&gt;
Both predicates together yield total correctness.&lt;/p&gt;
&lt;h2&gt;
  
  
  Translating to Testing
&lt;/h2&gt;

&lt;p&gt;A defect is an input that makes total-correctness false, i.e. violates termination or partial correctness.&lt;br&gt;
Testing samples finite inputs &lt;code&gt;I₁,…,Iₙ&lt;/code&gt; approximating the universal quantifiers. For termination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight isabelle"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I₁&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∃o.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;denote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I₁&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∧&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I₂&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∃o.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;denote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;I₂&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∧&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;…&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∧&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Iₙ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;⟶&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;∃o.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;denote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;prog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Iₙ&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly for partial correctness with expected outputs &lt;code&gt;O₁,…,Oₙ&lt;/code&gt;.&lt;br&gt;
A &lt;em&gt;good&lt;/em&gt; test suite approximates the true quantified formula well with few cases.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;By reading these formulae we gain a sharper understanding of software testing, and we can construct convincing formal arguments about correctness.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Here, “confirm” means checking that replacing the literature’s definitions with those used in this series causes no problems; i.e. that the two versions of total correctness are equivalent for the same program and specification (pair of pre- and post-conditions). The book &lt;em&gt;Semantics with Applications&lt;/em&gt; defines correctness using operational semantics (fixing an evaluation order), whereas &lt;a href="https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi"&gt;Part 1&lt;/a&gt; did so with denotational semantics (viewing programs as mathematical functions). ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Notation follows Isabelle syntax: &lt;a href="https://isabelle.in.tum.de" rel="noopener noreferrer"&gt;https://isabelle.in.tum.de&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Some texts write &lt;code&gt;⇒&lt;/code&gt; instead of &lt;code&gt;⟶&lt;/code&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Parentheses are redundant; we keep them for readability. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>testing</category>
      <category>computerscience</category>
      <category>formalmethods</category>
    </item>
    <item>
      <title>Software Testing: Theory and Practice (Part 1) - Fundamental Concepts of Software Testing</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Wed, 23 Apr 2025 00:31:23 +0000</pubDate>
      <link>https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi</link>
      <guid>https://dev.to/kuniwak/software-testing-theory-and-practice-part-1-fundamental-concepts-of-software-testing-2khi</guid>
      <description>&lt;h1&gt;
  
  
  Purpose of This Series
&lt;/h1&gt;

&lt;p&gt;In this series, we will thoroughly explain basic concepts and best practices related to writing test code, test design, and test strategies in software testing. The intended audience ranges from those who have heard of software testing but don't know what it entails, to those struggling to understand the theoretical aspects of it.&lt;/p&gt;

&lt;p&gt;This series includes definitions and explanations of foundational concepts in software testing. Unfortunately, many books on software testing do not clearly define basic terms like "specification" and "behavior." Merely reading vague explanations can create a false sense of understanding, or lead to inconsistent interpretations. This series aims to provide a more solid understanding of software testing based on clear definitions from computer science.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key Takeaways
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Software testing is the process of verifying that the implementation meets the specification.&lt;/li&gt;
&lt;li&gt;A specification refers to the expected behavior of a program. If the program determines output based on input, this behavior can be represented as an input-output table.&lt;/li&gt;
&lt;li&gt;The purpose of software testing is to confirm that the risk of failure is within acceptable limits, and to prompt fixes if it is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview of Software Testing
&lt;/h1&gt;

&lt;p&gt;In this series, software testing is succinctly defined as "the process of verifying that the implementation meets the specification"&lt;sup id="fnref1"&gt;1&lt;/sup&gt;. Some literature may include checking whether the implementation meets requirements, but for the sake of clarity, this series adopts the narrower definition.&lt;/p&gt;

&lt;p&gt;For example, consider a specification that requires "displaying the name of the logged-in user." If a user registers their name as &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;constructor&lt;/code&gt;, and the name is not displayed or causes an error, then the implementation fails to satisfy the specification&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;In short, the purpose of software testing is to ensure that names like &lt;code&gt;null&lt;/code&gt; or &lt;code&gt;constructor&lt;/code&gt; do not cause errors, and to determine whether the implementation needs fixing if they do.&lt;/p&gt;

&lt;p&gt;To understand software testing more deeply, we must also define the terms "specification" and "implementation." Can you clearly explain what a specification or implementation is? If not, read on for definitions and theory.&lt;/p&gt;

&lt;h1&gt;
  
  
  Definitions and Theory Surrounding Software Testing
&lt;/h1&gt;

&lt;p&gt;Understanding the definitions of specification and implementation, and the relationship between them, is essential to understanding software testing. Yet, most testing books do not define these terms properly. In engineering disciplines like software testing, definitions are often provided by fundamental sciences like computer science. This section borrows definitions from computer science literature to explain key concepts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification
&lt;/h2&gt;

&lt;p&gt;In computer science, one common definition is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generally, the expected behavior of a program is called the program's specification.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A specification is defined as the "expected behavior" of a program. While this seems straightforward, digging deeper often reveals surprising misunderstandings.&lt;/p&gt;

&lt;h4&gt;
  
  
  Behavior
&lt;/h4&gt;

&lt;p&gt;A key point that varies in understanding is the definition of "behavior." This article focuses on functional programs, where the output is uniquely determined by the input&lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;For example, a Fizz Buzz program — outputting "Fizz" for multiples of 3, "Buzz" for 5, and "Fizz Buzz" for both — is functional. A compiler translating source code into machine code is also a functional program.&lt;/p&gt;

&lt;p&gt;In functional programs, behavior refers to the relationship between input and output, which can be represented in a table like &lt;strong&gt;Table 1&lt;/strong&gt;. Exceptions can be listed instead of return values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 1: Fizz Buzz Program Specification&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;"1"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;"2"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;"4"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;"Buzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;"7"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;"8"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;"Buzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;"11"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;"13"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;"14"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;"Fizz Buzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(and so on&lt;sup id="fnref4"&gt;4&lt;/sup&gt;)&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Inputs not listed in the table are considered to have &lt;strong&gt;implementation-dependent undefined behavior&lt;/strong&gt; — the implementation can handle them freely. This allows for more efficient or optimized implementations.&lt;/p&gt;

&lt;p&gt;However, implementations for undefined behavior have no intrinsic value. Good specifications allow some freedom but avoid sacrificing value&lt;sup id="fnref5"&gt;5&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Returning to the "display logged-in user name" example: a program that displays any logged-in user’s name is correct. If it fails even once or crashes, it does not meet the specification. If the user is not logged in, the spec allows undefined behavior (e.g., an error message).&lt;/p&gt;

&lt;h2&gt;
  
  
  Aside: Reactive Programs
&lt;/h2&gt;

&lt;p&gt;So far, we’ve covered functional programs, which take input, produce output, and stop. Some programs are reactive — continuously interacting with users or other programs. A web server is one example.&lt;/p&gt;

&lt;p&gt;Reactive programs require a different definition of behavior. One formalism for specifying such behavior is &lt;strong&gt;Communicating Sequential Processes (CSP)&lt;/strong&gt;, which can also express implementations and automatically verify conformance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;implementation&lt;/strong&gt; is "a program intended to satisfy a given specification." While not always precisely defined in literature, this is the general usage.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 1&lt;/strong&gt; will show an example Fizz Buzz implementation, which throws an error for inputs &amp;lt;1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code 1: Example Implementation of the Fizz Buzz Program&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fizz Buzz program implementation written in TypeScript&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fizzBuzz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unsupported input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FizzBuzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fizz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Buzz&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike specifications, implementations must define output for all inputs. The input-output relationship in implementations is called the &lt;strong&gt;meaning&lt;/strong&gt; of the program and can also be shown in a table (&lt;strong&gt;Table 2&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 2: Fizz Buzz Program Meaning&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;1&lt;/td&gt;
&lt;td&gt;Exception&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;"1"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;"2"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;"4"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;"Buzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The implementation satisfies the specification if the meaning (actual output) is included in the specification (expected output). If any output differs, the implementation fails to meet the spec.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defect
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;defect&lt;/strong&gt; (or bug) is a point where the implementation does not match the specification. Formally, it is an input that produces different output than specified — including exceptions or infinite loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;failure&lt;/strong&gt; occurs when a defective input is actually executed, and the resulting output differs from the spec. A defect doesn't always cause a failure unless the input is used. But every failure has an underlying defect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Risk
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Failure risk&lt;/strong&gt; combines severity and probability. Often, severity is rated with integers, and risk is calculated as severity × probability to enable comparison.&lt;/p&gt;

&lt;p&gt;With this groundwork, we can better understand the definition and purpose of software testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Software Testing
&lt;/h2&gt;

&lt;p&gt;As stated, software testing is verifying that an implementation meets the specification — or confirming the absence of defects.&lt;/p&gt;

&lt;p&gt;To verify there are no defects, all possible (often infinite) inputs must be tested. Even for Fizz Buzz, verifying every number ≥1 is required. Reactive programs can have infinite traces even with finite input spaces&lt;sup id="fnref6"&gt;6&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Since exhaustive testing is unrealistic&lt;sup id="fnref7"&gt;7&lt;/sup&gt;, software testing relies on &lt;strong&gt;sampling&lt;/strong&gt; — selecting testable inputs from the specification.&lt;/p&gt;

&lt;p&gt;Testing only samples can't confirm the absence of all defects. Thus, the goal is often adjusted: to verify that &lt;strong&gt;failure risk is acceptable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even partial testing reduces failure risk compared to no testing. As long as the benefits outweigh the risks, the program has value.&lt;/p&gt;

&lt;p&gt;So even if software testing doesn’t prove correctness, it remains highly valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test Case
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;test case&lt;/strong&gt; is a pair of input and expected output sampled from the specification&lt;sup id="fnref8"&gt;8&lt;/sup&gt;. For Fizz Buzz, test cases can be extracted from the table (see &lt;strong&gt;Table 3&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;There are two major approaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example-based testing&lt;/strong&gt;: selects concrete input-output pairs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property-based testing&lt;/strong&gt;: defines relations between inputs and outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Property-based testing allows more automatic input generation but requires discovering correct relationships — a more difficult task. Both will be discussed in later parts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table 3: Example Fizz Buzz Test Cases&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Expected Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;"1"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;"Fizz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;"Buzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;"FizzBuzz"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Software testing is the process of verifying that the implementation meets the specification.&lt;/li&gt;
&lt;li&gt;A specification refers to the expected behavior of a program. If the program determines output based on input, this behavior can be represented as an input-output table.&lt;/li&gt;
&lt;li&gt;The purpose of software testing is to confirm that the risk of failure is within acceptable limits, and to prompt fixes if it is not.&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This explanation limits software testing to verification — confirming that the implementation meets the specification. Broader definitions (e.g., from ISTQB or SQuaRE) include validation — confirming that the implementation meets the requirements. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; can cause issues in SQL queries without parameter escaping. &lt;code&gt;constructor&lt;/code&gt; is problematic in JavaScript when using objects as dictionaries without &lt;code&gt;hasOwnProperty&lt;/code&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;For clarity, we exclude non-deterministic programs like those relying on random numbers or the current time. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;Although inputs ≥16 are omitted, they should be specified. Specifications may also be written in natural language or logic. For example: “For input &lt;code&gt;n &amp;gt; 0&lt;/code&gt;, if divisible by both 3 and 5, return 'FizzBuzz'; by 3 only, 'Fizz'; by 5 only, 'Buzz'; otherwise, return &lt;code&gt;n&lt;/code&gt; as a string.” ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;If all inputs are undefined, even random outputs or errors satisfy the spec, yet such implementations are worthless. Specifications must balance between freedom and meaningfulness. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn6"&gt;
&lt;p&gt;Functional programs have finite but huge input spaces. Reactive programs may have infinite traces, even if inputs are finite. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn7"&gt;
&lt;p&gt;Logical proof tools like Isabelle or Rocq can prove absence of defects even for infinite input spaces. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn8"&gt;
&lt;p&gt;ISTQB defines test cases more broadly to support reactive programs. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>testing</category>
      <category>programming</category>
      <category>beginners</category>
      <category>computerscience</category>
    </item>
    <item>
      <title>How Implementing a Static Checker for Screen Specifications Led Us to Uncover Numerous Defects</title>
      <dc:creator>Kuniwak</dc:creator>
      <pubDate>Tue, 22 Apr 2025 00:00:19 +0000</pubDate>
      <link>https://dev.to/kuniwak/how-implementing-a-static-checker-for-screen-specifications-led-us-to-uncover-numerous-defects-38g9</link>
      <guid>https://dev.to/kuniwak/how-implementing-a-static-checker-for-screen-specifications-led-us-to-uncover-numerous-defects-38g9</guid>
      <description>&lt;p&gt;I’m &lt;a href="https://kuniwak.com/" rel="noopener noreferrer"&gt;Kuniwak&lt;/a&gt;, a Software Engineer in Test. &lt;br&gt;
In this article I present a case study on developing a &lt;strong&gt;static checker&lt;/strong&gt; for screen specifications (explained below).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Want to Share
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Specification documents that describe screen layouts and transitions can be made &lt;strong&gt;machine‑readable&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;Once a specification is machine‑readable, &lt;strong&gt;static checking&lt;/strong&gt; of the spec becomes possible. &lt;/li&gt;
&lt;li&gt;By running a static check we uncovered &lt;strong&gt;just under 40 defects across 15 % of the screens&lt;/strong&gt; in my scope of responsibility. &lt;/li&gt;
&lt;li&gt;Machine‑readable specifications pave the way for &lt;strong&gt;further automation and reuse&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Recap: What Is a Specification?
&lt;/h2&gt;

&lt;p&gt;There are several definitions of &lt;em&gt;specification&lt;/em&gt;. &lt;br&gt;
Here, we treat a specification as &lt;strong&gt;the criterion that defines the correct behaviour of an implementation&lt;/strong&gt;. &lt;br&gt;
When an implementation is judged correct, we say that the implementation &lt;em&gt;satisfies the specification&lt;/em&gt;. &lt;br&gt;
Regardless of who performs the judgment, the result should be identical.&lt;/p&gt;

&lt;p&gt;Just as implementations can be faulty, &lt;strong&gt;specifications themselves can contain defects&lt;/strong&gt;. &lt;br&gt;
If a specification erroneously flags behaviour that was intended to be correct—or the reverse—it is defective.&lt;/p&gt;

&lt;p&gt;A classic example is &lt;strong&gt;contradictory statements&lt;/strong&gt;. &lt;br&gt;
If one part of the spec instructs the login‑screen button text to read “Login” while another says “Sign In”, no implementation can satisfy both simultaneously. &lt;br&gt;
A specification that no implementation can satisfy is worthless; evidently some behaviour was supposed to be accepted, but the spec rejects it.&lt;/p&gt;

&lt;p&gt;Another kind of defect (outside this article’s scope) arises when the spec is internally consistent yet the resulting correct implementation fails to solve the real‑world problem (e.g. sales targets are still missed).&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens When a Specification Has Defects?
&lt;/h2&gt;

&lt;p&gt;In a development process the main consumers of a specification are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Implementers&lt;/strong&gt; – responsible for delivering an implementation that satisfies the spec. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifiers&lt;/strong&gt; – responsible for confirming that the implementation satisfies the spec.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the specification is defective, both suffer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Unnecessary communication cost&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;If implementers or verifiers notice something suspicious, they must raise questions before coding—communication that should have been unnecessary. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wasted implementation / verification cost&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;If no one notices, code is written exactly as specified; the defect surfaces only when spec authors touch the system. Even if caught before release, some or all of that work is wasted. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loss of trust&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;If the defect slips into production, end‑users discover it and confidence is lost in addition to wasted effort.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A specification is the yardstick for correct system behaviour. &lt;/li&gt;
&lt;li&gt;Specifications, like code, can contain defects. &lt;/li&gt;
&lt;li&gt;Defective specs cause extra cost and may damage trust.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Is a &lt;em&gt;Screen&lt;/em&gt; Specification?
&lt;/h2&gt;

&lt;p&gt;This article deals with specs about the &lt;strong&gt;appearance of GUI screens&lt;/strong&gt; and the &lt;strong&gt;transitions between them&lt;/strong&gt;. &lt;br&gt;
Together we call these the &lt;strong&gt;screen specification&lt;/strong&gt;, consisting of a &lt;strong&gt;screen‑display specification&lt;/strong&gt; and a &lt;strong&gt;screen‑transition specification&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;screen&lt;/strong&gt; is (roughly) a set of application states grouped by identical GUI appearance &lt;sup id="fnref1"&gt;1&lt;/sup&gt;. &lt;br&gt;
Take a typical login screen: the states formed by combinations of input‑form contents and server‑communication status collectively make up the &lt;em&gt;login screen&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt; For instance, if a service distinguishes general and privileged users, their login screens may look alike, yet the paths leading to them and subsequent destinations differ, so modelling them as separate screens is clearer.&lt;/p&gt;

&lt;p&gt;State transitions within a screen—or between screens—are triggered by &lt;strong&gt;events&lt;/strong&gt; such as UI actions (click, hover, scroll), server messages, or simply time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen‑Display Specification
&lt;/h3&gt;

&lt;p&gt;Most screens place UI elements such as input forms and buttons at fixed positions. &lt;br&gt;
A &lt;strong&gt;screen‑display specification&lt;/strong&gt; instructs, for each state, &lt;em&gt;where&lt;/em&gt; each UI element appears and &lt;em&gt;how&lt;/em&gt; it looks. &lt;br&gt;
Behaviour and state changes of individual UI elements are usually defined separately as a &lt;strong&gt;UI‑element specification&lt;/strong&gt;, so shared widgets need not be duplicated across screens.&lt;/p&gt;

&lt;p&gt;In this article we assume the UI‑element spec already exists and the screen‑display spec describes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Layout&lt;/strong&gt; – where each UI element sits, and &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behaviour caused by interaction between UI elements&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;Interaction between UI elements&lt;/em&gt; means that an event changes multiple elements in concert &lt;sup id="fnref2"&gt;2&lt;/sup&gt;. &lt;br&gt;
Example: an input form and a text label share a screen. &lt;br&gt;
On keyboard input, if the entry violates a rule, an error message appears in the label; otherwise the label is hidden—a textbook UI interaction.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example
&lt;/h4&gt;

&lt;p&gt;If element positions do not vary by state, a single screenshot suffices to show the layout. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51cdahgznxweiuzucxtk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F51cdahgznxweiuzucxtk.png" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Where layouts differ by state, representative screenshots for each layout suffice.&lt;/p&gt;

&lt;p&gt;UI‑element interaction must be described in addition to the layout picture; in this case we use natural language, like the error‑message example above.&lt;/p&gt;

&lt;p&gt;An implementation is judged to satisfy the screen‑display spec if, &lt;strong&gt;given the same event sequence, its appearance matches the spec&lt;/strong&gt; in every state &lt;sup id="fnref3"&gt;3&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt; If they are non‑deterministic, each yields a set of possible appearances; the implementation passes when its set is included in the spec’s set. &lt;br&gt;
 If internal events exist, unstable states should be excluded. For client–server screens you usually want to &lt;em&gt;expose&lt;/em&gt; communication events rather than hide them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen‑Transition Specification
&lt;/h3&gt;

&lt;p&gt;Most GUIs expect users to navigate through multiple screens. &lt;br&gt;
We model a screen’s internal states as &lt;strong&gt;nodes&lt;/strong&gt; and events as &lt;strong&gt;labelled directed edges&lt;/strong&gt;, forming a graph called the &lt;strong&gt;screen‑transition specification&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;For example, from state &lt;code&gt;S_0&lt;/code&gt; (empty user‑name/password fields) entering correct credentials &lt;code&gt;Foo&lt;/code&gt; + password moves to &lt;code&gt;S_Foo&lt;/code&gt;; we draw an edge labelled “input valid credentials for Foo” between those nodes &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtvizkusujdjhf9gou0c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frtvizkusujdjhf9gou0c.png" width="699" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Common notations include state‑transition diagrams and tables.&lt;/p&gt;

&lt;p&gt;Explaining how to judge conformance to this spec would be lengthy; our implementation relies on the theory of &lt;strong&gt;Communicating Sequential Processes (CSP)&lt;/strong&gt;. &lt;br&gt;
For details, see the concept of &lt;em&gt;refinement&lt;/em&gt; in CSP. &lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Screen Spec Machine‑Readable
&lt;/h2&gt;

&lt;p&gt;We started with existing Confluence pages written in &lt;a href="https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html" rel="noopener noreferrer"&gt;Confluence wiki markup&lt;/a&gt; that lacked enough detail to serve as screen specs. &lt;br&gt;
We rewrote each screen as a pair of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;UI‑element layout diagram&lt;/strong&gt; (left below), and &lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;UI‑element table&lt;/strong&gt; (right below).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fp79n9jysvsikfu9eha.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fp79n9jysvsikfu9eha.png" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Scr.001&lt;/code&gt; is the screen ID and &lt;code&gt;Login Screen&lt;/code&gt; is the screen name. &lt;br&gt;
Every screen is given an ID because other parts of the spec (e.g. the UI‑element table’s transition column) need to reference screens uniquely.&lt;/p&gt;

&lt;p&gt;The left image is part of the screen‑display spec. &lt;br&gt;
The right table records, for each UI element, its ID, type, display conditions, display content, and interactions. &lt;br&gt;
Display conditions/content belong to the screen‑display spec; interactions belong to the screen‑transition spec.&lt;/p&gt;

&lt;p&gt;Because the UI‑element table is plain wiki markup, our static checker can parse it to extract the transition graph. &lt;br&gt;
State‑transition diagrams are drawn with a PlantUML macro so they remain machine‑readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static Checking of Machine‑Readable Screen Specs
&lt;/h2&gt;

&lt;p&gt;A machine‑readable spec enables static analysis. &lt;br&gt;
We implemented a &lt;strong&gt;static checker (~6000 lines of Go)&lt;/strong&gt; with &lt;strong&gt;23 rules&lt;/strong&gt; based on about 20 spec‑inspection viewpoints gathered in advance. &lt;br&gt;
Here are a few examples (eight were automated):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Consistency between the transition diagram and UI‑element tables&lt;/strong&gt; 

&lt;ul&gt;
&lt;li&gt;Each edge in the PlantUML diagram must appear in some table’s interaction column, and vice‑versa. &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction description for interactive elements&lt;/strong&gt; (buttons, check‑boxes, etc.). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit ordering for list‑type UI elements&lt;/strong&gt; (look for the word “order” in the content column). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling instructions for dynamic images&lt;/strong&gt; (“zoom in/out” or “crop” must appear when image size differs from its frame). &lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; However, a fully machine‑readable notation is not always human‑friendly, so we limited ourselves to natural‑language explanations in this case.&lt;/p&gt;

&lt;p&gt;To avoid overlooking typos or omissions we added &lt;strong&gt;15 auxiliary checks&lt;/strong&gt;, e.g.:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No duplicate IDs. &lt;/li&gt;
&lt;li&gt;The string &lt;code&gt;TODO&lt;/code&gt; must not appear. &lt;/li&gt;
&lt;li&gt;Text and images must specify whether they are static or dynamic. &lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Implementation‑wise, each rule is a function that takes one of &lt;em&gt;layout&lt;/em&gt;, &lt;em&gt;element table&lt;/em&gt; or &lt;em&gt;transition diagram&lt;/em&gt; and returns a list of detected defects, keeping rules independent and easy to add/remove. &lt;br&gt;
Ignoring non‑applicable elements prevents imposing a single rigid format; richer notations (e.g. a flowchart for complex conditions) can coexist, and dedicated rules can be added later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results of Introducing Static Checking
&lt;/h2&gt;

&lt;p&gt;Using this checker, we caught &lt;strong&gt;40 defects across 15 % of the screens&lt;/strong&gt; &lt;em&gt;before&lt;/em&gt; the spec reached programmers. &lt;br&gt;
After the checker flagged issues, the number of follow‑up questions about the spec was lower than in comparable projects without the checker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges of Machine‑Readable Screen Specs
&lt;/h2&gt;

&lt;p&gt;Maintaining a machine‑readable spec turned out to be burdensome for non‑programmers, so the checker did not see continuous use. &lt;br&gt;
Handing spec maintenance to developers might solve this, but then their effort must be justified—perhaps by automatically generating part of the implementation from the spec, as discussed next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applications of Machine‑Readable Specs
&lt;/h2&gt;

&lt;p&gt;Making the screen spec and its surroundings machine‑readable allows parts of the &lt;strong&gt;specification process&lt;/strong&gt; to be automated. &lt;br&gt;
In our case, steps &lt;strong&gt;P3&lt;/strong&gt; and &lt;strong&gt;P4&lt;/strong&gt; of the &lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l5hstivo0yrinljckuxv.png" rel="noopener noreferrer"&gt;process flow diagram&lt;/a&gt; were automated. &lt;br&gt;
For &lt;strong&gt;P3&lt;/strong&gt;, we linked layout diagrams to Figma objects and used the Figma API to refresh screenshots mechanically &lt;sup id="fnref5"&gt;5&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;With a machine‑readable spec we could also auto‑generate parts of the implementation or of the test suite. &lt;br&gt;
Complete generation may be hard, but partial generation is realistic. &lt;br&gt;
For example, typical E2E tests exercise screen transitions; by adding element IDs to the transition spec, E2E tests could be generated automatically.&lt;/p&gt;

&lt;p&gt;One could also imagine an &lt;em&gt;MCP server&lt;/em&gt; that indexes and interprets machine‑readable specs for LLMs, aiding code generation, test generation, or other tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Specification documents describing screen layouts and transitions can be made machine‑readable. &lt;/li&gt;
&lt;li&gt;Machine‑readable specs enable static checking. &lt;/li&gt;
&lt;li&gt;Static checking uncovered nearly 40 defects across 15 % of the screens in my area. &lt;/li&gt;
&lt;li&gt;Machine‑readable specs open the door to further automation and reuse.&lt;/li&gt;
&lt;/ol&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Even visually similar pages can be treated as separate screens.  ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;This parallels &lt;em&gt;parallel composition&lt;/em&gt; in CSP. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;When both spec and implementation are deterministic and contain no internal events.  ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;For clarity, this diagram uses raw states and events. In practice it is cleaner to include state variables, guards and post‑conditions. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;Confluence’s Figma‑embed widget was too slow, so we avoided it. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
