<?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>Forem: Philipp Scheit</title>
    <description>The latest articles on Forem by Philipp Scheit (@pscheit).</description>
    <link>https://forem.com/pscheit</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%2F1231188%2F0e0fbb91-5234-4251-b9cc-fa4ccfbd4323.jpeg</url>
      <title>Forem: Philipp Scheit</title>
      <link>https://forem.com/pscheit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pscheit"/>
    <language>en</language>
    <item>
      <title>The Lazy Lurk: A Mental Model for Better Tests</title>
      <dc:creator>Philipp Scheit</dc:creator>
      <pubDate>Mon, 01 Dec 2025 19:58:10 +0000</pubDate>
      <link>https://forem.com/pscheit/the-lazy-lurk-a-mental-model-for-better-tests-n32</link>
      <guid>https://forem.com/pscheit/the-lazy-lurk-a-mental-model-for-better-tests-n32</guid>
      <description>&lt;p&gt;You know how to write tests. Red, green, refactor. Assert this, mock that. But here's a question that might make you uncomfortable:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Could your tests pass with a completely broken implementation?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let me show you what I mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The roundtrip trap
&lt;/h2&gt;

&lt;p&gt;I recently saw this test for an encryption service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_encrypt_decrypt_roundtrip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;encrypter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'important data'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'important data'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;encrypter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$encrypted&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;Looks reasonable, right? Encrypt something, decrypt it, verify you get the original back. Ship it.&lt;/p&gt;

&lt;p&gt;But here's the thing: &lt;strong&gt;I can make this test pass in 10 seconds.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Encrypter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$data&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;Test is green. ✅&lt;/p&gt;

&lt;p&gt;No encryption happened. Your "important data" is stored in plain text. But the test passes, so everything's fine... right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the Lazy Lurk
&lt;/h2&gt;

&lt;p&gt;Here's a mental model I use when writing tests:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Imagine the person implementing the code is a lazy lurk. They will do &lt;strong&gt;anything&lt;/strong&gt; to make the test pass - even if it makes no sense.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not malicious. Just lazy. They want green tests with minimum effort. Your job as the test writer is to make cheating impossible.&lt;/p&gt;

&lt;p&gt;Every time you write a test, ask yourself: &lt;strong&gt;"What's the dumbest implementation that would pass this?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "do nothing" or "return a hardcoded value" - your test is incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classic examples of tests the Lazy Lurk loves
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The roundtrip test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$serializer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;deserialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$serializer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="c1"&gt;// Lazy implementation: return $input for both methods&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The single-case test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$calculator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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;// Lazy implementation: return 4;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The boolean trap:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test@example.com'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Lazy implementation: return true;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The "it works" test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertNotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Lazy implementation: return new Result();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to defeat the Lazy Lurk
&lt;/h2&gt;

&lt;p&gt;The fix is always the same: &lt;strong&gt;add another constraint that the lazy implementation can't satisfy.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  For the encryption roundtrip:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_encrypt_actually_encrypts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$plaintext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'important data'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;encrypter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plaintext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The Lazy Lurk can't cheat past this&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertNotSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$encrypted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// And we still verify the roundtrip&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;encrypter&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$encrypted&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 no-op implementation fails. The Lazy Lurk actually has to encrypt something.&lt;/p&gt;

&lt;h3&gt;
  
  
  For the calculator:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_add&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$calculator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$calculator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// Can't hardcode both!&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertSame&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="nv"&gt;$calculator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;add&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="mi"&gt;0&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;
  
  
  For the validator:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;test_email_validation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'test@example.com'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$validator&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'not-an-email'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// Forces real logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;See what's happening? We're adding &lt;strong&gt;triangulation&lt;/strong&gt; - multiple data points that force a real implementation.&lt;/p&gt;

&lt;p&gt;One test case = hardcodeable.&lt;br&gt;
Two contradicting cases = requires actual logic.&lt;/p&gt;

&lt;p&gt;It's like GPS. One satellite tells you nothing useful. Two gives you a line. Three gives you a position.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Lurk always wins (a little)
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;strong&gt;the Lazy Lurk will always get away with something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No matter how many test cases you add, there's always a gap. Tests can only prove the absence of &lt;em&gt;known&lt;/em&gt; issues, not the absence of &lt;em&gt;all&lt;/em&gt; bugs. That's not a failure of your tests - that's the nature of testing.&lt;/p&gt;

&lt;p&gt;So it's up to you to stop the circle.&lt;/p&gt;

&lt;h2&gt;
  
  
  TDD is not one loop
&lt;/h2&gt;

&lt;p&gt;Here's something I see developers get wrong all the time (and honestly, I catch myself doing it too): they think TDD is one cycle per feature.&lt;/p&gt;

&lt;p&gt;Write test. Red. Write code. Green. Done.&lt;/p&gt;

&lt;p&gt;But that's not how it works. &lt;strong&gt;One feature is many cycles.&lt;/strong&gt; You write a test, it fails, you make it pass. Then you think: "Wait, what if the input is empty?" New test. Red. Green. "What about special characters?" Red. Green. "What if it's called twice?" Red. Green.&lt;/p&gt;

