<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Olorondu Chukwuemeka</title>
    <description>The latest articles on DEV Community by Olorondu Chukwuemeka (@oloronduemeka).</description>
    <link>https://dev.to/oloronduemeka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F180406%2F7a2d9335-f778-4009-ba6e-770a2497816c.jpg</url>
      <title>DEV Community: Olorondu Chukwuemeka</title>
      <link>https://dev.to/oloronduemeka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oloronduemeka"/>
    <language>en</language>
    <item>
      <title>Adapter Design Pattern: A Guide to Manage Multiple Third-Party Integrations</title>
      <dc:creator>Olorondu Chukwuemeka</dc:creator>
      <pubDate>Thu, 02 Nov 2023 01:07:52 +0000</pubDate>
      <link>https://dev.to/oloronduemeka/adapter-design-pattern-a-guide-to-manage-multiple-third-party-integrations-5dkc</link>
      <guid>https://dev.to/oloronduemeka/adapter-design-pattern-a-guide-to-manage-multiple-third-party-integrations-5dkc</guid>
      <description>&lt;p&gt;&lt;strong&gt;TABLE OF CONTENTS&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
Introduction

&lt;ul&gt;
&lt;li&gt; Introducing the Adapter Pattern
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt; Managing Multiple Third-Party Integrations
&lt;/li&gt;
&lt;li&gt; Implementing the Adapter Pattern: A Practical Example

&lt;ul&gt;
&lt;li&gt; Defining a common interface&lt;/li&gt;
&lt;li&gt; Creating the Adapter class
&lt;/li&gt;
&lt;li&gt; Implement custom logic for switching
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt; Benefits
&lt;/li&gt;
&lt;li&gt; Challenges and Considerations
&lt;/li&gt;
&lt;li&gt; Real-World Applications
&lt;/li&gt;
&lt;li&gt; Conclusion
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Imagine a hectic day at the office. It's lunchtime, and after spending the first three hours in a lengthy and exhausting meeting, you're eager to order food from your favorite vendor. Instinctively, you navigate to the order history and repeat your previous order from yesterday, as you're not in the mood for a culinary adventure. Upon checkout, you select your saved card and click the familiar "Place Order" button, not waiting to see the outcome because you subconsciously expect your order to be placed without any issues. After 10 minutes, you realize that you haven't received the usual push notification confirming your order. You unlock your phone and see an error banner that reads, "Unable to process payment. Please retry in a few minutes." You can't believe your eyes. You retry a couple of times, but nothing changes. Frustrated, you close the app and download a competitor's app because you refuse to go hungry due to a failed payment.&lt;/p&gt;

&lt;p&gt;Believe it or not, scenarios like this are quite common across various technology sectors, including failed payments, unsuccessful email or text message deliveries, and the like. As you can infer from the experience described above, customers don't care about service downtimes from your third-party providers. They simply want the app to work seamlessly when they need to use it. Anything less than that compromises the user experience.&lt;/p&gt;

&lt;p&gt;To prevent scenarios like these, a typical approach involves integrating multiple providers that offer the same service. One provider acts as the primary source, while the others serve as backup providers. This setup allows for seamless real-time switching between providers in the event of downtime from one of them.&lt;/p&gt;

&lt;p&gt;Ideally, you would want to implement a solution that accomplishes this objective in a scalable and efficient manner. How can we achieve this?&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing the Adapter Pattern
&lt;/h3&gt;

&lt;p&gt;The Adapter Pattern is a structural design pattern used in software development to enable the interface of one class to work with another interface that would otherwise be incompatible. It is advantageous when you have existing classes or components with different interfaces that need to cooperate. The Adapter Pattern serves as a bridge between these two interfaces, allowing them to collaborate seamlessly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Multiple Third-Party Integrations
&lt;/h2&gt;

&lt;p&gt;It's not uncommon for applications to perform specific functions by integrating with third-party systems through APIs and SDKs. However, a drawback of this approach is that the part of the system relying on third-party integration becomes tightly coupled with it. This means that if the third-party system experiences downtime, the corresponding functionality will be unavailable to customers.&lt;/p&gt;

