<?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: Łukasz Łepecki</title>
    <description>The latest articles on Forem by Łukasz Łepecki (@llepecki).</description>
    <link>https://forem.com/llepecki</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%2F2862581%2Fd66f48d8-101f-4b8e-aea7-768ca2e6b47b.png</url>
      <title>Forem: Łukasz Łepecki</title>
      <link>https://forem.com/llepecki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/llepecki"/>
    <language>en</language>
    <item>
      <title>The Economics of Testing: Why the Shape of Your Test Pyramid Matters</title>
      <dc:creator>Łukasz Łepecki</dc:creator>
      <pubDate>Tue, 18 Mar 2025 13:46:25 +0000</pubDate>
      <link>https://forem.com/llepecki/the-economics-of-testing-why-the-shape-of-your-test-pyramid-matters-489o</link>
      <guid>https://forem.com/llepecki/the-economics-of-testing-why-the-shape-of-your-test-pyramid-matters-489o</guid>
      <description>&lt;p&gt;If all QA can be covered by E2E tests, why even bother writing unit and integration tests at all?&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;When designing your test strategy, make sure that the test pyramid is actually shaped more or less like the ancient Egyptians would build it: perform as many tests as possible on the lowest part of the test pyramid because it will save you money.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unit Tests - Fast Execution and Instant Feedback
&lt;/h2&gt;

&lt;p&gt;Unit tests should be the base of your construction. They're fast and provide instant feedback. Decent coverage makes refactoring nice and easy since you can make sure you didn't break anything by instantly running unit tests after every small change you introduce. Don't obsess over the percentage of coverage though. Good coverage cuts through your business logic, not simply displays a big number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration Tests - The Cost Is Growing
&lt;/h2&gt;

&lt;p&gt;You won't cover everything with unit tests. At some point, you'll have to move on to integration tests that verify the correctness of interactions and communication between components you've already unit-tested individually. They should still be easy to reason about and debug, but you'll have to wait some time (seconds or - worst case - minutes) for the results. Building Docker images takes time, spinning up databases takes time, and setting up additional dependencies adds even more delay. When it all adds up, you'll have the reason why those tests can't be run as frequently as unit tests: time is money, and you don't have an infinite bag of gold, do you?&lt;/p&gt;

&lt;h2&gt;
  
  
  End to End Tests - Expensive but Necessary
&lt;/h2&gt;

&lt;p&gt;Integration tests alone won't provide enough coverage. At some point, you'll want to test the actual experience that your users interact with, i.e., perform API or UI tests. They are expensive mainly due to the complexity and scale of infrastructure required to run them. You have to maintain all dependencies required by your application like databases, authentication and authorization services, infrastructure like messaging brokers and so on. But that's not the end. You’ll also need realistic, production-like data, ensuring it doesn’t accumulate or become stale across multiple test runs. The execution time gets even longer for the end-to-end tests, but you'll spend even more time maintaining the whole environment that supports these tests. It'll cost at least an order of magnitude more than what you had to invest in both unit and integration tests; that's why you'll want to limit the coverage only to what couldn't be covered with the previous two kinds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Every test type has its place, but let the economics guide your strategy. Invest heavily in rapid-feedback unit tests, be selective with integration tests, and use expensive E2E tests only where absolutely necessary. Your wallet will thank you and so will your dev team when they're not waiting hours for test runs to complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Roy Osherove - &lt;em&gt;The Art of Unit Testing&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Vladimir Khorikov - &lt;em&gt;Unit Testing Principles, Practices, and Patterns&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helped you understand how to optimize your testing architecture and cut development costs, consider &lt;a href="https://www.buymeacoffee.com/llepecki" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt; ☕️&lt;/p&gt;

</description>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Commit Message Policy for Your Team</title>
      <dc:creator>Łukasz Łepecki</dc:creator>
      <pubDate>Mon, 17 Feb 2025 19:39:58 +0000</pubDate>
      <link>https://forem.com/llepecki/commit-message-policy-for-your-team-57bo</link>
      <guid>https://forem.com/llepecki/commit-message-policy-for-your-team-57bo</guid>
      <description>&lt;p&gt;&lt;a href="https://www.conventionalcommits.org/en/v1.0.0/" rel="noopener noreferrer"&gt;Conventional Commits&lt;/a&gt; is a standardized way to write commit messages. Following the convention will allow you to achieve a neat commit history that’s easily readable for both humans and machines.&lt;/p&gt;

&lt;p&gt;Here’s a quick guide on how to enforce this policy on your local machine so that you are always sure your commits follow the standard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a directory for Git hooks
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.git-templates/hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Git to use this directory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; core.hooksPath ~/.git-templates/hooks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create &lt;code&gt;commit-msg&lt;/code&gt; hook
&lt;/h2&gt;

&lt;p&gt;Create file at &lt;code&gt;~/.git-templates/hooks/commit-msg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;commit_msg_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;commit_msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$commit_msg_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;conventional_regex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(!)?(\([a-z0-9-]+\))?: .+$'&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$commit_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ &lt;span class="nv"&gt;$conventional_regex&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"-------------------------------------------------------------------------"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" ❌ The commit message does not follow Conventional Commits specification"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"     (see https://www.conventionalcommits.org/en/v1.0.0):"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" Format: type(scope)?(!)?: subject"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" Allowed types:"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"     feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;" Example: feat(api)!: Add validation to the create-user endpoint"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"-------------------------------------------------------------------------"&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Adjust the error message and the regex as needed.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Examples of valid commit messages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;feat: Add new user onboarding flow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fix(api): Correct error-handling for 404 responses&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refactor(ui)!: Overhaul navigation for better accessibility&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs: Update README to reflect API changes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make the hook executable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.git-templates/hooks/commit-msg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Existing repos
&lt;/h2&gt;

&lt;p&gt;Existing repos won’t automatically use the new global hooks if they already had &lt;code&gt;.git/hooks&lt;/code&gt; populated. In that case, you can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove or rename the repo’s local hooks folder so Git falls back to the global one.&lt;/li&gt;
&lt;li&gt;Or manually copy/symlink the commit-msg hook into each repo’s hooks folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once set, any commit message that fails the regex check will abort the commit with an error message, enforcing Conventional Commits across all your local repos.&lt;/p&gt;




&lt;p&gt;If you found this guide useful, consider &lt;a href="https://www.buymeacoffee.com/llepecki" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt; ☕️&lt;/p&gt;

</description>
      <category>git</category>
      <category>devops</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