&lt;p&gt;Each cycle, you're blocking another cheat. Each cycle, the Lazy Lurk has fewer places to hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know when to stop
&lt;/h2&gt;

&lt;p&gt;But here's the trap: you can circle forever.&lt;/p&gt;

&lt;p&gt;What about null? What about unicode? What about concurrent access? What about leap years on a Tuesday during a full moon?&lt;/p&gt;

&lt;p&gt;At some point, you need to stop. Too detailed tests become a burden:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They're brittle and break on every refactor&lt;/li&gt;
&lt;li&gt;They test implementation details instead of behavior&lt;/li&gt;
&lt;li&gt;They slow down your build&lt;/li&gt;
&lt;li&gt;They make the next developer afraid to touch anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Lazy Lurk &lt;em&gt;will&lt;/em&gt; get away with something. That's okay. Your job isn't to write perfect tests - it's to write &lt;em&gt;useful&lt;/em&gt; tests that catch the important cheats.&lt;/p&gt;

&lt;p&gt;Ship it. If the Lazy Lurk's shortcut causes a bug in production, you've just discovered your next test case.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>tdd</category>
      <category>php</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Gelesen: Google Mitarbeiter Nr. 59</title>
      <dc:creator>Philipp Scheit</dc:creator>
      <pubDate>Mon, 01 Dec 2025 07:31:53 +0000</pubDate>
      <link>https://forem.com/pscheit/gelesen-google-mitarbeiter-nr-59-19ip</link>
      <guid>https://forem.com/pscheit/gelesen-google-mitarbeiter-nr-59-19ip</guid>
      <description>&lt;p&gt;Gelesen: &lt;a href="http://www.amazon.de/dp/3868813314" rel="noopener noreferrer"&gt;Google-Mitarbeiter Nr. 59: Der erste Insider-Bericht aus dem Weltkonzern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nein: ich habe es nicht selbst gekauft. Und ja: ich bin trotzdem froh, dass ich es geschenkt bekommen habe.&lt;/p&gt;

&lt;p&gt;Doug beschreibt als Google Mitarbeiter 59 fast jedes Detail seiner Arbeit bei Google. Seine Funktion als die "Stimme von Google", muss er erst finden, vielmehr erfinden. Er tut sich schwer damit, mit den Visionen der Gründer Larry und Sergey und dem Entwicklungstempo Unternehmen mitzuhalten, welches als einzige Regel "Technik hat Vorfahrt" und "Tu nichts Unmoralisches" zu kennen scheint.&lt;/p&gt;

&lt;p&gt;Das Buch ist eigentlich eine Autobiographie eines Mitarbeiters in einem Technologie Startup-Unternehmen. Es liest sich jedoch weder wie eine Biographie, noch wie ein Insiderbericht, sondern eher wie ein Roman. Obwohl man das ein oder andere der Unternehmensgeschichte von Google schon weiß, ertappt man sich trotzdem dabei, wie man mitfiebert und hofft, dass die Maschinen in den drei Rechenzentren den 1 Milliarde-Seiten-Index tatsächlich stemmen können, den Google Yahoo verspricht.&lt;/p&gt;

&lt;p&gt;Auch das Ende des Buches liest sich eher wie ein Happy-End einer unglaublichen Geschichte und Reise in einer Welt, die man sich nur schwerlich vorstellen kann: Masseurinnen, tausende von Küchen mit Essen umsonst, einem eigensinnigen Chefkoch inklusive gesamter Kochmannschaft, einem blue-room, um langwierige Entscheidungen in einem Computer-Game auszufighten, Segways auf den Fluren, einem Hockeyfeld auf dem Parkplatz und vieles mehr.&lt;/p&gt;

&lt;p&gt;Wenn man die Danksagung liest, hat man fast das Gefühl, die genannten Namen als Freunde gewonnen zu haben, die alle als Akteure in diesem Märchen des Silicon Valleys agieren; Viel lachen, weinen und aneinander geraten.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meine Gedanken dazu
&lt;/h2&gt;

&lt;p&gt;Ich habe meine Kollegen bei Stammtischen stundenlang angehalten: "Das kann doch nicht sein! Dass SO größere Firmen arbeiten! Das ist einfach ineffektiv! Stell Dir ein Land vor, in dem es nur Unternehmen gibt, in denen die ganzen Zeitverschwender und Faulpelze rausgeschmissen werden und man sich auf das konzentriert, was man am Besten kann."&lt;/p&gt;

&lt;p&gt;"Naja, P.", sagen sie, "Du bist in einer kleinen Firma. Bei einem Betrieb mit mehreren hundert Leuten ist das völlig normal."&lt;/p&gt;

&lt;p&gt;Ich empfehle euch nur: kauft euch dieses Buch und ihr werdet eines Besseren belehrt. Ich weiß jetzt zumindest: die meisten Milchmädchen-Rechnungen, die ich schon seit Ewigkeiten aufstelle, scheinen in allen Google-Produkten und ganz Google als Unternehmen, in Formeln, Daten und Überzeugungen Platz gefunden zu haben.&lt;/p&gt;