&lt;p&gt;One way to mitigate the impact of potential downtimes from third-party providers is to have backup options in the form of multiple integrations for similar features. Nevertheless, managing several third-party providers can be a challenging task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Adapter Pattern: A Practical Example
&lt;/h2&gt;

&lt;p&gt;Consider a hypothetical e-commerce company, TrendyWears , which needs to send emails to its users triggered by various actions such as sign-up, email verification, transactions, and orders. For this example, we can make the following assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the past, &lt;code&gt;TrendyWears&lt;/code&gt; integrated a single email provider that experienced downtimes during critical business periods (flash sales, Black Friday, New Year, etc.).&lt;/li&gt;
&lt;li&gt;Having faced disappointment, they decided to integrate three email providers to seamlessly switch between them in real-time.&lt;/li&gt;
&lt;li&gt;The company has an internal service for managing various providers for different functions (messaging, payments, logistics, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let's see how it all fits together. &lt;/p&gt;

&lt;p&gt;In this example, we will be using TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defining a common interface
&lt;/h3&gt;

&lt;p&gt;The first step in implementing the Adapter pattern involves defining a common interface. We can establish a unified &lt;code&gt;Notifications&lt;/code&gt; interface, which includes a single &lt;code&gt;sendEmail&lt;/code&gt; method for sending emails as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Notifications interface establishes a contract to which the Adapter class adheres.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the Adapter class
&lt;/h3&gt;

&lt;p&gt;Recall that we mentioned earlier that the company &lt;code&gt;TrendyWears&lt;/code&gt; integrated three fictitious email providers. To ensure consistency, we can have these email providers adhere to the same contract as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sample email metadata&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;EmailMetadta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;email_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// mailer contract&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Mailer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mailMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailMetadata&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;fetchDeliveryLog&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EmailClientA&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Mailer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;mailMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// external API call to service provider A&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// purely for demonstration purpose&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;fetchDeliveryLog&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EmailClientB&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Mailer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;mailMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// external API call to service provider B&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;EmailClientC&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Mailer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;mailMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// external API call to service provider C&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we create the initial structure of the Adapter class as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Notificator&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmaiClientA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailCLientB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// we'll develop this in a bit&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Implement custom logic for switching
&lt;/h3&gt;

&lt;p&gt;Next, we will implement custom logic for switching between email clients. To maintain simplicity, we will base our implementation on the following assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a simple retry method for retrying failed attempts, as follows:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// custom logic for retry&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The method for switching between providers utilizes a straightforward round-robin approach for n consecutive failed email delivery attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can implement a simple switching logic as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Notificator&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmaiClientA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailCLientB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;currentProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EmailClientC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientA&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mailClientC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mailClientC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// simple hard-coded provider queue&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailClientA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailClientB&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mailClientC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// default provider&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// these should be stored as env variables&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;15ms&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;mailMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// switch to next provider in even intervals&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isEvenAttempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isEvenAttempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resetCurrentProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// trigger retry&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;resetCurrentProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;    
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the above example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a queue of &lt;code&gt;providers&lt;/code&gt;, which are references to each of the email clients. In a production app, this can be persisted in an in-memory store like Redis.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;currentProvider&lt;/code&gt; variable keeps track of the current email client reference, and this variable is reset after every failed event attempt.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;sendEmail&lt;/code&gt; method is retried for a maximum of 6 attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Notificator&lt;/code&gt; class effectively serves as a bridge between the sendEmail function and various email clients, while also providing an abstraction for dynamically switching between email clients in real time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;As demonstrated in the previous example, the adapter pattern offers several benefits to software systems, some of which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliability:&lt;/strong&gt; The system becomes more dependable as it can seamlessly switch between third-party providers, ensuring uninterrupted service even if one provider encounters issues or downtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of Concerns:&lt;/strong&gt; The Adapter pattern aids in maintaining a clear separation of concerns. Other components of the system that rely on the feature interface don't need to be aware of the intricacies of individual third-party integrations. Instead, they interact with the feature through a common interface, making the system more modular and maintainable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service-Level Agreements (SLAs):&lt;/strong&gt; Businesses can more effectively meet their service-level agreements (SLAs) with customers, as the Adapter pattern allows them to adapt to changing conditions and ensure that commitments are fulfilled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges and Considerations
&lt;/h2&gt;

&lt;p&gt;While the Adapter pattern offers numerous benefits, managing multiple third-party integrations and their respective implementations can become increasingly challenging.&lt;/p&gt;

&lt;p&gt;As with any decision in software engineering, trade-offs must be made between managing these integrations within a service class inside the system or creating a dedicated integration service for this purpose, while considering the additional costs involved. The choice depends on the number of integrations and the specific needs of the business.&lt;/p&gt;

&lt;p&gt;Adapting this concept to our hypothetical &lt;code&gt;TrendyWears&lt;/code&gt; company, we could create a separate &lt;code&gt;trendywears-integrations&lt;/code&gt; service whose sole responsibility is managing multiple integrations for the company, spanning various domains such as messaging (email and/or push notifications), payments, logistics, and more. For the messaging integrations mentioned in our example, we could expose functionalities as APIs, allowing other services within the system (such as orders, payments, etc.) to interact with them. These include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;sendEmail&lt;/code&gt; API for sending emails: Services interacting with this API remain unaware of the providers being used.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;getCurrentProvider&lt;/code&gt; API: This could be helpful for client-facing applications (web/mobile) to determine the appropriate SDKs and implement routing logic.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;resetProvider&lt;/code&gt; API for resetting the current provider: Typically, this is called after n consecutive failed attempts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;p&gt;The Adapter pattern has several real-world applications related to managing multiple third-party integrations, some of which include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Messaging (email and/or push notifications)&lt;/li&gt;
&lt;li&gt;Cloud services (switching between multiple cloud providers, cloud storage, etc.)&lt;/li&gt;
&lt;li&gt;Payments (supporting multiple payment providers)&lt;/li&gt;
&lt;li&gt;API versioning&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The power of the Adapter Pattern lies in its ability to enhance system reliability, separate concerns among components, and adapt to changing conditions. It is a tool that empowers businesses to meet their commitments, even in the face of a dynamic and uncertain technological landscape. By understanding and effectively applying the Adapter Pattern, software engineers can unlock new dimensions of robustness and flexibility in their systems, ultimately delivering a more reliable and adaptable experience to users and businesses.&lt;/p&gt;

</description>
      <category>designpatterns</category>
      <category>architecture</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to setup an automated testing pipeline with Codecov and GitHub Actions.</title>
      <dc:creator>Olorondu Chukwuemeka</dc:creator>
      <pubDate>Sun, 26 Sep 2021 16:36:01 +0000</pubDate>
      <link>https://dev.to/oloronduemeka/how-to-setup-an-automated-testing-pipeline-with-codecov-and-github-actions-7h3</link>
      <guid>https://dev.to/oloronduemeka/how-to-setup-an-automated-testing-pipeline-with-codecov-and-github-actions-7h3</guid>
      <description>&lt;h2&gt;
  
  
  Outline
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Scope of the tutorial &lt;/li&gt;
&lt;li&gt;  Introduction &lt;/li&gt;
&lt;li&gt; Prerequisites (optional) &lt;/li&gt;
&lt;li&gt; Code coverage with Codecov &lt;/li&gt;
&lt;li&gt; Project Setup &lt;/li&gt;
&lt;li&gt; Codecov &amp;amp; GitHub Actions &lt;/li&gt;
&lt;li&gt; Codecov in action &lt;/li&gt;
&lt;li&gt; Metadata (badges) &lt;/li&gt;
&lt;li&gt; Conclusion &lt;/li&gt;
&lt;li&gt; Helpful links &lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Scope of the tutorial
&lt;/h2&gt;

&lt;p&gt;This tutorial teaches how to integrate unit testing into a development workflow with Github Actions &amp;amp; Codecov.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-scope:&lt;/strong&gt; This tutorial does not teach about unit testing with PHP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Unit testing in software engineering is a development practice in which individual components (functions, modules, classes, etc) of a software system are tested independently as single units to validate that they function as they're intended to by the software engineer. This practice is very important because it safeguards the software against future breaking changes by acting as an "alerting system" of some sort that notifies the software engineers involved, once the tests are executed. Usually, unit testing is combined with other forms of automated testing such as integration testing, acceptance testing, etc.&lt;/p&gt;

&lt;p&gt;Automated testing, in general, is very important, but it's also crucial that tests are written to account for all (or most) possible edge cases that exist. Hence, we are faced with an important question: how do we measure this metric?&lt;/p&gt;

&lt;p&gt;In this tutorial, we will learn about code coverage and how to set up an automated testing pipeline with Codecov &amp;amp; GitHub Actions, using PHP for demonstration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites (optional)
&lt;/h2&gt;

&lt;p&gt;The following prerequisites are &lt;strong&gt;optional&lt;/strong&gt; and not required to understand this tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge of PHP and automated testing with PHPUnit.&lt;/li&gt;
&lt;li&gt;PHP, MySQL &amp;amp; XDebug installed locally.&lt;/li&gt;
&lt;li&gt;Composer installed locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code coverage with Codecov
&lt;/h2&gt;

&lt;p&gt;Code coverage in software engineering is an automated testing metric that measures the percentage of lines of code that are covered by the written tests. This gives an insight into the extent to which your codebase is tested. Code coverage is usually determined by a number of criteria such as function coverage, edge coverage, statement coverage, and the likes (helpful link at the end of this tutorial).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://about.codecov.io/"&gt;Codecov&lt;/a&gt;  is a tool that helps to measure code coverage and automatically generates reports based on the result obtained. It comes with features that help to enforce a minimum code coverage percentage in your project and can be integrated with a number of Continuous Integration (CI) tools. &lt;/p&gt;

&lt;p&gt;In the sections below, we would learn how to set up Codecov in a software development project using GitHub Actions as our CI tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;p&gt;The sample project is a simple API built with vanilla PHP and is framework agnostic. Clone the project from  &lt;a href="https://github.com/olorondu-emeka/php_crud_app"&gt;this repo&lt;/a&gt; and follow the guidelines detailed in the Readme file for the local development setup.&lt;/p&gt;

&lt;p&gt;The structure of the project is as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php_crud_app
├─ .env
├─ .github
│  ├─ PULL_REQUEST_TEMPLATE.md
│  └─ workflows
│     └─ test.yml
├─ .gitignore
├─ api
│  ├─ AlternativeUserRepository.php
│  ├─ DbConnection.php
│  ├─ UserController.php
│  └─ UserRepository.php
├─ bootstrap.php
├─ codecov.yml
├─ composer.json
├─ composer.lock
├─ envLoader.php
├─ phpunit.xml
├─ public
│  └─ index.php
├─ README.md
├─ setup.sql
└─ tests
   └─ Feature
      └─ UserTest.php

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Codecov &amp;amp; GitHub Actions
&lt;/h2&gt;

&lt;p&gt;In order to setup Codecov with GitHub Actions, the following secret tokens are required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Personal Access Token (PAT):&lt;/strong&gt; This is a secret token unique to every GitHub account, which allows its owners to perform actions that require authentication such as repository-actions (clone, push &amp;amp; pull), setting up C pipelines with GitHub Actions, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codecov token:&lt;/strong&gt; This is required for granting GitHub Actions access to upload the coverage metrics to Codecov.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a GitHub PAT
&lt;/h3&gt;

&lt;p&gt;To create a personal access token for GitHub, follow the steps below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Login to your GitHub account and click on "settings"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y7Izm6Ov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068547322/Hho25TK7L.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y7Izm6Ov--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068547322/Hho25TK7L.png" alt="1_github_profile.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Click on Developer settings&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bruj7O8l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070842016/JEAs-eckbu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bruj7O8l--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070842016/JEAs-eckbu.png" alt="2_developer_settings.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Click on Personal access tokens&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ahjqLvzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068733253/ud709_Rxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ahjqLvzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068733253/ud709_Rxv.png" alt="3_pat.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; If you haven't created a PAT before, click on 'Create token'. Else, choose an existing PAT and click on "Edit token".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5:&lt;/strong&gt; Select the checkbox labeled "workflows" and click on "Update token" &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HWtOzL7d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068983015/G9uCNa7V7A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HWtOzL7d--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632068983015/G9uCNa7V7A.png" alt="4_edit-personal-access-token.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it. Ensure you save the PAT in a secure location and do not expose it to anyone. &lt;br&gt;
Next, we will set up Codecov.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up Codecov for the project
&lt;/h3&gt;

&lt;p&gt;In order to setup Codecov for the project, follow the steps below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Log in to  &lt;a href="https://about.codecov.io/"&gt;Codecov&lt;/a&gt;  with your GitHub account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; On the Codecov tab, click on "Analytics" and click on "Add new repository"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5Onnl1RW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632069568101/tidUUpbQ3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5Onnl1RW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632069568101/tidUUpbQ3.png" alt="7_add_new_codecov_repo.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Scroll down to the repo of your choice and click on "setup repo"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0KIaP0IQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632069642620/lJ32aBVd1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0KIaP0IQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632069642620/lJ32aBVd1.png" alt="9_setup_codecov_repo.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4:&lt;/strong&gt; Copy the "Upload token" and keep it safe. We would use it soon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1vtk4QYH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632090157631/E6CTPMAME.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1vtk4QYH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632090157631/E6CTPMAME.png" alt="10_codecov_upload_token.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating repo secrets
&lt;/h3&gt;

&lt;p&gt;We need to save the GitHub PAT and Codecov token earlier generated as repo secrets in order for GitHub Actions to have access to it.&lt;/p&gt;

&lt;p&gt;To create a new secret for the repo,  follow the steps below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Navigate to the target repo on GitHub and click on the Settings tab&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Click on "New repository secret"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_73pSjqL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070234892/EXcRODD0w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_73pSjqL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070234892/EXcRODD0w.png" alt="12_create_new_repo_secret.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3:&lt;/strong&gt; Name the GitHub PAT as "MY_PAT", paste its value, and click on "Add secret" to save. Follow the same steps for "CODECOV_TOKEN".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M6sZD1Ct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070571060/fuHZ2x9Yq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M6sZD1Ct--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632070571060/fuHZ2x9Yq.png" alt="13_add_new_repo_secret.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting up an automated testing pipeline with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;Now that we've successfully generated the required PAT and Codecov tokens, we can set up the testing pipeline.&lt;/p&gt;

&lt;p&gt;In the root of your project folder, create the directory: &lt;code&gt;.github/workflows&lt;/code&gt;. Next, create a file in the &lt;code&gt;workflows&lt;/code&gt; directory called &lt;strong&gt;test.yml&lt;/strong&gt;. Copy the contents below and paste them into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test Suite&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-18.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout to code repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MY_PAT }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup PHP with Xdebug&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;shivammathur/setup-php@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;php-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7.4"&lt;/span&gt;
          &lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xdebug&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update dependencies in lock file&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer update&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;composer test&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload Code Coverage to Codecov&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov/codecov-action@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.CODECOV_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./coverage.xml&lt;/span&gt;
          &lt;span class="na"&gt;flags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unit_tests&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codecov-umbrella&lt;/span&gt;
          &lt;span class="na"&gt;fail_ci_if_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;verbose&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try to break down critical aspects of the &lt;code&gt;test.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;on "push":&lt;/strong&gt; This line instructs GitHub Action to run every time a commit is pushed to the repo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setup PHP with XDebug:&lt;/strong&gt; This section installs PHP with  &lt;a href="https://xdebug.org/"&gt;XDebug&lt;/a&gt;, which is a PHP extension that is critical to generating the coverage report file labeled "coverage.xml". For projects written in other programming languages, this section of the CI pipeline is usually where necessary setup for running the unit tests is done.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run tests:&lt;/strong&gt; This is the section in which the command required for running unit tests is inserted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload Code Coverage to Codecov:&lt;/strong&gt; This section uploads the coverage report to Codecov, which is generated after running unit tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Codecov config file
