Jekyll2023-10-29T20:36:39+00:00https://matthewonsoftware.com/feed.xmlMatthew on SoftwareMatthew is a software developer from Poland, who shares things that he learnt and already knew and want to share it in blog post form. Hope you will find something useful for yourself.Mateusz DeskaRequest-Response vs Publish-Subscribe Architecture2023-09-23T00:00:00+00:002023-09-23T00:00:00+00:00https://matthewonsoftware.com/blog/request-response-vs-publish-subscribe<h3 id="request-response-architecture">Request-Response Architecture</h3>
<p>Request-Response is a foundational communication pattern primarily utilized in client-server architectures. In this model, a client sends a request to a server, which then processes the request and sends back an appropriate response. This interaction ensures a tight coupling between the client’s request and the server’s response, making the communication synchronous and bidirectional.</p>
<p>Key characteristics of the Request-Response model include:</p>
<ol>
<li><strong>Direct Interaction:</strong> The client actively waits for the server’s response after sending a request. The server, upon receiving the request, is expected to provide a corresponding reply.</li>
<li><strong>Statelessness:</strong> Typically found in HTTP-based systems, the server doesn’t retain any memory of client interactions between individual requests, promoting scalability and simplicity.</li>
<li><strong>Point-to-point Communication:</strong> The model revolves around direct communication between two entities, namely the client and the server, making it simple and straightforward.</li>
</ol>
<h3 id="request-response-pros--cons">Request Response Pros & Cons</h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Elegant and Simple, well known approach.</li>
<li>Stateless (HTTP).</li>
<li>Scalable, especially horizontally (though ‘scalability’ is often an overloaded term).</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Not suitable for multiple receivers.</li>
<li>High Coupling.</li>
<li>Both the client and the server have to be active and running simultaneously.</li>
<li>Requires mechanisms like chaining, circuit breakers, and retries to ensure interconnected, highly coupled systems can communicate effectively.</li>
</ul>
<h3 id="publish-subscribe-architecture">Publish Subscribe Architecture</h3>
<p>Publish-Subscribe, or Pub/Sub, is an alternative message communication pattern where publishers send messages without specifying receivers. Instead, messages are categorized into topics. Subscribers, on the other hand, express interest in one or more topics and only receive messages that are of interest to them. Think of it like a magazine subscription model; publishers release editions without knowing who’ll read them, and subscribers receive only editions of magazines they’ve subscribed to.</p>
<p>The system managing these subscriptions and dispatching messages is commonly referred to as the “message broker”. It ensures that messages published to a topic are received by all subscribers to that topic.</p>
<ol>
<li><strong>Dynamic Scalability:</strong> New subscribers can be added dynamically without needing any changes in the publisher’s configuration.</li>
<li><strong>Decoupled Architecture:</strong> Publishers and subscribers are loosely coupled, meaning that they have minimal knowledge about each other, promoting flexibility in system evolution.</li>
<li><strong>Real-time Communication:</strong> Ideal for scenarios that require real-time updates like stock market feeds or live sports scores.</li>
</ol>
<h3 id="pubsub-pros--cons">Pub/Sub Pros & Cons</h3>
<p><strong>Pros:</strong></p>
<ul>
<li>Easily scales with multiple receivers or subscribers.</li>
<li>Perfectly suited for microservices architecture.</li>
<li>Loose coupling (<a href="https://matthewonsoftware.com/blog/what-is-coupling/">please refer to this post about coupling</a>)</li>
<li>Operational even when some clients are offline.</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li>Message delivery issues can lead to eventual consistency challenges.</li>
<li>Introduces complexity, e.g., managing back pressure, brokers storing messages, re-reading of topics, etc.</li>
</ul>
<h3 id="summary">Summary</h3>
<p>Request-Response and Publish-Subscribe architectures each have their strengths, and neither is universally superior. The choice depends on the specific context and use case. While Request-Response is excellent for direct and immediate interactions, Publish-Subscribe is tailored for scalable, real-time, and decoupled communications. Often, systems can benefit from employing both approaches in tandem, harnessing the strengths of each to address varied needs.</p>Mateusz DeskaRequest-Response Architecture Request-Response is a foundational communication pattern primarily utilized in client-server architectures. In this model, a client sends a request to a server, which then processes the request and sends back an appropriate response. This interaction ensures a tight coupling between the client’s request and the server’s response, making the communication synchronous and bidirectional.Unraveling the Schools of Unit Testing: The Classic and London Approaches2023-06-27T00:00:00+00:002023-06-27T00:00:00+00:00https://matthewonsoftware.com/testing/system%20quality/schools-of-unit-tests<p>There exist two major methodologies when it comes to unit testing. These methodologies guide how much to use mocks, stubs, and the special test implementations known as test doubles. The two prominent schools of thought are the Classic School, also known as the “Detroit School” or the “Classical School,” and the London School. These schools primarily differ in the way they define the concept of isolation.</p>
<h3 id="classic-school">Classic School</h3>
<p>In the Classic School, isolation signifies the separation of individual tests from each other. This school emphasizes running tests in parallel and in-memory, ensuring no shared state, such as databases, where individual tests could affect the state. The use of mocks, stubs, and test doubles should be minimal in this school. They’re only employed for dependencies that might introduce a shared state and disrupt this kind of isolation.</p>
<p>The aim here is to use production code wherever possible. For these tests, a unit is defined as a class or even a set of classes. Therefore, we want to test an aggregate collectively without overutilizing mocks, stubs, and test doubles, restricting their usage to dependencies that would change the state.</p>
<h3 id="london-school">London School</h3>
<p>In stark contrast, the London School defines a unit as a single class. The goal is to test this class in isolation, unlike the Classic School’s approach that aims to test a whole component made up of classes performing a common functionality.</p>
<p>In the London School, we create a test implementation of a class, for instance, CustomerVerifier, that performs customer verification based on different implementations. The test implementation might be “always passed verification” or “always failed”. Each verification type, such as by name or PESEL (Polish national identification number), would then be tested separately.</p>
<h3 id="which-school-to-choose">Which School to Choose?</h3>
<p>Both schools come with their distinct advantages and disadvantages. Here are the pros of each approach:</p>
<h4 id="classic-school-detroit-or-classical-school">Classic School (Detroit or Classical School)</h4>
<ul>
<li>
<p><strong>Reflects Real Operation:</strong> The Classic School’s tests closely mimic the actual functioning of the system, providing a high degree of confidence in the system’s real-world performance.</p>
</li>
<li>
<p><strong>Better Coverage:</strong> With a more comprehensive approach to testing, the Classic School achieves superior coverage of the real system logic.</p>
</li>
<li>
<p><strong>Emphasizes Production Code:</strong> This school encourages the usage of production code in testing as much as possible, limiting the use of mocks and stubs only to dependencies introducing shared state.</p>
</li>
</ul>
<h4 id="london-school-1">London School</h4>
<ul>
<li>
<p><strong>Isolation:</strong> The London School excels at isolating the code for testing purposes, making it easier to pinpoint and rectify errors.</p>
</li>
<li>
<p><strong>Simplifies Test Writing:</strong> Utilizing test doubles and mocks helps simplify the graphs of collaborators, making the test writing process faster and easier.</p>
</li>
<li>
<p><strong>Effective for Complex Collaborators’ Graph:</strong> This approach is especially beneficial when dealing with complex collaborators’ graph and difficult setup for tests. The extensive use of mocks enables efficient testing of the code in isolation.</p>
</li>
</ul>
<p>Jakub Pilimon proposed a heuristic in <a href="https://www.youtube.com/watch?v=GjKYLmimGeE">his presentation “Testing – Love, Hate, Love”</a> that may be useful in this context. He recommends examining the class being tested and considering if the dependencies are separate merely for better code readability or if they introduce different responsibilities and layers. If it’s solely a matter of readability, using the actual object is preferable. However, if multiple responsibilities and layers emerge, it’s better to use a mock.</p>
<p>In conclusion, the key is to avoid being purists. Depending on the complexity of the collaborators’ graph and the difficulty of setting up the tests, both schools can be employed. However, caution with mocks is advised. As noted by the authors of the Mockito mocking library, if everything is mocked, are we truly testing the production code at all? They advocate for a mixed approach, underscoring the need for balance and judgment in choosing the right testing strategy.</p>Mateusz DeskaThere exist two major methodologies when it comes to unit testing. These methodologies guide how much to use mocks, stubs, and the special test implementations known as test doubles. The two prominent schools of thought are the Classic School, also known as the “Detroit School” or the “Classical School,” and the London School. These schools primarily differ in the way they define the concept of isolation.Rate Limiting2023-05-17T00:00:00+00:002023-05-17T00:00:00+00:00https://matthewonsoftware.com/system%20design/rate-limiting<p>Rate limiting is a pivotal technique employed to manage and control the number of incoming and outgoing requests
interacting with a system. Not only does it serve as a defensive shield against denial-of-service (DoS) attacks, but it
also facilitates a fair distribution of resources among users, ensuring a smooth and uninterrupted service for all. The
application of rate limiting can be diverse, extending to different levels such as IP-address, user-account, or even a
geographic region. Moreover, this strategy can be implemented in a tiered structure, for example, limiting a particular
network request to 1 per second, 5 per 10 seconds, and 10 per minute.</p>
<p>Beyond its preventative role against cyber threats, rate limiting also brings about other significant benefits. It helps
maintain system stability by preventing overuse or misuse of resources, thus reducing the risk of system failures. It
can also protect sensitive information from being exposed to brute force attacks. Additionally, rate limiting aids in
controlling costs associated with third-party APIs that charge based on the number of requests.</p>
<p>Without rate limiting, systems are left vulnerable to malicious actors who can flood them with superfluous requests,
potentially causing disruptions or complete shutdowns. Thus, rate limiting is not just a security measure, but an
essential component for maintaining the robustness, performance, and financial viability of a system.</p>
<h3 id="dos-attack">DoS Attack</h3>
<p>Denial-of-service attack, abbreviated as DoS attack, is a malevolent endeavor where an attacker aims to render a system
unavailable to its users. This is usually achieved by overloading the system with excessive traffic. Although rate
limiting can prevent certain types of DoS attacks, others might be considerably more challenging to counter.</p>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-17-rate-limiting/dos.png" alt="img" /></p>
<h3 id="ddos-attack">DDoS Attack</h3>
<p>A distributed denial-of-service attack, or DDoS attack, is a more complex form of a DoS attack. In a DDoS attack, the
overwhelming traffic directed at the target system originates from numerous sources—potentially thousands of
machines—making it significantly more difficult to prevent or mitigate.</p>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-17-rate-limiting/ddos.png" alt="img" /></p>
<h3 id="examples-and-analogies">Examples and Analogies</h3>
<p>To visualize the concept of rate limiting, consider a popular restaurant with a maximum seating capacity. If more customers arrive than the restaurant can accommodate, the excess customers are asked to wait, ensuring the restaurant can effectively serve those already seated. In this scenario, the restaurant’s seating capacity is akin to the rate limit, and the customers represent incoming requests to a system.</p>
<p>For DoS and DDoS attacks, imagine a highway filled with cars. In a DoS attack, it’s as if a single truck has intentionally blocked all lanes, stopping the flow of traffic. In a DDoS attack, it’s like hundreds of cars from different directions converging on the same highway, causing a massive traffic jam.</p>Mateusz DeskaRate limiting is a pivotal technique employed to manage and control the number of incoming and outgoing requests interacting with a system. Not only does it serve as a defensive shield against denial-of-service (DoS) attacks, but it also facilitates a fair distribution of resources among users, ensuring a smooth and uninterrupted service for all. The application of rate limiting can be diverse, extending to different levels such as IP-address, user-account, or even a geographic region. Moreover, this strategy can be implemented in a tiered structure, for example, limiting a particular network request to 1 per second, 5 per 10 seconds, and 10 per minute.Best Practices for Writing Effective and Reliable Tests2023-05-12T00:00:00+00:002023-05-12T00:00:00+00:00https://matthewonsoftware.com/testing/system%20quality/best-practices-for-writing-effective-and-reliable-tests<p>Many teams face a common challenge after introducing the practice of writing tests: builds take up to half an hour, refactoring tests prove to be difficult, and testing consumes too much of the team’s time and energy. However, this problem can be solved by adhering to good testability practices.</p>
<h3 id="the-first-principle">The FIRST Principle</h3>
<p>The FIRST principle is a set of criteria that good unit tests should meet:</p>
<ul>
<li>
<p><strong>Fast</strong>: Quick tests lead to quicker project building times. However, the execution time can vary depending on the type of test. Typically, unit tests are faster than integration tests, which in turn are faster than end-to-end (E2E) tests.</p>
</li>
<li>
<p><strong>Independent</strong>: Tests should not depend on each other. Each test should be able to run alone and in any order. This makes it easier to pinpoint problems when a test fails.</p>
</li>
<li>
<p><strong>Repeatable</strong>: Tests should give the same result each time, no matter how often they are run.</p>
</li>
<li>
<p><strong>Self-Validating</strong>: The test should have a clear result, either positive or negative. There’s no need for any additional activities to interpret the result.</p>
</li>
<li>
<p><strong>Timely</strong>: Ideally, tests should be written just before the production code that makes them pass (Test-Driven Development - TDD).</p>
</li>
</ul>
<h3 id="clarity-in-tests">Clarity in Tests</h3>
<p>In the realm of software testing, readability and clarity are crucial to understanding the purpose of each test, the functionality being tested, and the expected outcome. Let’s consider an initial version of a test that lacks these elements:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">testVerification</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">Customer</span> <span class="n">correctCustomer</span> <span class="o">=</span> <span class="nc">CustomerBuilder</span><span class="o">.</span><span class="na">create</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="nc">HttpPost</span> <span class="n">httpPost</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpPost</span><span class="o">(</span><span class="no">LOAN_ORDERS_URI</span><span class="o">);</span>
<span class="n">httpPost</span><span class="o">.</span><span class="na">setEntity</span><span class="o">(</span><span class="k">new</span> <span class="nc">StringEntity</span><span class="o">(</span><span class="n">objectMapper</span><span class="o">.</span><span class="na">writeValueAsString</span><span class="o">(</span><span class="k">new</span> <span class="nc">LoanOrder</span><span class="o">(</span><span class="n">correctCustomer</span><span class="o">))));</span>
<span class="n">httpPost</span><span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"Content-type"</span><span class="o">,</span> <span class="s">"application/json"</span><span class="o">);</span>
<span class="nc">HttpResponse</span> <span class="n">postResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">httpPost</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">postResponse</span><span class="o">.</span><span class="na">getStatusLine</span><span class="o">().</span><span class="na">getStatusCode</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="mi">200</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">loanOrderId</span> <span class="o">=</span> <span class="nc">EntityUtils</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">postResponse</span><span class="o">.</span><span class="na">getEntity</span><span class="o">()).</span><span class="na">replaceAll</span><span class="o">(</span><span class="s">"data:"</span><span class="o">,</span> <span class="s">""</span><span class="o">).</span><span class="na">trim</span><span class="o">();</span>
<span class="nc">HttpGet</span> <span class="n">httpGet</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpGet</span><span class="o">(</span><span class="no">LOAN_ORDERS_URI</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="n">loanOrderId</span><span class="o">);</span>
<span class="n">httpGet</span><span class="o">.</span><span class="na">setHeader</span><span class="o">(</span><span class="s">"Content-type"</span><span class="o">,</span> <span class="s">"application/json"</span><span class="o">);</span>
<span class="nc">HttpResponse</span> <span class="n">getResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">httpGet</span><span class="o">);</span>
<span class="nc">LoanOrder</span> <span class="n">loanOrder</span> <span class="o">=</span> <span class="n">objectMapper</span><span class="o">.</span><span class="na">readValue</span><span class="o">(</span><span class="nc">EntityUtils</span><span class="o">.</span><span class="na">toString</span><span class="o">(</span><span class="n">getResponse</span><span class="o">.</span><span class="na">getEntity</span><span class="o">()).</span><span class="na">replaceAll</span><span class="o">(</span><span class="s">"data:"</span><span class="o">,</span> <span class="s">""</span><span class="o">),</span> <span class="nc">LoanOrder</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="no">VERIFIED</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>While this test might work perfectly, it’s somewhat hard to reason about. It’s not immediately clear what the test setup is, what operation is being tested, and what the expected outcome is. This can become problematic as the codebase grows and as more developers interact with the tests.</p>
<p>To address these issues, we can refactor the test to be more readable and clear. <strong>The Given-When-Then practice (Arrange-Act-Assert)</strong>, commonly used in behavior-driven development (BDD), is a brilliant approach to make tests more comprehensible. It separates the test method into three well-defined sections:</p>
<ul>
<li><strong>Given (set up)</strong>: Outlines the preconditions.</li>
<li><strong>When (execution)</strong>: Describes the action to be tested.</li>
<li><strong>Then (verification)</strong>: Verifies the outcome.</li>
</ul>
<p>Let’s refactor our original test to adhere to the Given-When-Then pattern:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">testVerification</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="c1">// Given</span>
<span class="nc">Customer</span> <span class="n">correctCustomer</span> <span class="o">=</span> <span class="nc">CustomerBuilder</span><span class="o">.</span><span class="na">create</span><span class="o">().</span><span class="na">build</span><span class="o">();</span>
<span class="nc">LoanOrder</span> <span class="n">loanOrderRequest</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LoanOrder</span><span class="o">(</span><span class="n">correctCustomer</span><span class="o">);</span>
<span class="nc">HttpPost</span> <span class="n">httpPost</span> <span class="o">=</span> <span class="n">createHttpPost</span><span class="o">(</span><span class="no">LOAN_ORDERS_URI</span><span class="o">,</span> <span class="n">loanOrderRequest</span><span class="o">);</span>
<span class="c1">// When</span>
<span class="nc">HttpResponse</span> <span class="n">postResponse</span> <span class="o">=</span> <span class="n">httpClient</span><span class="o">.</span><span class="na">execute</span><span class="o">(</span><span class="n">httpPost</span><span class="o">);</span>
<span class="c1">// Then</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">postResponse</span><span class="o">.</span><span class="na">getStatusLine</span><span class="o">().</span><span class="na">getStatusCode</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="mi">200</span><span class="o">);</span>
<span class="c1">// Extract loanOrderId from the response</span>
<span class="nc">String</span> <span class="n">loanOrderId</span> <span class="o">=</span> <span class="n">parseLoanOrderIdFromResponse</span><span class="o">(</span><span class="n">postResponse</span><span class="o">);</span>
<span class="c1">// When</span>
<span class="nc">HttpResponse</span> <span class="n">getResponse</span> <span class="o">=</span> <span class="n">executeHttpGet</span><span class="o">(</span><span class="no">LOAN_ORDERS_URI</span><span class="o">,</span> <span class="n">loanOrderId</span><span class="o">);</span>
<span class="c1">// Then</span>
<span class="nc">LoanOrder</span> <span class="n">loanOrder</span> <span class="o">=</span> <span class="n">parseLoanOrderFromResponse</span><span class="o">(</span><span class="n">getResponse</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getStatus</span><span class="o">()).</span><span class="na">isEqualTo</span><span class="o">(</span><span class="no">VERIFIED</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>In the realm of software testing, readability and clarity are crucial elements. By breaking down tests into smaller, well-named methods and adhering to the Given-When-Then pattern, we significantly improve the readability and maintenance of our code. This pattern is very effective in breaking down the test into clear, understandable stages. The test itself becomes more focused, making it easier to understand the functionality that’s being tested, the initial state, the operation being tested, and the expected outcome.</p>
<p>However, the <strong>Given-When-Then (Arrange-Act-Assert)</strong> approach is just one of many techniques we can use to improve our tests. Other testing patterns and methodologies can provide additional clarity and efficiency, and they are worth considering as part of a robust testing strategy. Here are a few worth mentioning:</p>
<ul>
<li>
<p><strong>Assertion Class</strong>: This involves creating a separate class to handle assertions. This makes tests easier to read and allows for better reuse of common assertions.</p>
</li>
<li>
<p><strong>Fixture Setup</strong>: A fixed setup (or ‘fixture’) is a context where tests run. This can be anything from input data to a mock object or a test double. Clear and consistent setup of fixtures can make tests more reliable and easier to understand.</p>
</li>
<li>
<p><strong>Custom Assertions</strong>: Developing custom assertions for your tests can improve readability and reduce code duplication. This might involve creating a method that takes in parameters and performs an assertion based on those parameters.</p>
</li>
<li>
<p><strong>Data-Driven Testing</strong>: This pattern involves parameterizing your tests to run them with different inputs, reducing code duplication and making your tests more flexible and comprehensive.</p>
</li>
</ul>
<p>By incorporating these practices, along with the Given-When-Then pattern, we can make our test suite more efficient, effective, and manageable as part of our development process. The key is to consider the specific needs and challenges of our testing environment and to apply the methods and patterns that provide the most benefit in our context.</p>
<h3 id="following-consistent-naming-conventions-in-your-project">Following Consistent Naming Conventions in Your Project</h3>
<p>Adhering to a consistent naming convention across your testing suite is a crucial aspect of maintaining clean and comprehensible code. This practice makes it easier for developers to understand the purpose of a test at a glance and allows for more efficient navigation through the test suite.</p>
<p>Let’s consider the naming of test methods. A good test name should clearly articulate what functionality is being tested and the expected outcome. One common convention is to structure the test name like so: <strong>‘methodName_Condition_ExpectedBehavior’</strong>.</p>
<p>For example, if you are testing the <strong>‘withdraw’</strong> method in a <strong>‘BankAccount’</strong> class, a test might be named <strong>‘withdraw_InsufficientFunds_ThrowsException’</strong>. This clearly communicates that the <strong>‘withdraw’</strong> method, when dealing with a condition of insufficient funds, is expected to throw an exception.</p>
<p>A consistent naming convention also applies to the organization of your test files. Tests should be grouped logically and clearly labeled so that it’s straightforward to find the tests related to a specific component. If you’re using a tool that supports it, consider structuring your tests to mirror the structure of your application code. This makes it even easier to locate the tests for a particular piece of functionality.</p>
<p>Maintaining consistent naming conventions isn’t just about cleanliness—it’s about communication. Clear, descriptive names make your tests more understandable to others (and your future self), making your codebase easier to maintain and evolve. By establishing and following these conventions in your project, you can create a more effective and efficient testing environment.</p>
<h3 id="avoiding-false-negatives-in-testing">Avoiding False Negatives in Testing</h3>
<p>While clarity in tests is key, it’s equally important to trust our tests. False negatives can lead to missed bugs and give a false sense of confidence. This is often due to poorly constructed tests or those that pass without the correct logic.</p>
<p>To counter this, we must ensure our tests pass only when the right logic is in place and all assertions hold true. This not only improves readability, but also enhances test reliability, reinforcing our confidence in the test results.</p>
<p>Consider the following example, where a test passes without any implementation:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleVerification</span> <span class="kd">implements</span> <span class="nc">Verification</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">passes</span><span class="o">(</span><span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// TODO</span>
<span class="c1">// use someLogicResolvingToBoolean(person);</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">someLogicResolvingToBoolean</span><span class="o">(</span><span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">UnsupportedOperationException</span><span class="o">(</span><span class="s">"Not yet implemented!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="c1">// Test that passes even without implementation</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldPassSimpleVerification</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Given</span>
<span class="nc">Customer</span> <span class="n">customer</span> <span class="o">=</span> <span class="n">buildCustomer</span><span class="o">();</span>
<span class="nc">CustomerVerifier</span> <span class="n">service</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CustomerVerifier</span><span class="o">(</span><span class="k">new</span> <span class="nc">TestVerificationService</span><span class="o">(),</span> <span class="n">buildSimpleVerification</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">TestBadServiceWrapper</span><span class="o">());</span>
<span class="c1">// When</span>
<span class="nc">CustomerVerificationResult</span> <span class="n">result</span> <span class="o">=</span> <span class="n">service</span><span class="o">.</span><span class="na">verifyPerson</span><span class="o">(</span><span class="n">customer</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">result</span><span class="o">.</span><span class="na">getStatus</span><span class="o">())</span>
<span class="o">.</span><span class="na">isEqualTo</span><span class="o">(</span><span class="nc">CustomerVerificationResult</span><span class="o">.</span><span class="na">Status</span><span class="o">.</span><span class="na">VERIFICATION_FAILED</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>In this example, the test shouldPassSimpleVerification() passes even without the implementation of the method someLogicResolvingToBoolean(). This leads to a false negative, as the test passes despite the lack of implementation.</p>
<p><strong>Solutions to Avoid False Negatives</strong></p>
<p>To avoid these kinds of false negatives, consider the following practices:</p>
<ul>
<li>
<p><strong>Check if the test works without your logic</strong>: Make sure your tests fail when the logic they’re testing is not implemented. This way, you ensure that the tests are indeed checking the correct logic.</p>
</li>
<li>
<p><strong>Reverse assertions to make sure the test fails</strong>: If you reverse your assertions and the test still passes, then the test is not effective. By ensuring the test fails when assertions are reversed, you confirm the test’s reliability.</p>
</li>
<li>
<p><strong>Use a build tool to check if tests are being run:</strong> Sometimes, tests may not run due to configuration issues, leading to a false sense of security. Using a build tool can help ensure that all tests are indeed running.</p>
</li>
</ul>
<p>By ensuring your tests are correctly designed and run as expected, you can minimize the risk of false negatives, increasing the reliability of your software testing practices.</p>
<h3 id="avoiding-high-cyclomatic-complexity">Avoiding High Cyclomatic Complexity</h3>
<p>Cyclomatic complexity is a software metric that quantifies the number of linearly independent paths through a program’s source code. In simpler terms, it measures how complex your code is, based on the number of different paths execution could take through it. High cyclomatic complexity means that there are many different paths through the code, which can make the code hard to understand, test, and maintain.</p>
<p>Let’s take a look at an example. Consider the following piece of code:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">,</span> <span class="nc">Order</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">user</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">order</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">order</span><span class="o">.</span><span class="na">isValid</span><span class="o">())</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"process_order"</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// Process the order...</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>This piece of code has a cyclomatic complexity of 4 because there are four potential paths through the code. This is not only hard to read, but also hard to test because we need to create tests for each of these paths.</p>
<p>We can reduce the cyclomatic complexity by inverting the conditions and returning early, like so:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="nc">User</span> <span class="n">user</span><span class="o">,</span> <span class="nc">Order</span> <span class="n">order</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">user</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">order</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="o">!</span><span class="n">order</span><span class="o">.</span><span class="na">isValid</span><span class="o">()</span> <span class="o">||</span> <span class="o">!</span><span class="n">user</span><span class="o">.</span><span class="na">hasPermission</span><span class="o">(</span><span class="s">"process_order"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// Process the order...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Now, the cyclomatic complexity is 2, making the code easier to understand and test.</p>
<p>It’s essential to keep cyclomatic complexity in check because it directly impacts the understandability, testability, and maintainability of your code. As a general rule, a cyclomatic complexity above 10 is considered high and should be avoided.</p>Mateusz DeskaMany teams face a common challenge after introducing the practice of writing tests: builds take up to half an hour, refactoring tests prove to be difficult, and testing consumes too much of the team’s time and energy. However, this problem can be solved by adhering to good testability practices.Key Types of Testing in Software Development2023-05-12T00:00:00+00:002023-05-12T00:00:00+00:00https://matthewonsoftware.com/testing/system%20quality/key-types-of-testing-in-software-development<p>Software testing is an integral part of the development process. Each type of testing has its specific purpose and helps ensure the highest quality of the final product. Here are the most crucial types of tests used in software development.</p>
<h3 id="unit-tests">Unit Tests</h3>
<p>Unit tests operate at the lowest level, targeting individual methods, classes, components, and modules without any integration with other modules or involving any database communication or simulated HTTP traffic. These are considered “small tests” according to Google’s scale and involve a single process.</p>
<p>Unit tests are quick to execute and involve simple objects, structures, stubs, and mocks, without the need for setting up and running application frameworks.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">verificationShouldPassForAgeBetween18And99</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// given</span>
<span class="nc">AgeVerification</span> <span class="n">verification</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AgeVerification</span><span class="o">(</span><span class="mi">22</span><span class="o">);</span>
<span class="c1">// when</span>
<span class="kt">boolean</span> <span class="n">passes</span> <span class="o">=</span> <span class="n">verification</span><span class="o">.</span><span class="na">passes</span><span class="o">();</span>
<span class="c1">// then</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">passes</span><span class="o">).</span><span class="na">isTrue</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="integration-tests">Integration Tests</h3>
<p>Integration tests are designed to verify the integration and interaction between different modules. They check whole groups of modules responsible for specific business functionalities. They validate the consistency of returned results concerning business requirements and the interaction between the individual modules within the group.</p>
<p>They also test integration with the infrastructure. According to Google’s scale, these tests are considered “medium tests” and are performed on a single machine.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldFailWithConnectionResetByPeer</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">WireMock</span><span class="o">.</span><span class="na">stubFor</span><span class="o">(</span><span class="nc">WireMock</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"/18210116954"</span><span class="o">)</span>
<span class="o">.</span><span class="na">willReturn</span><span class="o">(</span><span class="nc">WireMock</span><span class="o">.</span><span class="na">aResponse</span><span class="o">().</span><span class="na">withFault</span><span class="o">(</span><span class="nc">Fault</span><span class="o">.</span><span class="na">CONNECTION_RESET_BY_PEER</span><span class="o">)));</span>
<span class="nc">BDDAssertions</span><span class="o">.</span><span class="na">thenThrownBy</span><span class="o">(()</span> <span class="o">-></span> <span class="n">service</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">zbigniew</span><span class="o">()))</span>
<span class="o">.</span><span class="na">hasRootCauseInstanceOf</span><span class="o">(</span><span class="nc">IOException</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="end-to-end-tests">End-to-End Tests</h3>
<p>End-to-End tests simulate the complete flow of a given process in an application or distributed system from beginning to end. They are intended to mimic real-life scenarios of end-users and verify the correctness of entire business processes, data consistency, integration of different applications that are part of the system, and communication with external systems.</p>
<p>Google’s scale classifies these as “large tests” and they often require dedicated testing environments, databases, queues, and network protocols similar to those used in production.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldDisplayErrorMessage</span><span class="o">()</span> <span class="o">{</span>
<span class="err">$</span><span class="o">(</span><span class="n">byLinkText</span><span class="o">(</span><span class="s">"ERROR"</span><span class="o">)).</span><span class="na">click</span><span class="o">();</span>
<span class="err">$</span><span class="o">(</span><span class="n">byText</span><span class="o">(</span><span class="s">"Something happened..."</span><span class="o">)).</span><span class="na">shouldBe</span><span class="o">(</span><span class="nc">Condition</span><span class="o">.</span><span class="na">visible</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="manual-tests">Manual Tests</h3>
<p>Manual tests are performed by people who have a good understanding of the business side of a given system. They verify entire business processes and user scenarios in dedicated testing environments with data similar to production data.</p>
<h3 id="performance-tests">Performance Tests</h3>
<p>Performance tests verify the system’s performance under a given load. Specialized tools like JMeter are used for performance and load testing. The design of the load is based on the current and anticipated system load as well as key performance indicators (KPIs).</p>
<h3 id="user-testing">User Testing</h3>
<p>User tests are performed by potential end-users who carry out specific tasks or processes in the application. These tests are monitored by a UX specialist to identify problematic areas from a user experience perspective.</p>
<h3 id="conclusion">Conclusion</h3>
<p>Remember, each type of testing serves its purpose, and their collective use ensures the development of a robust, user-friendly, and efficient software system.</p>
<hr />
<h3 id="testing-approaches-verifying-results-checking-state-and-ensuring-communication">Testing Approaches: Verifying Results, Checking State, and Ensuring Communication</h3>
<p>In addition to the various types of testing, such as unit testing, integration testing, and end-to-end testing, there are also different testing approaches that can be employed in software development.</p>
<h3 id="verifying-results">Verifying Results</h3>
<p>One approach to testing involves verifying the results returned by a component after processing specific input data. This type of test focuses on the output of the code and does not require verifying the internal state of the component or any side effects it may have. By validating the returned result against expected values, we can ensure that the component behaves as intended.</p>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-12-key-types-of-testing-in-software-development/result-verfication.png" alt="img" /></p>
<p>Consider the following example:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldCreateStudentLoan</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">LoanOrderService</span> <span class="n">loanOrderService</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LoanOrderService</span><span class="o">();</span>
<span class="nc">Customer</span> <span class="n">student</span> <span class="o">=</span> <span class="n">aStudent</span><span class="o">();</span>
<span class="nc">LoanOrder</span> <span class="n">loanOrder</span> <span class="o">=</span> <span class="n">loanOrderService</span><span class="o">.</span><span class="na">studentLoanOrder</span><span class="o">(</span><span class="n">student</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getPromotions</span><span class="o">())</span>
<span class="o">.</span><span class="na">filteredOn</span><span class="o">(</span><span class="n">promotion</span> <span class="o">-></span> <span class="n">promotion</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="s">"Student Promo"</span><span class="o">))</span>
<span class="o">.</span><span class="na">size</span><span class="o">().</span><span class="na">isEqualTo</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>In this test, we create a student loan order using the LoanOrderService and verify that the resulting LoanOrder contains a promotion named “Student Promo.” By checking the size of the promotions list and filtering it based on the promotion name, we can assert the expected behavior of the code.</p>
<p>This approach to testing focuses on the effectiveness of the tests by directly comparing the actual output with the expected output. It promotes better architectural design as it encourages writing code that produces verifiable results.</p>
<h3 id="checking-state">Checking State</h3>
<p>Another testing approach involves verifying the state of the system after the completion of an operation. This type of test focuses on the state of the tested component, its collaborators, or external dependencies (e.g., in integration tests). By checking the state of relevant objects, we can ensure that the desired changes have occurred.</p>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-12-key-types-of-testing-in-software-development/state-verfications.png" alt="img" /></p>
<p>Consider the following example:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldAddManagerPromo</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">LoanOrder</span> <span class="n">loanOrder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LoanOrder</span><span class="o">(</span><span class="nc">LocalDate</span><span class="o">.</span><span class="na">now</span><span class="o">(),</span> <span class="n">aCustomer</span><span class="o">());</span>
<span class="no">UUID</span> <span class="n">managerUuid</span> <span class="o">=</span> <span class="no">UUID</span><span class="o">.</span><span class="na">randomUUID</span><span class="o">();</span>
<span class="n">loanOrder</span><span class="o">.</span><span class="na">addManagerDiscount</span><span class="o">(</span><span class="n">managerUuid</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getPromotions</span><span class="o">()).</span><span class="na">hasSize</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getPromotions</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getName</span><span class="o">())</span>
<span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">managerUuid</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span>
<span class="n">assertThat</span><span class="o">(</span><span class="n">loanOrder</span><span class="o">.</span><span class="na">getPromotions</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">).</span><span class="na">getDiscount</span><span class="o">())</span>
<span class="o">.</span><span class="na">isEqualTo</span><span class="o">(</span><span class="mi">50</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>In this test, we create a LoanOrder and add a manager discount using the addManagerDiscount method. We then verify that the promotions list has a size of 1, the name of the first promotion contains the manager’s UUID, and the discount value is equal to 50. By asserting the expected changes in the state of the LoanOrder object, we can ensure that the addManagerDiscount method works correctly.</p>
<p>This approach is useful when testing methods that return void and when it is necessary to verify the state of the system due to the existing architecture or design decisions.</p>
<h3 id="ensuring-communicationverification">Ensuring Communication/Verification</h3>
<p>The third testing approach involves verifying the communication between objects. This includes checking the messages sent to other objects or using mocks to verify interactions. By capturing and verifying the expected communication, we can ensure that the correct messages are being exchanged.</p>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-12-key-types-of-testing-in-software-development/interaction-verification.png" alt="img" /></p>
<p>Consider the following example:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">setUp</span><span class="o">()</span> <span class="o">{</span>
<span class="n">customer</span> <span class="o">=</span> <span class="n">buildCustomer</span><span class="o">();</span>
<span class="n">eventEmitter</span> <span class="o">=</span> <span class="n">mock</span><span class="o">(</span><span class="nc">EventEmitter</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">customerVerifier</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CustomerVerifier</span><span class="o">(</span><span class="n">buildVerifications</span><span class="o">(</span><span class="n">eventEmitter</span><span class="o">));</span>
<span class="nd">@Test</span>
<span class="kt">void</span> <span class="nf">shouldAddManagerPromo</span><span class="o">()</span> <span class="o">{</span>
<span class="n">customerVerifier</span><span class="o">.</span><span class="na">verify</span><span class="o">(</span><span class="n">customer</span><span class="o">);</span>
<span class="n">verify</span><span class="o">(</span><span class="n">eventEmitter</span><span class="o">,</span> <span class="n">times</span><span class="o">(</span><span class="mi">3</span><span class="o">)).</span><span class="na">emit</span><span class="o">(</span><span class="n">argThat</span><span class="o">(</span><span class="nl">VerificationEvent:</span><span class="o">:</span><span class="n">passed</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>Mateusz DeskaSoftware testing is an integral part of the development process. Each type of testing has its specific purpose and helps ensure the highest quality of the final product. Here are the most crucial types of tests used in software development.Polling And Streaming2023-05-11T00:00:00+00:002023-05-11T00:00:00+00:00https://matthewonsoftware.com/system%20design/polling-and-streaming<p>In computing systems, the interaction between clients and servers is fundamental. This interaction often revolves around the client sending requests, and the server responding with the requested data. The frequency at which this data is updated can vary, and this largely depends on the system’s requirements and purpose. Two fundamental concepts often used in this context are polling and streaming.</p>
<h3 id="polling">Polling</h3>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-11-polling-and-streaming/client-server-polling.png" alt="img" /></p>
<p>Polling is essentially the process of gathering data at regular intervals. For instance, a system may be set up to request temperature data every ‘X’ seconds. This method has its utility, but it is not without its limitations.</p>
<p>Consider an online chat platform such as Facebook or WhatsApp, where instant messaging is paramount. In such scenarios, receiving messages after a certain time interval is not ideal. You could theoretically decrease the polling interval to as low as 0.1 second to simulate real-time communication, but this approach leads to significant load on the servers.</p>
<p>For a single client, issuing 10 requests per second might seem manageable. However, when you scale this to thousands or potentially even millions of users, each issuing 10 requests per second, it results in a tremendous load on the server. That’s precisely where the concept of streaming becomes relevant.</p>
<h3 id="streaming">Streaming</h3>
<p><img src="https://matthewonsoftware.com/assets/blog_images/2023-05-11-polling-and-streaming/client-server-streaming.png" alt="img" /></p>
<p>In the context of networking, streaming generally refers to continuously receiving a feed of information from a server by maintaining an open connection between the client and the server.</p>
<p>Streaming is achieved via sockets, a fascinating subject worth learning more about. In simple terms, sockets are akin to files that your computer can read from and write to over an ongoing connection with another machine. This connection remains open until one of the machines terminates it.</p>
<p>In essence, polling and streaming are two different approaches to client-server communication. While polling is about periodic data requests, streaming involves maintaining an open connection for continuous data transfer. The choice between the two depends on the specific requirements and constraints of your system.</p>Mateusz DeskaIn computing systems, the interaction between clients and servers is fundamental. This interaction often revolves around the client sending requests, and the server responding with the requested data. The frequency at which this data is updated can vary, and this largely depends on the system’s requirements and purpose. Two fundamental concepts often used in this context are polling and streaming.The Economics of Software Testing2023-05-10T00:00:00+00:002023-05-10T00:00:00+00:00https://matthewonsoftware.com/testing/system%20quality/economy-of-testing<h3 id="the-importance-of-software-testing-in-business">The Importance of Software Testing in Business</h3>
<p>In the fast-paced world of software development, common challenges such as lack of time, management pressure, and tight schedules often prevent thorough software testing. Additionally, there is often a misunderstanding about the need for writing tests on the business side, leading to significant challenges.</p>
<p>A common misconception is that developers are paid to write functionality, not tests. This perspective is prevalent in many business environments, where the focus is often more on delivering features than ensuring their reliability.</p>
<h3 id="financial-arguments">Financial Arguments</h3>
<p>When it comes to convincing business stakeholders about the importance of testing, financial arguments often resonate the most. Efficient testing can lead to faster detection of a significant portion of errors. The sooner a mistake is found, the lower the cost of its repair.</p>
<p>The closer a bug is found to the developer’s machine, the cheaper it is to fix. Conversely, the further it goes into the deployment pipeline, the higher the cost of detecting and correcting the error becomes.</p>
<p>Research has shown that the detection of a problem in the maintenance phase generates costs 100 times greater than in the design phase. Although the difference is not as significant if the error is detected in the testing phase, the cost difference is still substantial.</p>
<h3 id="real-world-consequences">Real-World Consequences</h3>
<p>The real-world implications of not conducting thorough software testing can lead to significant financial losses for businesses.</p>
<ul>
<li>
<p><strong>Samsung Note 7:</strong> Due to battery issues, the company had to recall its product, resulting in a loss of around 17 billion dollars.</p>
</li>
<li>
<p><strong>Toyota:</strong> A software error in the braking system led to a loss of about 3 billion dollars.</p>
</li>
<li>
<p><strong>Knight Capital Inc:</strong> A trading glitch caused a loss of over 440 million dollars.</p>
</li>
<li>
<p><strong>GitLab:</strong> An accidental deletion of a production database led to significant downtime and recovery efforts. Here is the <a href="https://www.youtube.com/watch?v=tLdRBsuvVKc">video</a> with more details.</p>
</li>
</ul>
<h3 id="beyond-financial-costs-the-immeasurable-impact">Beyond Financial Costs: The Immeasurable Impact</h3>
<p>Beyond these financial consequences, it’s crucial to note that the fallout from software errors can also have immeasurable impacts. For instance, consumer trust in a brand, company, or product can be significantly damaged. Once lost, this trust can be challenging, if not impossible, to regain. This erosion of trust can lead to a long-term decline in customer base and overall market share, making the true cost of software errors even higher than the immediate financial loss suggests.</p>
<p>In addition to these financial benefits, implementing test cases allows tracking the progress of business requirement fulfillment. This can also facilitate easy generation of progress reports for business stakeholders, further establishing the importance of software testing in a business context.</p>
<h3 id="conclusion">Conclusion</h3>
<p>In conclusion, it’s not only beneficial for software developers to write tests, but it’s also a financially sound decision for businesses. The economics of software testing strongly suggests that time and resources allocated towards testing can save businesses from substantial costs in the long run, while also safeguarding their reputation and customer trust.</p>Mateusz DeskaThe Importance of Software Testing in BusinessWhy Are Tests Essential?2023-05-10T00:00:00+00:002023-05-10T00:00:00+00:00https://matthewonsoftware.com/testing/system%20quality/why-are-tests-essential-comprehensive-look-at-software-testing<p>Software testing is a fundamental aspect of any successful software development project. It is the process of examining a system or system component to determine whether it meets specified requirements. But why is it so important? Let’s delve into the reasons.</p>
<h3 id="ensuring-correct-functionality-and-handling-edge-cases">Ensuring Correct Functionality and Handling Edge Cases</h3>
<p>Software tests are a critical source of feedback. They answer two essential questions:</p>
<ul>
<li>Are all functionalities working correctly?</li>
<li>Will our software behave correctly with atypical data and edge cases?</li>
</ul>
<p>By addressing these questions, we can ensure the system’s efficiency and reliability. We can also ascertain that the software will handle unexpected scenarios effectively, reducing the likelihood of failure during real-world use.</p>
<h3 id="cost-effective">Cost-Effective</h3>
<p>Errors and system failures can be costly, and fixing them post-production can be even more expensive.</p>
<p>According to a report, the global cost of errors and system failures in IT was over 3.3 trillion dollars in 2002. By 2018, this figure had risen to 1.1 trillion dollars in the USA alone. By implementing effective testing strategies, we can reduce these costs substantially.</p>
<h3 id="facilitating-changes-safely-and-efficiently">Facilitating Changes Safely and Efficiently</h3>
<p>Testing also aids in the smooth and secure implementation of changes to the software. It minimizes regression, preventing the occurrence of errors in functionalities that previously worked correctly. Testing is particularly beneficial when new team members unfamiliar with the project or certain areas make changes. The testing phase can catch any unintentional errors or oversights, ensuring the software’s consistency and integrity.</p>
<h3 id="living-project-documentation">Living Project Documentation</h3>
<p>Another notable advantage of software testing is that it serves as a living document of the project. It provides insights into how the system actually works and can conveniently export reports for business use. Moreover, there’s potential for automatic generation of user documentation from the test code, adding to the project’s efficiency.</p>
<h3 id="fast-debugging-and-better-communication">Fast Debugging and Better Communication</h3>
<p>Testing enables quicker debugging of code snippets, eliminating the need to run the entire system or raise the context. This expedites the problem-solving process, allowing developers to focus on creating the best possible software. Furthermore, it facilitates better communication between developers. Instead of explaining business requirement nuances or subtle errors, developers can simply say, “Send me the test for this.”</p>
<h3 id="superior-application-and-api-design">Superior Application and API Design</h3>
<p>Good testing practices encourage better application and API design. They help limit the responsibilities of components and reduce unnecessary dependencies (decoupling), leading to more maintainable and scalable software architecture.</p>
<h3 id="accelerated-and-secure-deployments">Accelerated and Secure Deployments</h3>
<p>With robust testing in place, deployments become faster, safer, and can happen more frequently. This is because tests provide assurance that the system is behaving as expected, reducing the risk associated with deployments.</p>
<h3 id="legacy-projects">Legacy Projects</h3>
<p>In legacy projects, tests enable easy execution of isolated methods. This allows developers to better understand the existing system’s behavior and make changes or fixes more confidently.</p>
<p><br /></p>
<h3 id="conclusion">Conclusion</h3>
<p>In conclusion, software testing is indispensable in the modern software development cycle. It ensures functionality, saves costs, eases changes, and contributes to better project documentation and communication. With software becoming increasingly complex, the need for comprehensive and efficient testing will only continue to grow.</p>Mateusz DeskaSoftware testing is a fundamental aspect of any successful software development project. It is the process of examining a system or system component to determine whether it meets specified requirements. But why is it so important? Let’s delve into the reasons.Peer-To-Peer Networks2023-05-09T00:00:00+00:002023-05-09T00:00:00+00:00https://matthewonsoftware.com/system%20design/peer-to-peer-networks<h3 id="introduction-to-peer-to-peer-networks">Introduction to Peer-To-Peer Networks</h3>
<p>A Peer-To-Peer (P2P) network is a distributed system comprised of connected nodes, known as peers, that cooperate and share resources to achieve a common objective. These peers collectively distribute workloads amongst themselves, which often leads to faster and more efficient task completion as compared to traditional centralized systems. P2P networks are widely utilized in various applications, with file-sharing, content distribution, and decentralized storage being some of the most popular use cases.</p>
<h3 id="examples-of-peer-to-peer-network-usage">Examples of Peer-To-Peer Network Usage</h3>
<p>File-sharing and content distribution: One of the most famous examples of P2P networks is BitTorrent, a protocol used to share large files and content over the internet. In this system, users can download files from multiple peers, distributing the bandwidth and workload across the network, thus increasing the overall download speed and efficiency.</p>
<ul>
<li>
<p><strong>Distributed storage systems</strong>: P2P networks can be used for distributed storage solutions like InterPlanetary File System (IPFS). IPFS allows users to store and retrieve data in a decentralized manner, reducing reliance on centralized servers and improving data redundancy and resilience.</p>
</li>
<li>
<p><strong>Cryptocurrencies and blockchain</strong>: Blockchain networks like Bitcoin and Ethereum use P2P networks for the decentralized management and verification of transactions. Nodes in these networks maintain copies of the blockchain ledger and validate new transactions, eliminating the need for central authorities.</p>
</li>
<li>
<p><strong>Communication and messaging</strong>: P2P networks can be used to build secure and private messaging systems like the Signal Protocol, which is the foundation for encrypted messaging apps like Signal and WhatsApp. These systems enable end-to-end encryption and direct communication between peers without the need for a centralized server.</p>
</li>
</ul>
<h3 id="gossip-protocol-in-p2p-networks">Gossip Protocol in P2P Networks</h3>
<p>The Gossip Protocol, also known as epidemic protocol, is a communication mechanism used in P2P networks to effectively disseminate information across the network. In this protocol, each peer periodically exchanges information with its neighbors in a randomized and decentralized manner. The process of exchanging information, or gossip, helps maintain consistency and fault tolerance in the network.</p>
<h3 id="alternative-approaches-and-techniques-in-p2p-networks">Alternative Approaches and Techniques in P2P Networks</h3>
<p>Besides the Gossip Protocol, P2P networks can also implement alternative approaches for maintaining the knowledge of data location and network structure, such as Distributed Hash Tables (DHTs) and central trackers.</p>
<ul>
<li>
<p><strong>Distributed Hash Table (DHT)</strong>: A DHT is a decentralized data structure that allows nodes to efficiently store and retrieve data in a P2P network. In a DHT-based system, each peer is responsible for a portion of the hash table, storing and maintaining data associated with specific keys. Nodes can efficiently locate and retrieve data by querying the appropriate peer responsible for the required key. Examples of DHT-based P2P systems include Kademlia, which is used in the BitTorrent protocol, and the Distributed Hash Table used in Ethereum’s network.</p>
</li>
<li>
<p><strong>Central Tracker</strong>: In this approach, a central node keeps track of which peer contains specific data, and clients can query the central node to locate the desired data. This method is used in some BitTorrent implementations to maintain an up-to-date list of available peers for downloading a specific file.</p>
</li>
</ul>
<p>In summary, Peer-To-Peer networks are distributed systems that leverage the collective power of connected nodes to achieve a common goal. These networks utilize efficient communication mechanisms, such as the Gossip Protocol, as well as alternative approaches like Distributed Hash Tables and central trackers, to provide scalable, fault-tolerant, and decentralized solutions for various applications, including file-sharing, content distribution, and decentralized storage.</p>Mateusz DeskaIntroduction to Peer-To-Peer NetworksLeader Election2023-05-08T00:00:00+00:002023-05-08T00:00:00+00:00https://matthewonsoftware.com/system%20design/leader-election<p>In most societies, citizens elect their leaders through a voting system. Similarly, in a distributed system, servers utilize a set of algorithms to select a master node. This process, known as “leader election” or “algorithmocracy,” forms a critical part of many systems.</p>
<p>Let’s consider the design of a subscription-based product system like Netflix or Amazon Prime, where users can opt for monthly or annual plans. A specific service within this system is tasked with periodically collating data from the database and sending requests to third-party systems to charge customers whose subscriptions are nearing expiration. As charging a client must not be duplicated, we need to ensure it only happens once. This is where leader election comes into play.</p>
<p>Leader election allows a group of servers or machines, each capable of performing the same task, to select a leader among themselves. This elected leader is solely responsible for executing the desired action, thereby preventing task duplication.</p>
<p>To illustrate, if we have four servers handling the business logic between the database and the third-party payment service, they will elect a leader amongst themselves. The leader server will handle the business logic while the other three servers (followers) stand ready to step in if the leader encounters issues. If the leader fails, a new leader is elected from the remaining servers to take over.</p>
<h3 id="leader-election">Leader Election</h3>
<p>Leader election is the process through which nodes in a cluster (e.g., servers in a server set) elect a “leader” among them. This leader takes charge of the primary operations of the service these nodes support. When implemented correctly, leader election ensures all nodes in the cluster are aware of the current leader and can elect a new one should the leader fail.</p>
<h3 id="consensus-algorithm">Consensus Algorithm</h3>
<p>A consensus algorithm is a sophisticated type of algorithm that allows multiple entities to agree on a single data value. This could be used for determining the “leader” among a group of machines. Two popular consensus algorithms are Paxos and Raft.</p>
<h3 id="paxos--raft">Paxos & Raft</h3>
<p>Paxos and Raft are consensus algorithms that, when properly implemented, enable synchronization of specific operations, even in a distributed setting. These algorithms are essential for ensuring that distributed systems maintain consistency and agree on certain values or decisions, such as electing a leader.</p>
<p>Notable examples of systems that implement these consensus algorithms include Apache ZooKeeper and etcd.</p>
<p>Apache ZooKeeper utilizes a variation of the Paxos algorithm known as Zab (ZooKeeper Atomic Broadcast), enabling it to maintain strong consistency across distributed deployments. It provides an infrastructure for cross-node synchronization and is widely used by high-scale distributed applications.</p>
<p>On the other hand, etcd is a distributed key-value store that uses the Raft consensus algorithm. It provides a reliable way to store data across a cluster of machines and is used extensively in the Kubernetes container orchestration system to store all cluster data.</p>
<p>Both systems illustrate the practical implementation of consensus algorithms like Paxos and Raft in real-world distributed systems.</p>Mateusz DeskaIn most societies, citizens elect their leaders through a voting system. Similarly, in a distributed system, servers utilize a set of algorithms to select a master node. This process, known as “leader election” or “algorithmocracy,” forms a critical part of many systems.