&lt;p&gt;Dies bekräftigt mich, weiterhin bei Stammtischen über Meetings, E-Mail-Fluten und Büros außerhalb der Stadt zu fluchen und die Diskussion über Ineffizienz erneut zu entfachen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beispiele:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Wenn ich mir einen zweiten Monitor kaufe, der meine Produktivität jeden Tag nur um 5 Minuten steigert, spare ich im Jahr ca 21 Stunden. Selbst bei einem Stundenlohn von 8 EUR, hat sich der Monitor nach einem Jahr rentiert.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wenn ich einen Koch einstelle, sodass 3 meiner Mitarbeiter nicht jeden Tag insgesamt 2 Stunden von der Arbeit abgehalten werden, um das Essen vorzubereiten. Dann haben 1. meine Mitarbeiter was gesundes zu Essen, was sie produktiver macht, und 2. kann ich die gewonnenen 65 Arbeitstage meiner nicht-kochenden Mitarbeiter für etwas anderes sinnvoller nutzen.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on ps-webforge.com on February 25th, 2013&lt;/em&gt;&lt;/p&gt;

</description>
      <category>books</category>
      <category>productivity</category>
      <category>google</category>
      <category>german</category>
    </item>
    <item>
      <title>Trick for testing exceptions in PHPUnit</title>
      <dc:creator>Philipp Scheit</dc:creator>
      <pubDate>Mon, 01 Dec 2025 07:14:39 +0000</pubDate>
      <link>https://forem.com/pscheit/trick-for-testing-exceptions-in-phpunit-28d3</link>
      <guid>https://forem.com/pscheit/trick-for-testing-exceptions-in-phpunit-28d3</guid>
      <description>&lt;p&gt;When you're using PHPUnit to test for an exception you can use &lt;code&gt;$this-&amp;gt;expectException(EvatrInvalidVatNumberException::class);&lt;/code&gt; for setting up your test to listen to the exception. If the expected exception is not thrown your test will fail and PHPUnit will report the issue.&lt;/p&gt;

&lt;p&gt;When you only need to assert the message (or a substring) you would use $this-&amp;gt;expectExceptionMessage().&lt;/p&gt;

&lt;p&gt;In most test cases this works very well. But sometimes you might want to assert some more aspects of the exception. For example if it contains some debug infos.&lt;/p&gt;

&lt;p&gt;Of course you could try to use &lt;code&gt;$this-&amp;gt;expectExceptionObject()&lt;/code&gt; but this is only a shortcut for expecting the class, message and code. It won't compare properties on the exception for example (!).&lt;/p&gt;

&lt;p&gt;You could assert that the exception is thrown when you write the whole test by hand like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testAnExceptionIsThrown&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; 
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some debug info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;debugInfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Expected Exception is not thrown'&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 works. But its not very nice. The fail now does not provide any information anymore which exception is thrown instead. Therefore does not convey why the tests fails. And the return in the catch is hard to read and easy to miss.&lt;/p&gt;

&lt;p&gt;Looking closer at it, there is another issue. The exception count is wrong: &lt;code&gt;assertContains&lt;/code&gt; when the test passes, but actually you have two assertions&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The correct exception is thrown&lt;/li&gt;
&lt;li&gt;The exception contains the debug info&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A Better Approach
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;testAnExceptionIsThrownWithDebugInfo&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain\Exception&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Domain\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'some debug info'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;debugInfo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nv"&gt;$e&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;Testing the exception like this has some advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assertion count is now correct&lt;/li&gt;
&lt;li&gt;Your test will still be reporting correctly if the exception is changed&lt;/li&gt;
&lt;li&gt;It's more readable&lt;/li&gt;
&lt;li&gt;When you miss to add the throw the test will still fail&lt;/li&gt;
&lt;li&gt;The failing test (wrong exception thrown) will report nicely which exception is caught instead and skip the assertEquals&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Insights from the comments
&lt;/h2&gt;

&lt;p&gt;Be extra careful, when using something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Domain\Exception&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;\RuntimeException&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$code&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="nc"&gt;\Throwable&lt;/span&gt; &lt;span class="nv"&gt;$previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$debugInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&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="c1"&gt;// and in the test:&lt;/span&gt;
&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectExceptionObject&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;Domain\Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'this went wrong'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="n"&gt;debugInfo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'someDebugInfo'&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;Because the phpunit implementation (til 15) is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;final&lt;/span&gt; &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;expectExceptionObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Exception&lt;/span&gt; &lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;void&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectExceptionMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getMessage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;expectExceptionCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$exception&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getCode&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;So the test would not compare the $debugInfo, even if it looks like it SHOULD.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Originally published on ps-webforge.com on March 3rd, 2013, updated Dec 2025&lt;/em&gt;&lt;/p&gt;

</description>
      <category>php</category>
      <category>phpunit</category>
      <category>testing</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