&lt;/h3&gt;

&lt;p&gt;The Codecov config file controls critical aspects of Codecov features to be applied in your project, which is available through the Codecov bot. &lt;/p&gt;

&lt;p&gt;In your project's root directory, create a file called &lt;strong&gt;codecov.yml&lt;/strong&gt; and past the content below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reach,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;diff,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;flags,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;files"&lt;/span&gt;
  &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;require_changes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;require_base&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
  &lt;span class="na"&gt;require_head&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;

&lt;span class="na"&gt;coverage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;70..100"&lt;/span&gt;
  &lt;span class="na"&gt;round&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;down&lt;/span&gt;
  &lt;span class="na"&gt;precision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# basic&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60%&lt;/span&gt;
        &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0%&lt;/span&gt;
        &lt;span class="c1"&gt;# advanced settings&lt;/span&gt;
        &lt;span class="na"&gt;if_ci_failed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
        &lt;span class="na"&gt;informational&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;only_pulls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config file controls important features such as pull request comment, target coverage percentage amongst others. For a more detailed understanding of this file, check out the &lt;a href="https://docs.codecov.com/docs/common-recipe-list"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codecov in action.
&lt;/h2&gt;

&lt;p&gt;For every pull request made, the GitHub Actions pipeline runs for every commit pushed to the repo, and a coverage report is commented by the Codecov bot, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P0Yb6kox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632083302951/C21Vxu5wG.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P0Yb6kox--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632083302951/C21Vxu5wG.png" alt="coverage_report_failed.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pipeline is currently failing because the overall code coverage for the project is &lt;strong&gt;0.00%&lt;/strong&gt;, and we set the minimum code coverage to be &lt;strong&gt;60%&lt;/strong&gt;, from the &lt;code&gt;codecov.yml&lt;/code&gt; file. This is a result of the lack of adequate tests in the project.&lt;/p&gt;

