{
    "archive_path": "archive/1754185306.630266",
    "base_url": "olano.dev/blog/unit-testing-principles",
    "basename": "",
    "bookmarked_date": "2025-08-03 01:41",
    "canonical": {
        "archive_org_path": "https://web.archive.org/web/olano.dev/blog/unit-testing-principles",
        "dom_path": "output.html",
        "favicon_path": "favicon.ico",
        "git_path": "git/",
        "google_favicon_path": "https://www.google.com/s2/favicons?domain=olano.dev",
        "headers_path": "headers.json",
        "htmltotext_path": "htmltotext.txt",
        "index_path": "index.html",
        "media_path": "media/",
        "mercury_path": "mercury/content.html",
        "pdf_path": "output.pdf",
        "readability_path": "readability/content.html",
        "screenshot_path": "screenshot.png",
        "singlefile_path": "singlefile.html",
        "warc_path": "warc/",
        "wget_path": null
    },
    "domain": "olano.dev",
    "downloaded_at": "2025-08-03T01:41:49.164533+00:00",
    "downloaded_datestr": "2025-08-03 01:41",
    "extension": "",
    "hash": "1GTW2710KCM4P3GHP1EJ",
    "history": {
        "archive_org": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://web.archive.org/save/https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-03T01:43:31.906528+00:00",
                "index_texts": null,
                "output": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://olano.dev/blog/unit-testing-principles/']' timed out after 60 seconds",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:31.831816+00:00",
                "status": "failed"
            }
        ],
        "dom": [
            {
                "cmd": [
                    "/usr/bin/chromium-browser",
                    "--proxy-server=socks5://tor-socks-proxy:9150",
                    "--disable-features=DarkMode",
                    "--run-all-compositor-stages-before-draw",
                    "--hide-scrollbars",
                    "--autoplay-policy=no-user-gesture-required",
                    "--no-first-run",
                    "--use-fake-ui-for-media-stream",
                    "--use-fake-device-for-media-stream",
                    "--simulate-outdated-no-au='Tue, 31 Dec 2099 23:59:59 GMT'",
                    "--headless=new",
                    "--no-sandbox",
                    "--no-zygote",
                    "--disable-dev-shm-usage",
                    "--disable-software-rasterizer",
                    "--disable-sync",
                    "--window-size=1440,2000",
                    "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "--user-data-dir=/data/personas/Default/chrome_profile",
                    "--profile-directory=Default",
                    "--dump-dom",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "131.0.6778",
                "end_ts": "2025-08-03T01:42:09.857515+00:00",
                "index_texts": null,
                "output": "output.html",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:41:57.159049+00:00",
                "status": "succeeded"
            }
        ],
        "favicon": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--output",
                    "favicon.ico",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://www.google.com/s2/favicons?domain=olano.dev"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-03T01:41:52.407662+00:00",
                "index_texts": null,
                "output": "favicon.ico",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:41:49.362284+00:00",
                "status": "succeeded"
            }
        ],
        "git": [],
        "headers": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--head",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-03T01:41:56.961299+00:00",
                "index_texts": null,
                "output": "headers.json",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:41:52.465195+00:00",
                "status": "succeeded"
            }
        ],
        "htmltotext": [
            {
                "cmd": [
                    "(internal) archivebox.extractors.htmltotext",
                    "./{singlefile,dom}.html"
                ],
                "cmd_version": "0.8.5rc51",
                "end_ts": "2025-08-03T01:42:21.655118+00:00",
                "index_texts": [
                    "Unit Testing Principles | olano.dev (/assets/css/main.css) (/feed.xml) (olano.dev) (https://olano.dev/blog/unit-testing-principles)  (/) olano.dev (/blog) blog (/#etc) etc  Unit Testing Principles 2025-01-23 (/blog/tags#software) #software (/blog/tags#books) #books (/blog/tags#tldr) #tldr    (https://enterprisecraftsmanship.com/book/)   Background  I learned about the (https://enterprisecraftsmanship.com/book/) Unit Testing  book through Sa\u0161a Juri\u0107\u2019s (https://www.youtube.com/watch?v=6sNmJtoKDCo) Clarity talk . The entire talk was brilliant but the last 15 minutes especially, when he turned the discussion to testing, were eye-opening. Juri\u0107 attributed his style of testing units of behavior instead of units of code to Vladimir Khorikov\u2019s Unit Testing book, so I decided to buy a copy. I had the book but, truth is, I didn\u2019t plan to read it. It didn\u2019t even make it to my (../my-software-bookshelf) software bookshelf post. After all, I knew what worked for me and what didn\u2019t when it came to tests; there sure was plenty to learn from the book, but I had enough to get by. I\u2019d rather spend my reading time on some other book. But then I started a new job, joining a new team. What I found there was curious:\nmy new colleagues had been maintaining an extensive test suite, they were very disciplined about it, coverage was high, every public function on every module had its own test. And, yet, this was an ineffective test suite. Tests were a lot of work to write, breaking on the smallest of refactors, bugs slipping through the cracks.\nWhat\u2019s worse, this wasn\u2019t perceived as a problem; the team didn\u2019t realize they could do better. I have (../what-i-think-i-know-about-testing) strong opinions about testing, so I immediately knew what I wanted to change on this project. The problem was that my opinions were that: just opinions\u2014based on experience but ultimately subjective intuitions. And I was the new guy, without reputation credits to spend; I would need something better than my gut feeling to convince the team to change habits, and to justify the effort to my manager. So for a while, I (https://www.simplermachines.com/why-you-need-a-wtf-notebook/) refrained from proposing any changes and started reading the testing book instead. Commentary  Right from the introduction, this book proved to be what I was looking for: This book can help you articulate why the techniques and best practices you\u2019ve been using all along are so helpful. Don\u2019t underestimate this skill. The ability to clearly communicate your ideas to colleagues is priceless. A software developer\u2014even a great one\u2014rarely gets full credit for a design decision if they can\u2019t explain why, exactly, that decision was made. This book can help you transform your knowledge from the realm of the unconscious to something you are able to talk about with anyone.  I come from a mathematical background and strongly believe that guidelines in programming, like theorems in math, should be derived from first principles. I\u2019ve tried to structure this book in a similar way: start with a blank slate by not jumping to conclusions or throwing around unsubstantiated claims, and gradually build my case from the ground up. Interestingly enough, once you establish such first principles, guidelines and best practices often flow naturally as mere implications.  What pleasantly surprised me was that, without trying too hard, this book says a lot about software design.\nIt makes sense when you think about it: if, as the author suggests, we backtrack to the foundation of our discipline, we\u2019ll land on what testing and design have in common: the pursuit of sustainable software. A good design lends itself to efficient testing\u2014striving for a good test suite helps arrive at a good design. This is not to say that the code should be adjusted to the tests. And is not to say that the tests should be driving the implementation. This interrelation between design and testing is best illustrated in chapter 7, where the author suggests an ideal structure for the codebase, and shows how to refactor code towards that structure, thus enabling effective tests.  Overcomplicated code should be split into deep domain classes, to be thoroughly unit tested, and wide controllers,  exercised by strategic integration tests.  I found this notion interestingly similar to the discussion of module depth from A Philosophy of Software Design :  But where Ousterhout advocates for avoiding shallow modules, Khorikov suggests that there\u2019s a role for such wide (and thin) classes: to orchestrate the pieces involved in any meaningful operation, freeing the domain model to focus on business logic\u2014the program\u2019s essence. Highlights  Chapter 1: The goal of unit testing  The goal of testing is to enable sustainable growth of the software project. Some tests are valuable and contribute a lot to overall software quality. Others don\u2019t. They raise false alarms, don\u2019t help you catch regression errors, and are slow and difficult to maintain. To enable sustainable project growth, you have to exclusively focus on high-quality tests\u2014those are the only type of tests that are worth keeping in the test suite. Coverage metrics are a good negative indicator (low coverage means you\u2019re not testing enough) but a bad positive one (high coverage doesn\u2019t guarantee good testing quality). Targeting a specific coverage number creates a perverse incentive that goes against the goal of unit testing.  Chapter 2: What is a unit test?  A unit test is an automated test that: verifies a single unit of behavior , does it quickly, and does it in isolation from other tests .   Tests shouldn\u2019t verify units of code . Rather, they should verify units of behavior , something that is meaningful for the problem domain and, ideally, something that a business person can recognize as useful. The number of classes it takes to implement such a unit of behavior is irrelevant. The ubiquitous use of mocks produces tests that couple too tightly to the implementation. Instead of reaching for mocks to test a large, complicated graph of interconnected classes, you should focus on not having such a graph of classes in the first place. More often than not, a large class graph is a result of a code design problem.  Chapter 4: The four pillars of a good unit test  A good unit test has the following four attributes: Protection against regressions Resistance to refactoring Fast feedback Maintainability   When there is resistance to refactoring, you become confident that your code changes won\u2019t lead to regressions. Without such confidence, you will be much more hesitant to refactor and much more likely to leave the code base to deteriorate. The more the test is coupled to the implementation details of the system under test (SUT), the more false alarms it generates. You need to make sure the test verifies the end result the SUT delivers: its observable behavior, not the steps it takes to do that. Choose black-box testing over white-box testing by default. If you can\u2019t trace a test back to a business requirement, it\u2019s an indication of the test\u2019s brittleness. Either restructure or delete this test.  Chapter 5: Mocks and test fragility  For a piece of code to be part of the system\u2019s observable behavior, it has to do one of the following things: Expose an operation that helps the client achieve one of its goals. Expose a state that helps the client achieve one of its goals.  Any code that does neither of those two things is an implementation detail.  Ideally, the system\u2019s public API surface should coincide with its observable behavior, and all its implementation details should be hidden from the eyes of the clients. Such a system has a well-designed API. Making the API well-designed automatically improves unit tests. The way your system talks to the external world forms the observable behavior of that system as a whole. It\u2019s part of the contract your application must hold at all times. The use of mocks is beneficial when verifying the communication pattern between your system and external applications. Conversely, using mocks to verify communications between classes inside your system results in tests that couple to implementation details and therefore fall short of the resistance-to-refactoring metric.  Chapter 7: Refactoring toward valuable unit tests  All production code can be categorized along two dimensions: Complexity or domain significance. The number of collaborators.   This categorization gives us four kinds of code: Trivial code (low complexity/significance, few collaborators): this code shouldn\u2019t be tested at all Domain model and algorithms (high complexity/significance, few collaborators): this code should be unit tested. The resulting unit tests are highly valuable and cheap. Controllers (low complexity/significance, many collaborators): controllers should be briefly tested as part of overarching integration tests. Overcomplicated code (high complexity/significance, many collaborators): this code is hard to test, and as such it\u2019s better to split it into domain/algorithms and controllers.   The domain model encapsulates the business logic and the controllers deal with the orchestration of collaborators. You can think of these two responsibilities in terms of code depth versus code width . Your code can be either deep (complex or important) or wide (work with many collaborators), but not both. Getting rid of the overcomplicated code and unit testing only the domain model and algorithms is the path to a highly valuable, easily maintainable test suite. With this approach, you won\u2019t have 100% test coverage, but you don\u2019t need to.  Chapter 8: Why integration testing?  Check as many of the business scenario\u2019s edge cases as possible with unit tests; use integration tests to cover one happy path, as well as any edge cases that can\u2019t be covered by unit tests. In the most trivial cases, you might have no unit tests whatsoever. Integration tests retain their value even in simple applications. Try to always have an explicit, well-known place for the domain model in your code base. The explicit boundary makes it easier to tell the difference between unit and integration tests. Layers of indirection negatively affect your ability to reason about the code. This results in a lot of low-value integration tests, that provide insufficient protection against regressions combined with low resistance to refactoring. In most backend systems, you can get away with just three layers: the domain model, application services layer (controllers), and infrastructure layer.  \u2190 (/blog/what-i-think-i-know-about-testing) What I think I know about testing   (/blog/gleam-coming-from-erlang) Gleam, coming from Erlang  \u2192    (https://olano.dev/blog/unit-testing-principles)  (/) Facundo Olano If we backtrack to the foundation of our discipline, we\u2019ll land on what testing and design have in common: the pursuit of sustainable software.    powered by (https://jorge.olano.dev) jorge | (https://github.com/facundoolano/olano.dev/tree/main/src/blog/unit-testing-principles.org) source | (mailto:facundo.olano@gmail.com) contact     "
                ],
                "output": "htmltotext.txt",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:21.585275+00:00",
                "status": "succeeded"
            }
        ],
        "media": [
            {
                "cmd": [
                    "/usr/local/bin/yt-dlp",
                    "--restrict-filenames",
                    "--trim-filenames",
                    "128",
                    "--write-description",
                    "--write-info-json",
                    "--write-annotations",
                    "--write-thumbnail",
                    "--no-call-home",
                    "--write-sub",
                    "--write-auto-subs",
                    "--convert-subs=srt",
                    "--yes-playlist",
                    "--continue",
                    "--no-abort-on-error",
                    "--ignore-errors",
                    "--geo-bypass",
                    "--add-metadata",
                    "--format=(bv*+ba/b)[filesize<=750m][filesize_approx<=?750m]/(bv*+ba/b)",
                    "--skip-download",
                    "--cache-dir=/data/yt-dlp-cache/",
                    "--cookies=/data/yt-dlp-cache/cookies.txt",
                    "--proxy=socks5://tor-socks-proxy:9150",
                    "--no-playlist",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "2024.10.7",
                "end_ts": "2025-08-03T01:42:31.783010+00:00",
                "index_texts": [],
                "output": "media/",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:22.059399+00:00",
                "status": "succeeded"
            }
        ],
        "mercury": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/postlight-parser",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "2.2.3",
                "end_ts": "2025-08-03T01:42:21.525260+00:00",
                "index_texts": null,
                "output": "mercury/",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:15.060408+00:00",
                "status": "succeeded"
            }
        ],
        "pdf": [],
        "readability": [
            {
                "cmd": [
                    "/home/archivebox/.npm/bin/readability-extractor",
                    "/tmp/tmpjdjwx957",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "0.0.11",
                "end_ts": "2025-08-03T01:42:14.576476+00:00",
                "index_texts": [
                    "Unit Testing Principles\n      \n      \n      \n    \n\n      \n\n\nBackground\n\nI learned about the Unit Testing book through Sa\u0161a Juri\u0107\u2019s Clarity talk. The entire talk was brilliant but the last 15 minutes especially, when he turned the discussion to testing, were eye-opening. Juri\u0107 attributed his style of testing units of behavior instead of units of code to Vladimir Khorikov\u2019s Unit Testing book, so I decided to buy a copy.\n\nI had the book but, truth is, I didn\u2019t plan to read it. It didn\u2019t even make it to my software bookshelf post. After all, I knew what worked for me and what didn\u2019t when it came to tests; there sure was plenty to learn from the book, but I had enough to get by. I\u2019d rather spend my reading time on some other book.\n\nBut then I started a new job, joining a new team. What I found there was curious:\nmy new colleagues had been maintaining an extensive test suite, they were very disciplined about it, coverage was high, every public function on every module had its own test. And, yet, this was an ineffective test suite. Tests were a lot of work to write, breaking on the smallest of refactors, bugs slipping through the cracks.\nWhat\u2019s worse, this wasn\u2019t perceived as a problem; the team didn\u2019t realize they could do better.\n\nI have strong opinions about testing, so I immediately knew what I wanted to change on this project. The problem was that my opinions were that: just opinions\u2014based on experience but ultimately subjective intuitions. And I was the new guy, without reputation credits to spend; I would need something better than my gut feeling to convince the team to change habits, and to justify the effort to my manager. So for a while, I refrained from proposing any changes and started reading the testing book instead.\n\n\nRight from the introduction, this book proved to be what I was looking for:\n\nThis book can help you articulate why the techniques and best practices you\u2019ve been using all along are so helpful. Don\u2019t underestimate this skill. The ability to clearly communicate your ideas to colleagues is priceless. A software developer\u2014even a great one\u2014rarely gets full credit for a design decision if they can\u2019t explain why, exactly, that decision was made. This book can help you transform your knowledge from the realm of the unconscious to something you are able to talk about with anyone.\n\n\nI come from a mathematical background and strongly believe that guidelines in programming, like theorems in math, should be derived from first principles. I\u2019ve tried to structure this book in a similar way: start with a blank slate by not jumping to conclusions or throwing around unsubstantiated claims, and gradually build my case from the ground up. Interestingly enough, once you establish such first principles, guidelines and best practices often flow naturally as mere implications.\n\n\nWhat pleasantly surprised me was that, without trying too hard, this book says a lot about software design.\nIt makes sense when you think about it: if, as the author suggests, we backtrack to the foundation of our discipline, we\u2019ll land on what testing and design have in common: the pursuit of sustainable software.\n\nA good design lends itself to efficient testing\u2014striving for a good test suite helps arrive at a good design. This is not to say that the code should be adjusted to the tests. And is not to say that the tests should be driving the implementation.\n\nThis interrelation between design and testing is best illustrated in chapter 7, where the author suggests an ideal structure for the codebase, and shows how to refactor code towards that structure, thus enabling effective tests.\n\n\n\nOvercomplicated code should be split into deep domain classes, to be thoroughly unit tested, and wide controllers,  exercised by strategic integration tests.\n\n\n\nI found this notion interestingly similar to the discussion of module depth from A Philosophy of Software Design:\n\n\n\nBut where Ousterhout advocates for avoiding shallow modules, Khorikov suggests that there\u2019s a role for such wide (and thin) classes: to orchestrate the pieces involved in any meaningful operation, freeing the domain model to focus on business logic\u2014the program\u2019s essence.\n\nHighlights\n\n\nChapter 1: The goal of unit testing\n\n\nThe goal of testing is to enable sustainable growth of the software project.\nSome tests are valuable and contribute a lot to overall software quality. Others don\u2019t. They raise false alarms, don\u2019t help you catch regression errors, and are slow and difficult to maintain.\nTo enable sustainable project growth, you have to exclusively focus on high-quality tests\u2014those are the only type of tests that are worth keeping in the test suite.\nCoverage metrics are a good negative indicator (low coverage means you\u2019re not testing enough) but a bad positive one (high coverage doesn\u2019t guarantee good testing quality). Targeting a specific coverage number creates a perverse incentive that goes against the goal of unit testing.\n\n\nChapter 2: What is a unit test?\n\n\n\nA unit test is an automated test that:\n\nverifies a single unit of behavior,\ndoes it quickly,\nand does it in isolation from other tests.\n\n\nTests shouldn\u2019t verify units of code. Rather, they should verify units of behavior, something that is meaningful for the problem domain and, ideally, something that a business person can recognize as useful. The number of classes it takes to implement such a unit of behavior is irrelevant.\nThe ubiquitous use of mocks produces tests that couple too tightly to the implementation.\nInstead of reaching for mocks to test a large, complicated graph of interconnected classes, you should focus on not having such a graph of classes in the first place. More often than not, a large class graph is a result of a code design problem.\n\n\nChapter 4: The four pillars of a good unit test\n\n\n\nA good unit test has the following four attributes:\n\nProtection against regressions\nResistance to refactoring\nFast feedback\nMaintainability\n\n\nWhen there is resistance to refactoring, you become confident that your code changes won\u2019t lead to regressions. Without such confidence, you will be much more hesitant to refactor and much more likely to leave the code base to deteriorate.\nThe more the test is coupled to the implementation details of the system under test (SUT), the more false alarms it generates. You need to make sure the test verifies the end result the SUT delivers: its observable behavior, not the steps it takes to do that.\nChoose black-box testing over white-box testing by default. If you can\u2019t trace a test back to a business requirement, it\u2019s an indication of the test\u2019s brittleness. Either restructure or delete this test.\n\n\nChapter 5: Mocks and test fragility\n\n\n\nFor a piece of code to be part of the system\u2019s observable behavior, it has to do one of the following things:\n\nExpose an operation that helps the client achieve one of its goals.\nExpose a state that helps the client achieve one of its goals.\n\nAny code that does neither of those two things is an implementation detail.\n\nIdeally, the system\u2019s public API surface should coincide with its observable behavior, and all its implementation details should be hidden from the eyes of the clients. Such a system has a well-designed API. Making the API well-designed automatically improves unit tests.\nThe way your system talks to the external world forms the observable behavior of that system as a whole. It\u2019s part of the contract your application must hold at all times.\nThe use of mocks is beneficial when verifying the communication pattern between your system and external applications. Conversely, using mocks to verify communications between classes inside your system results in tests that couple to implementation details and therefore fall short of the resistance-to-refactoring metric.\n\n\nChapter 7: Refactoring toward valuable unit tests\n\n\n\nAll production code can be categorized along two dimensions:\n\nComplexity or domain significance.\nThe number of collaborators.\n\n\n\nThis categorization gives us four kinds of code:\n\nTrivial code (low complexity/significance, few collaborators): this code shouldn\u2019t be tested at all\nDomain model and algorithms (high complexity/significance, few collaborators): this code should be unit tested. The resulting unit tests are highly valuable and cheap.\nControllers (low complexity/significance, many collaborators): controllers should be briefly tested as part of overarching integration tests.\nOvercomplicated code (high complexity/significance, many collaborators): this code is hard to test, and as such it\u2019s better to split it into domain/algorithms and controllers.\n\n\nThe domain model encapsulates the business logic and the controllers deal with the orchestration of collaborators. You can think of these two responsibilities in terms of code depth versus code width. Your code can be either deep (complex or important) or wide (work with many collaborators), but not both.\nGetting rid of the overcomplicated code and unit testing only the domain model and algorithms is the path to a highly valuable, easily maintainable test suite. With this approach, you won\u2019t have 100% test coverage, but you don\u2019t need to.\n\n\nChapter 8: Why integration testing?\n\n\nCheck as many of the business scenario\u2019s edge cases as possible with unit tests; use integration tests to cover one happy path, as well as any edge cases that can\u2019t be covered by unit tests.\nIn the most trivial cases, you might have no unit tests whatsoever. Integration tests retain their value even in simple applications.\nTry to always have an explicit, well-known place for the domain model in your code base. The explicit boundary makes it easier to tell the difference between unit and integration tests.\nLayers of indirection negatively affect your ability to reason about the code. This results in a lot of low-value integration tests, that provide insufficient protection against regressions combined with low resistance to refactoring.\nIn most backend systems, you can get away with just three layers: the domain model, application services layer (controllers), and infrastructure layer."
                ],
                "output": "readability/",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:10.522876+00:00",
                "status": "succeeded"
            }
        ],
        "screenshot": [],
        "singlefile": [],
        "title": [
            {
                "cmd": [
                    "/usr/bin/curl",
                    "--silent",
                    "--location",
                    "--compressed",
                    "--proxy",
                    "socks5://tor-socks-proxy:9150",
                    "--max-time",
                    "60",
                    "--user-agent",
                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)",
                    "https://olano.dev/blog/unit-testing-principles/"
                ],
                "cmd_version": "8.10.1",
                "end_ts": "2025-08-03T01:42:10.129873+00:00",
                "index_texts": null,
                "output": "Unit Testing Principles | olano.dev",
                "pwd": "/data/archive/1754185306.630266",
                "schema": "ArchiveResult",
                "start_ts": "2025-08-03T01:42:10.078113+00:00",
                "status": "succeeded"
            }
        ],
        "wget": []
    },
    "icons": null,
    "is_archived": true,
    "is_static": false,
    "latest": {
        "archive_org": "TimeoutExpired: Command '['/usr/bin/curl', '--silent', '--location', '--compressed', '--proxy', 'socks5://tor-socks-proxy:9150', '--head', '--max-time', '60', '--user-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 ArchiveBox/{VERSION} (+https://github.com/ArchiveBox/ArchiveBox/)', 'https://web.archive.org/save/https://olano.dev/blog/unit-testing-principles/']' timed out after 60 seconds",
        "dom": "output.html",
        "favicon": "favicon.ico",
        "git": null,
        "media": "media/",
        "pdf": null,
        "screenshot": null,
        "singlefile": null,
        "title": "Unit Testing Principles | olano.dev",
        "warc": null,
        "wget": null
    },
    "link_dir": "/data/archive/1754185306.630266",
    "newest_archive_date": "2025-08-03T01:42:31.831816+00:00",
    "num_failures": 1,
    "num_outputs": 8,
    "oldest_archive_date": "2025-08-03T01:41:49.362284+00:00",
    "path": "/blog/unit-testing-principles/",
    "schema": "Link",
    "scheme": "https",
    "snapshot_abid": "snp_01K1PSF4JJA0DC406C01YZCWV0",
    "snapshot_id": "ac517291-9f54-41dd-b335-dffd3df67360",
    "sources": [
        "/data/sources/1754185304-import.txt"
    ],
    "tags": null,
    "tags_str": "",
    "timestamp": "1754185306.630266",
    "title": "Unit Testing Principles | olano.dev",
    "url": "https://olano.dev/blog/unit-testing-principles/"
}