&lt;p&gt;Once adequate unit tests have been added to the project, and the minimum code coverage percentage is met, the pipeline passes as demonstrated below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UQ3Gv11h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632444100189/JFEpI96kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UQ3Gv11h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632444100189/JFEpI96kx.png" alt="codecov_comment.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how the summary looks like on the Codecov dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6ymW0Cd9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632444166042/oILK9l2Ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6ymW0Cd9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632444166042/oILK9l2Ca.png" alt="codecov_dashboard.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, we've successfully set up the unit testing pipeline for a project. &lt;/p&gt;

&lt;h2&gt;
  
  
  Metadata (badges)
&lt;/h2&gt;

&lt;p&gt;Displaying badges on your project's Readme is a good way to track metrics (code coverage, CI status) at a glance.&lt;br&gt;
Let's add badges for Codecov and GitHub Actions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Codecov badge
&lt;/h3&gt;

&lt;p&gt;To add a Codecov badge, visit the link:  &lt;code&gt;https://codecov.io/gh/&amp;lt;your-organisation&amp;gt;/&amp;lt;your-project&amp;gt;/settings/badge&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As an example, for the sample project in this tutorial, the link will be:  &lt;a href="https://codecov.io/gh/olorondu-emeka/php_crud_app/settings/badge"&gt;https://codecov.io/gh/olorondu-emeka/php_crud_app/settings/badge&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Copy the markdown link and paste it into your Readme file.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions badge
&lt;/h3&gt;

&lt;p&gt;Toadd a badge for GitHub Actions, follow this template: &lt;code&gt;![pipeline-name](https://github.com/&amp;lt;your-organisation&amp;gt;/&amp;lt;your-project&amp;gt;/actions/workflows/&amp;lt;pipeline-file&amp;gt;/badge.svg)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, the link for the GitHub badge for this project is:&lt;br&gt;
&lt;code&gt;![Github Actions](https://github.com/olorondu-emeka/php_crud_app/actions/workflows/test.yml/badge.svg)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The final result on the ReadMe file is as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FDZ9GNgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632446507704/qp86yzI2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FDZ9GNgH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1632446507704/qp86yzI2m.png" alt="updated_badges.png"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;So far, we've seen the importance of unit testing. Using a continuous integration tool (such as GitHub Actions) and Codecov is definitely an important step in ensuring code quality in any project, as the process is fully automated and feedback via metrics(badges and codecov comment) is displayed for every commit made.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helpful Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt; &lt;a href="https://docs.github.com/en/actions"&gt;Getting started with GitHub Actions&lt;/a&gt; &lt;/li&gt;
&lt;li&gt; &lt;a href="https://www.atlassian.com/continuous-delivery/software-testing/code-coverage"&gt;Introduction to code coverage&lt;/a&gt; &lt;/li&gt;
&lt;li&gt; &lt;a href="https://docs.codecov.com/docs"&gt;Codecov official docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>testing</category>
      <category>codequality</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
