<?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: sergio-sastre</title>
    <description>The latest articles on DEV Community by sergio-sastre (@sergiosastre).</description>
    <link>https://dev.to/sergiosastre</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%2F644852%2F7296c282-92d4-467d-ad46-8ef03d2aa14b.jpeg</url>
      <title>DEV Community: sergio-sastre</title>
      <link>https://dev.to/sergiosastre</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sergiosastre"/>
    <language>en</language>
    <item>
      <title>Better unit tests with Property-Based testing</title>
      <dc:creator>sergio-sastre</dc:creator>
      <pubDate>Sat, 14 Aug 2021 13:03:08 +0000</pubDate>
      <link>https://dev.to/sergiosastre/better-unit-tests-with-property-based-tests-10f</link>
      <guid>https://dev.to/sergiosastre/better-unit-tests-with-property-based-tests-10f</guid>
      <description>&lt;p&gt;In the previous post, we saw how &lt;em&gt;Parameterised Tests&lt;/em&gt; help write more focused, concise and scalable tests. However, Parameterised Tests belong to the so-called &lt;em&gt;Example-Based Tests&lt;/em&gt;: the tests run &lt;strong&gt;only with the input and expected values we choose to pass as examples&lt;/strong&gt;. And this arises the question: what if the tests would not pass with any other valid examples we have not picked?&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to the Password Validator Example
&lt;/h2&gt;

&lt;p&gt;One of the reasons to write tests is that they protect us against regression bugs. They serve as a tool to detect code changes that cause a feature that worked correctly to stop working. If that happens, the corresponding tests turn red and point out what got broken.&lt;/p&gt;

&lt;p&gt;Coming back to the PasswordValidator of my previous post, the final Parameterised Test looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;passwordValidator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;MinCharsValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsDigitValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsLowerCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;NoBlanksValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@DisplayName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PasswordValidator for invalid passwords"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ParameterizedTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"When password is \"{0}\", the error contains \"{1}\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@CsvSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"123456, no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"ABCDEF, no digits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"HELLO, no lower case letters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"1234A, contains less than 6 chars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"12 3 456, contains blanks"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testPasswordValidatorRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;expectedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;actualError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedError&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;Let's imagine now that you need to make some adjustments in the Validators. You finish it and push your code. However, It turns out that another teammate has also touched the Validators, and you are having some conflicts. You solve the conflicts and push the code again. Finally, your tests run green in your CI, so you are confident enough to merge your code.&lt;/p&gt;

&lt;p&gt;But, before that happening, your teammate reviews the code and requires you to do some code changes. He found out that the &lt;code&gt;ContainsUpperCaseLetterValidator&lt;/code&gt; implementation is incorrect.&lt;/p&gt;

&lt;p&gt;So you take a look...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;RequirementValidator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;keywordOnError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"no upper case letters"&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[a-z]"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRegex&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can see the code snippet of this broken validator &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/main/java/sergio/sastre/multiplying/quality/of/unittests/broken/BrokenValidators.kt"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes, it turned out that it now validates lower case (i.e. &lt;code&gt;"[a-z]"&lt;/code&gt;) instead of upper case chars (i.e. &lt;code&gt;"[A-Z]"&lt;/code&gt;). &lt;br&gt;
On the other hand, you feel confused. All tests ran green on the CI. What could be wrong? So you run the test again locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aSTq7WEr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624910414308/IwgJ-E_E5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aSTq7WEr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624910414308/IwgJ-E_E5.png" alt="All tests green.png" width="880" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still green. We know it should run red since we have detected the error in the implementation itself.&lt;br&gt;
But take a closer look at the &lt;code&gt;ContainsUpperCaseLetterValidator&lt;/code&gt; first &lt;em&gt;Example-based Test&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;There we input the password "123456", so &lt;code&gt;passwordValidator.validate("123456")&lt;/code&gt; should return an error message, not only for &lt;code&gt;ContainsUpperCaseValidator&lt;/code&gt;, but also for &lt;code&gt;ContainsLowerCaseValidator&lt;/code&gt; as well, since it does contain neither upper case nor lower case chars, just digits.&lt;/p&gt;

&lt;p&gt;In this case, we can fix the test (but not the implementation) by adding an extra example for &lt;code&gt;ContainsUpperCaseValidator&lt;/code&gt; containing a lower case char in the password input, for instance "a23456". In doing so, the test runs red, as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0H7rc3uz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624910495524/9RuUNyldo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0H7rc3uz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624910495524/9RuUNyldo.png" alt="Red test.png" width="880" height="283"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Click on  &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/parameterized/PasswordUnitTests.kt"&gt;this link&lt;/a&gt; to see the code of all the &lt;em&gt;Parameterised Tests&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This proves that choosing the right examples is very important to catch regression bugs, as well as tricky. How reliable is our test suite? How can we be sure that we have covered all the necessary cases with this extra example? what if we missed some edge cases? how many examples do we need to be confident enough?&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;em&gt;Property-Based Tests&lt;/em&gt; to the rescue
&lt;/h2&gt;

&lt;p&gt;Following the previous example, we could conclude that &lt;strong&gt;any password without at least 1 upper case char, must show the error message "does not contain upper case chars"&lt;/strong&gt;. This combination of preconditions and qualities that must be valid at every time is called &lt;em&gt;property&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What if instead of just providing concrete valid examples through &lt;em&gt;Parameterised Tests&lt;/em&gt;, we would also &lt;strong&gt;generate random valid values&lt;/strong&gt; for the precondition (no upper case chars) that must always fulfil the quality (shows "does not contain upper case chars" in the error message)? This is called &lt;em&gt;Property-Based Testing (PBT)&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;In my opinion, the most powerful PBT engine for JVM languages out there is &lt;em&gt;Jqwik&lt;/em&gt;. I find it much more flexible than &lt;em&gt;Kotest&lt;/em&gt; or &lt;em&gt;QuickUnit&lt;/em&gt; when it comes to writing custom value generators. Moreover, it provides unique features such as &lt;em&gt;collecting statistics&lt;/em&gt; for the generated inputs, what comes in handy to ensure that the generated random values are among those expected.&lt;br&gt;
Therefore, all the examples of this article will be written with &lt;em&gt;Jqwik&lt;/em&gt;. &lt;/p&gt;
&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Jqwik&lt;/em&gt; is very easy to configure and &lt;strong&gt;can run together with Junit4 and Junit5&lt;/strong&gt;. In an Android project you just need to add the following to your project gradle file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;testOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;unitTests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
             &lt;span class="n"&gt;useJUnitPlatform&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                  &lt;span class="c1"&gt;//this config supports Jqwik, Junit4 and Junit5&lt;/span&gt;
                  &lt;span class="n"&gt;includeEngines&lt;/span&gt; &lt;span class="s1"&gt;'jqwik'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'junit-jupiter'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'junit-vintage'&lt;/span&gt;
             &lt;span class="o"&gt;}&lt;/span&gt;
             &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'**/*Properties.class'&lt;/span&gt;
             &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'**/*Test.class'&lt;/span&gt;
             &lt;span class="n"&gt;include&lt;/span&gt; &lt;span class="s1"&gt;'**/*Tests.class'&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
     &lt;span class="o"&gt;...&lt;/span&gt;
     &lt;span class="n"&gt;testImplementation&lt;/span&gt; &lt;span class="s2"&gt;"net.jqwik:jqwik-api:1.5.1"&lt;/span&gt;
     &lt;span class="n"&gt;testImplementation&lt;/span&gt; &lt;span class="s2"&gt;"net.jqwik:jqwik-engine:1.5.1"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other java/jvm projects you can check the  &lt;a href="https://jqwik.net/docs/current/user-guide.html#gradle"&gt;&lt;em&gt;Jqwik&lt;/em&gt; doc&lt;/a&gt; .&lt;/p&gt;

&lt;h2&gt;
  
  
  Our first &lt;em&gt;Property-Based Test&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Property-Based Tests require to provide a Generator that creates the constrained values programatically or through an annotation.&lt;br&gt;
In &lt;em&gt;Jqwik&lt;/em&gt;, you can create a Generator with &lt;code&gt;@Provide&lt;/code&gt;, which must return a &lt;em&gt;Jqwik&lt;/em&gt; &lt;code&gt;Arbitrary&amp;lt;String&amp;gt;&lt;/code&gt; for our arbitrary password. &lt;br&gt;
Our generator, which must generate random strings &lt;strong&gt;without upper case chars&lt;/strong&gt;, is as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Provide&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;noUpperCase&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Arbitrary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="nc"&gt;Arbitraries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ascii&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[^A-Z]"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRegex&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;Nevertheless, this does not ensure us that the generated input is among the values we expect. As I mentioned before, one of the special features of &lt;em&gt;Jqwik&lt;/em&gt; is &lt;em&gt;collecting statistics&lt;/em&gt; for the generated inputs. In doing so, we ensure the correctness of the generated inputs. I strongly recommend to always add them to your &lt;em&gt;Property-Based Tests&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;According to our "strong password" rules, makes sense to watch for digits, as well as lower and upper case chars in our tests. The corresponding methods might look like this in &lt;em&gt;Jqwik&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;statsUpperCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?){&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;withUpperCase&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"[A-Z]"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toRegex&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nc"&gt;Statistics&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Upper case"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&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="n"&gt;withUpperCase&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s"&gt;"with upper case"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s"&gt;"without upper case"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;collectPasswordStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?){&lt;/span&gt;
    &lt;span class="nf"&gt;statsUpperCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;statsLowerCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;statsDigits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&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;blockquote&gt;
&lt;p&gt;See the full implementation of the stat collectors &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/pbt/StatCollectors.kt"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By adding statistics on the generated inputs you feel more confident about testing the right thing. The reports will contain those statistics as you'll see later.&lt;/p&gt;

&lt;p&gt;Once we have the generator and the statistics, our tests would look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"If password without upper case chars,"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"the error message contains 'no upper case letters'"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Property&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testPasswordValidatorRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"noUpperCase"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;collectPasswordStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;actualError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="nc"&gt;Truth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no upper case letters"&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;blockquote&gt;
&lt;p&gt;You can find the full test suite with PBT examples for all the validators as well as the random value generators &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/pbt/PasswordUnitTests.kt"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if we run it, we get a report similar to the one below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GpfMLGMg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624912071297/H2zuwhPe1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GpfMLGMg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624912071297/H2zuwhPe1.png" alt="Upper Case Error.png" width="880" height="110"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SlVeMUJT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624913625774/ogeWqfEGj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SlVeMUJT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1624913625774/ogeWqfEGj.png" alt="Jqwik report 2.png" width="880" height="617"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great! The test fails as expected, and 100% of the generated values are upper case free. As you can see, &lt;em&gt;Jqwik&lt;/em&gt; Generators take edge cases into account as well (more about  &lt;a href="https://jqwik.net/docs/current/user-guide.html#generation-of-edge-cases"&gt;edge cases here&lt;/a&gt; ) by default.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why &lt;strong&gt;flaky tests&lt;/strong&gt; is not an issue in PBT
&lt;/h2&gt;

&lt;p&gt;You might be thinking... the test fails, but the values are being generated randomly on every run... So if I run the test again, new values are generated, and could happen that the test does not fail for those new random values. Non-reproducible tests decrease reliability on your test suite and need to be avoided.&lt;/p&gt;

&lt;p&gt;But do not worry. &lt;br&gt;
First of all, &lt;em&gt;Jqwik&lt;/em&gt; &lt;strong&gt;reuses the same seed to generate random values until a green run&lt;/strong&gt; by default. &lt;br&gt;
Secondly, if you need to reproduce the tests with the same values &lt;strong&gt;at any time&lt;/strong&gt;, just add the seed shown in the previous report, at the very bottom, to the test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"If password without upper case chars,"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
       &lt;span class="s"&gt;"the error message contains 'no upper case letters'"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Property&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"-7293214509268013126"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testPasswordValidatorRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@ForAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"noUpperCase"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;collectPasswordStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;actualError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="nc"&gt;Truth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run the test continuously with the same values that made it fail.&lt;/p&gt;

&lt;p&gt;Do not forget to remove the seed once fixed. That way the generators will continue generating new random values on every run. In doing, so you might find out other values for which the implementation is incorrect.&lt;/p&gt;

&lt;p&gt;Congratulations, your tests have become &lt;strong&gt;more robust&lt;/strong&gt; to regression bugs! You are officially ready to catch'm all!&lt;/p&gt;

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

&lt;p&gt;Let's take a look at the Pros and Cons of &lt;em&gt;Property-Based tests&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Tests become more robust, and give you more confidence on your code.&lt;/li&gt;
&lt;li&gt;Tests are deterministic. It's possible to reuse the seed that made them fail, so that errors can be reproduced.&lt;/li&gt;
&lt;li&gt;Explores edge cases by default, which we might forget otherwise.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Some properties are hard to find, especially while initiating in PBT. You can take a look at this interesting post by Scott Wlaschin on &lt;a href="https://fsharpforfunandprofit.com/posts/property-based-testing-2/"&gt;how to find properties&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Property-Based Tests&lt;/em&gt; run more slowly since they execute 1000 times by default. However, most engines allow you to change that value if desired.&lt;/li&gt;
&lt;li&gt;Properties are not always sufficient to test correctness. Take a look at the wrong implementation of &lt;code&gt;String?.reversed()&lt;/code&gt; and its test below
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&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="c1"&gt;//forgot to reverse it?&lt;/span&gt;

&lt;span class="nd"&gt;@Label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Reversing a reversed String returns the original"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Property&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;reverseStringTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@ForAll&lt;/span&gt; &lt;span class="n"&gt;originalString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
       &lt;span class="n"&gt;originalString&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;originalString&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 test would pass though, giving us the false impression that the method functionality is correct. On the other hand, this is the kind of error that &lt;em&gt;Example-Based Tests&lt;/em&gt; spot quickly.&lt;/p&gt;

&lt;p&gt;Therefore, getting the best of both worlds with  a combination of &lt;em&gt;Example-Based&lt;/em&gt; and &lt;em&gt;Property-Based Tests&lt;/em&gt; work the best in most cases.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
All the code examples used in this article, as well as those of the first article of this series - &lt;a href="https://dev.to/sergiosastre/better-unit-tests-with-parameterized-tests-3hgo"&gt;Better Unit Tests with Parameterized Tests&lt;/a&gt; - are available under &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests"&gt;this repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
Did you like this post?&lt;br&gt;
You might also like to take a look at other articles I've written&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/sergiosastre/better-unit-tests-with-parameterized-tests-3hgo"&gt;Better unit tests with Parameterized testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/sergiosastre/hi-3k6i"&gt;Styling dynamic strings directly in Android xml&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;br&gt;
&lt;br&gt;
Cover photo by &lt;a href="https://unsplash.com/@brett_jordan?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Brett Jordan&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/letters-random?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Better unit tests with Parameterized testing</title>
      <dc:creator>sergio-sastre</dc:creator>
      <pubDate>Tue, 27 Jul 2021 17:25:23 +0000</pubDate>
      <link>https://dev.to/sergiosastre/better-unit-tests-with-parameterized-tests-3hgo</link>
      <guid>https://dev.to/sergiosastre/better-unit-tests-with-parameterized-tests-3hgo</guid>
      <description>&lt;p&gt;While writing &lt;em&gt;Unit Tests&lt;/em&gt;, we often verify that given an input, the output of the method under test is the one expected. When you execute the same test with a different set of inputs and expected outputs several times, you come up with &lt;em&gt;Data Driven Tests&lt;/em&gt; or &lt;em&gt;Parameterized Tests&lt;/em&gt;.&lt;br&gt;
In this first post I will explain how we can multiply not only the quantity, but also the quality of our tests with this methodology. The samples are in Junit5 and written in Kotlin but apply to Java as well.&lt;/p&gt;

&lt;p&gt;*To configure Junit5 in Android projects (which I strongly recommend at least for JVM tests), you can find a complete guide &lt;a href="https://www.lordcodes.com/articles/testing-on-android-using-junit-5"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  A common test case: a password validator
&lt;/h2&gt;

&lt;p&gt;Nowadays, most apps require the user to create an account with a "strong password". Let's assume we need to validate on the client side that the user provides a strong password, namely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contains at least 1 digit in lower case . Show "no digit" otherwise&lt;/li&gt;
&lt;li&gt;Contains at least 1 char in upper case. Show "no upper case letters" otherwise&lt;/li&gt;
&lt;li&gt;Contains at least 6 digits. Show "contains less than 6 chars" otherwise
Although you can take another approach to implement it, I've used a &lt;strong&gt;Composite pattern&lt;/strong&gt;: &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/main/java/sergio/sastre/multiplying/quality/of/unittests/PasswordValidator.kt"&gt;PasswordValidator (Composite)&lt;/a&gt; and &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/main/java/sergio/sastre/multiplying/quality/of/unittests/Validators.kt"&gt;Validators (Leaves)&lt;/a&gt; that accepts a &lt;em&gt;vararg&lt;/em&gt; of Validators (Leaves) as argument in its constructor, each value validating one single requirement.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In order to test that our Password validator works, we write a single test with an assert for each requirement as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;passwordValidatorTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;minCharsCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;passwordValidator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;MinCharsValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minCharsCount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;ContainsDigitValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1234A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains less than $minCharsCount chars"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ABCDEF"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no digits"&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;If you are not new to Unit Tests, I'm sure you have heard that &lt;strong&gt;you should strive to have only one assert per test&lt;/strong&gt;. In doing so, your tests are self-contained and clearly state what is being tested.&lt;/p&gt;

&lt;p&gt;Moreover, &lt;strong&gt;asserts fail hard&lt;/strong&gt; by default in Junit5. It means, whenever one assert fails, &lt;strong&gt;the test execution exits right away&lt;/strong&gt; with the corresponding error. The subsequent asserts in the tests will not run and therefore no further errors will be reported. &lt;em&gt;Soft Assertions&lt;/em&gt; would solve that problem though (&lt;em&gt;assertAll&lt;/em&gt; in Junit5), but it does not scale well in this case. Tests would continue becoming longer and longer.&lt;/p&gt;

&lt;p&gt;For instance, imagine that after a while, the requirements for a strong password get more strict and we also need to validated the following on password creation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contains at least 1 char in lower case. Show "no lower case letters" otherwise&lt;/li&gt;
&lt;li&gt;Contains no blanks. Show "contains blanks" otherwise
While adding the corresponding validators, our test becomes this:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;advancedPasswordValidatorTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;minCharsCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;passwordValidator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;MinCharsValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minCharsCount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;ContainsDigitValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;ContainsLowerCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nc"&gt;NoBlanksValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1234A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains less than $minCharsCount chars"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ABCDEF"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no digits"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HELLO"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no lower case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"12 3 456"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"contains blanks"&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;*You can find the complete set of initial and advanced strong password samples with multiple asserts &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/multipleasserts/PasswordUnitTests.kt"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At a glance, we notice the following:&lt;/p&gt;

&lt;p&gt;The test becomes longer: long code is generally more complicated, harder to read, and harder to maintain. The more requirements, the longer it becomes due to the extra asserts.&lt;br&gt;
The test becomes less focused: the more password requirements, the more asserts added. The more asserts, the harder to see what the test is actually validating.&lt;/p&gt;
&lt;h2&gt;
  
  
  One test per requirement:
&lt;/h2&gt;
&lt;h3&gt;
  
  
  brief and focused tests! ...but repetitive
&lt;/h3&gt;

&lt;p&gt;By creating one test per requirement we would solve both issues. Every test would assert only one thing. If a test fails, we know directly what is failing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;passwordValidator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;MinCharsValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsDigitValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsLowerCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;NoBlanksValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;passwordValidatorNoUpperCaseTest_showsError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123456"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;passwordValidatorNoLowerCaseTest_showsError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HELLO"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no lower case letters"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;passwordValidatorNoDigitsTest_showsError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ABCDEF"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no digits"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;*You can find the full set of tests with one assert &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/onetestperassert/PasswordUnitTests.kt"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;all tests look very repetitive&lt;/strong&gt;. If we add more requirements, the new tests are basically a &lt;strong&gt;copy-paste of any of the previous tests* but with different input and expected output values. They **do not scale well&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can do better. That's where &lt;em&gt;Parameterized Tests&lt;/em&gt; come in handy...&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameterized Tests:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  brief and focused tests! ...and also concise and scalable!
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Parameterized Tests&lt;/em&gt; enable us to &lt;strong&gt;execute a single test method multiple times with different parameters&lt;/strong&gt;. In Junit5, we can use the &lt;em&gt;@ParameterizedTest&lt;/em&gt; annotation for that purpose. In doing so, we can pass as input the password under test and its expected error message, solving the problems we were facing.&lt;/p&gt;

&lt;p&gt;And the resulting &lt;em&gt;Parameterized Test&lt;/em&gt; would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;passwordValidator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PasswordValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsUpperCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;MinCharsValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsDigitValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;ContainsLowerCaseLetterValidator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
     &lt;span class="nc"&gt;NoBlanksValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@DisplayName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PasswordValidator for invalid passwords"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@ParameterizedTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"When password is \"{0}\", the error contains \"{1}\""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@CsvSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"123456, no upper case letters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"ABCDEF, no digits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"HELLO, no lower case letters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"1234A, contains less than 6 chars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"12 3 456, contains blanks"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;testPasswordValidatorRight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;expectedError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;actualError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passwordValidator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;assertThat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actualError&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedError&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;*You can find the complete set of Parameterized Tests &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/tree/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/parameterized"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Apart from &lt;em&gt;@CsvSource&lt;/em&gt;, Junit5 comes with other annotations to provide inputs, such as &lt;em&gt;@CsvFileSource&lt;/em&gt;, &lt;em&gt;@EnumSource&lt;/em&gt; or even &lt;em&gt;@MethodSource&lt;/em&gt; for more complex input sets. You can take a deeper look at them &lt;a href="https://www.baeldung.com/parameterized-tests-junit-5"&gt;here&lt;/a&gt; to find which one fits your needs better.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And if we run the test, we get the following result:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1JZxV8h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1621164400758/vsQo2Yrhz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1JZxV8h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1621164400758/vsQo2Yrhz.png" alt="Android Studio output of Parameterized Tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, parameterizing your tests has the following advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Your tests are scalable&lt;/strong&gt;: every new requirement involves adding one new pair of input and expected output to the parameters we inject into the tests.
No more copy-pasting of tests. 
And what if you need to add an edge case you forgot? No need for extra asserts or extra tests. Again, simply add new data to your test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your tests are concise&lt;/strong&gt;: the names of the tests become more concise since they can include the value of the parameters. What tells more about what we are actually testing? What tells the sheer truth? &lt;em&gt;passwordValidatorNoDigitsTest_showsError&lt;/em&gt; or &lt;em&gt;When password is "ABCDEF", the error contains "no digits"&lt;/em&gt;?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With &lt;em&gt;Parameterized Tests&lt;/em&gt;, your tests are still &lt;strong&gt;brief&lt;/strong&gt; and &lt;strong&gt;focused&lt;/strong&gt; like before, but they are &lt;strong&gt;more concise&lt;/strong&gt; and &lt;strong&gt;scale better&lt;/strong&gt;! You have not only multiplied its quantity with ease, but also its quality!&lt;/p&gt;

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

&lt;p&gt;As we've seen in the samples, whenever you find yourself writing a test with multiple asserts, consider whether it makes sense to create one test per assert instead. That way your tests will be shorter, more focused and more readable.&lt;/p&gt;

&lt;p&gt;If after splitting the original tests into several, you find out that you can group the tests by extracting some common code into parameters, &lt;em&gt;Parameterized Tests&lt;/em&gt; will also help your tests &lt;strong&gt;scale much better&lt;/strong&gt;, while being &lt;strong&gt;brief&lt;/strong&gt; and &lt;strong&gt;focused&lt;/strong&gt;. They'll also become more concise by using its parameters to build the test name.&lt;/p&gt;

&lt;p&gt;However, do not fall into the trap of &lt;strong&gt;excessive generalization&lt;/strong&gt;. Do not write tests that take &lt;strong&gt;too many arguments as parameters&lt;/strong&gt;. If you end up with &lt;em&gt;Parameterized Tests&lt;/em&gt; whose names are too general or hard to understand, try to split them into separate &lt;em&gt;Parameterized Tests&lt;/em&gt; (or even simple tests) with less parameters. Take a look at how I grouped the &lt;em&gt;Parameterized Tests&lt;/em&gt; &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests/blob/master/app/src/test/java/sergio/sastre/multiplying/quality/of/unittests/parameterized/PasswordUnitTests.kt"&gt;here&lt;/a&gt; instead of merging all of them into one.&lt;/p&gt;

&lt;p&gt;Last but not least, remember that &lt;strong&gt;tests are also documentation&lt;/strong&gt;. Therefore, they need to be understandable, you never know who will have to fix those tests if they fail!&lt;/p&gt;

&lt;p&gt;*You can find the validators code and a more complete collection of the strong password test samples &lt;a href="https://github.com/sergio-sastre/Multiplying_the_quality_of_unit_tests"&gt;in this repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;In the following article of this series, we will explore &lt;strong&gt;Property-Based Tests&lt;/strong&gt; and how they can help us make our Password validator tests more robust. Already available on the link below!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/sergiosastre/better-unit-tests-with-property-based-tests-10f"&gt;Better Unit Tests with Property-Based Tests&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you liked this article, you might also like other articles I wrote in Dev.to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/sergiosastre/hi-3k6i"&gt;Styling dynamic strings directly in Android xml&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>unittest</category>
      <category>android</category>
      <category>kotlin</category>
      <category>testing</category>
    </item>
    <item>
      <title>Styling dynamic strings directly in Android xml
with HTML markup</title>
      <dc:creator>sergio-sastre</dc:creator>
      <pubDate>Fri, 09 Jul 2021 14:50:28 +0000</pubDate>
      <link>https://dev.to/sergiosastre/hi-3k6i</link>
      <guid>https://dev.to/sergiosastre/hi-3k6i</guid>
      <description>&lt;p&gt;In Android, Strings are one of the most common Objects when working with views that show plain text. While implementing user interfaces, it happens often that the text requires some styling. For styled Strings, we are required to use &lt;code&gt;CharSequences&lt;/code&gt; instead. Android supports some &lt;a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/Html.java#782" rel="noopener noreferrer"&gt;html tags&lt;/a&gt; out of the box, and those can be defined in the xml string resource, for instance&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lorem_ipsum"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   This is &lt;span class="nt"&gt;&amp;lt;font&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;red&lt;span class="nt"&gt;&amp;lt;/font&amp;gt;&lt;/span&gt; and this
   &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&amp;lt;i&amp;gt;&lt;/span&gt;bold and italic (nested); &lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;this just bold&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;,
   &lt;span class="nt"&gt;&amp;lt;u&amp;gt;&lt;/span&gt;underlined&lt;span class="nt"&gt;&amp;lt;/u&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and resolved into a &lt;code&gt;CharSequence&lt;/code&gt; by calling &lt;code&gt;context.getText(stringRes: Int)&lt;/code&gt; which takes care of all the supported HTML tags to style the text without us needing to do anything else&lt;/p&gt;

&lt;p&gt;However, sometimes we need to build a part of the string dynamically. In order to do this, we need to mark the dynamic parts in the xml with placeholders in the form of &lt;code&gt;%{digit}${type}&lt;/code&gt;, for instance &lt;code&gt;%1$s&lt;/code&gt; is a string passed as first vararg and &lt;code&gt;%2$d&lt;/code&gt; is a decimal number passed as second vararg in &lt;code&gt;String.format(text: String, vararg args: String)&lt;/code&gt;, which is called to resolve the placeholders.&lt;/p&gt;

&lt;p&gt;Nevertheless, things start getting complicated if we define &lt;em&gt;HTML tags together with dynamic placeholders&lt;/em&gt; in xml string resources, for instance something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lorem_ipsum"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   This is &lt;span class="nt"&gt;&amp;lt;font&lt;/span&gt; &lt;span class="na"&gt;color=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;red&lt;span class="nt"&gt;&amp;lt;/font&amp;gt;&lt;/span&gt; and this
   &lt;span class="nt"&gt;&amp;lt;b&amp;gt;&amp;lt;i&amp;gt;&lt;/span&gt;bold and italic (nested); &lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;this just bold&lt;span class="nt"&gt;&amp;lt;/b&amp;gt;&lt;/span&gt;, &lt;span class="nt"&gt;&amp;lt;u&amp;gt;&lt;/span&gt;underlined&lt;span class="nt"&gt;&amp;lt;/u&amp;gt;&lt;/span&gt;
   and here the placeholder = %1s
&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we take a look at the method signature of &lt;code&gt;String.format(text: String, vararg args: String)&lt;/code&gt;, its first argument requires a String instead of a &lt;code&gt;CharSequence&lt;/code&gt;. This means, the dynamic text placeholders will be correctly replaced, but our &lt;code&gt;CharSequence&lt;/code&gt; has to be converted to &lt;code&gt;String&lt;/code&gt;, throwing away its styling.&lt;/p&gt;

&lt;p&gt;In order to deal with HTML markup, Android provides HtmlCompat. It requires that the string resource encodes its opening unsafe characters, namely: &lt;code&gt;'&amp;lt;'&lt;/code&gt; , which becomes &lt;code&gt;'&amp;amp;lt;'&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lorem_ipsum"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   This is &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;font color="red"&amp;gt;red&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/font&amp;gt; 
   and this &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;b&amp;gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;i&amp;gt;bold and italic (nested); &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/i&amp;gt;this just bold&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/b&amp;gt;,
   &lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;u&amp;gt;underlined&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;/u&amp;gt; and here the placeholder = %1s
&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or alternatively, we can wrap the resource inside &lt;a href="https://developer.android.com/reference/org/w3c/dom/CDATASection" rel="noopener noreferrer"&gt;CDATASections&lt;/a&gt; instead to the xml as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;string&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"lorem_ipsum"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;![CDATA[This is &amp;lt;font color="red"&amp;gt;red&amp;lt;/font&amp;gt; and this &amp;lt;b&amp;gt;&amp;lt;i&amp;gt;bold and italic (nested); &amp;lt;/i&amp;gt;this just bold&amp;lt;/b&amp;gt;, &amp;lt;u&amp;gt;underlined&amp;lt;/u&amp;gt; and here the placeholder = %1s]]&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In any case, given our dynamic placeholder text to be "placeholder1", we can get the expected result by using &lt;em&gt;HtmlCompat&lt;/em&gt; as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"placeholder1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicStyledText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;dynamicText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FROM_HTML_MODE_COMPACT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamicStyledText&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618866510391%2FM4k07za1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618866510391%2FM4k07za1h.png" alt="Alphanumeric placeholder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the code above seems to work robustly, the result differs if the dynamic placeholder text contains at least one unescaped HTML character, for instance:&lt;code&gt;&amp;lt;&lt;/code&gt;, &lt;code&gt;&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;\&lt;/code&gt; or &lt;code&gt;"&lt;/code&gt;, like in &lt;code&gt;&amp;lt;placeholder1&amp;gt;&lt;/code&gt;, leading to the result below&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618864999291%2F-qc_fiWXO.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618864999291%2F-qc_fiWXO.png" alt="Placeholder containing '&amp;lt;'"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yes, the placeholder just disappears. That's because characters must be escaped before calling &lt;code&gt;HtmlCompat.fromHtml()&lt;/code&gt;. We solve that by encoding the placeholders before using &lt;em&gt;HtmlCompat&lt;/em&gt;, like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;encodedPlaceholder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;htmlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;placeholder1&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encodedPlaceholder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicStyledText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;dynamicText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FROM_HTML_MODE_COMPACT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamicStyledText&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618866598534%2Fqh8UYzcXY.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618866598534%2Fqh8UYzcXY.png" alt="Encoded string placeholder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although it works and it is the recommended way according to the &lt;a href="https://developer.android.com/guide/topics/resources/string-resource.html#kotlin" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;, I personally do not like any of the approaches before. Why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You end up changing the xml string resource completely for the sake of using a dynamic text placeholder&lt;/li&gt;
&lt;li&gt;You lose xml highlighting in the styled parts of the string resource and therefore, it is harder to read&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1617637524706%2FFZ3xQvaQ_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1617637524706%2FFZ3xQvaQ_.png" alt="Html tags highlighting in String resources"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A better approach would be to create a method that can handle the original xml string resource with HTML tags and placeholders. In doing so, it does not matter whether the string resource contains HTML markup or not, the method simply handles the placeholders while keeping the style defined by the (existing, if any) HTML tags ... no need to either replace opening unsafe characters or add &lt;a href="https://developer.android.com/reference/org/w3c/dom/CDATASection" rel="noopener noreferrer"&gt;CDATASections&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And yes, it is possible with a bit of hackery. Let's see how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Digging into a better solution
&lt;/h2&gt;

&lt;p&gt;We already know that using &lt;code&gt;context.getText(R.string.lorem_ipsum)&lt;/code&gt; returns the string resource styled as a &lt;code&gt;CharSequence&lt;/code&gt;. If the string resource has a placeholder, it will be shown the same as in the xml.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863719712%2FyGCqzo8SU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863719712%2FyGCqzo8SU.png" alt="step 1 - getText()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We also know that &lt;code&gt;HtmlCompat.fromHtml()&lt;/code&gt; processes "some" HTML tags. Its inverse method exists and does exactly the opposite: takes a &lt;code&gt;Spanned&lt;/code&gt; object and converts it to a string with the corresponding HTML tags. The flag we pass to the method also matters: &lt;code&gt;HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL&lt;/code&gt; also adds a new line at the end of the HTML string and we have to account for that. Therefore, we can get the desired HTML string as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// step 2 - toHtml()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;spannedString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpannedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styledString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;htmlString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;spannedString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TO_HTML_PARAGRAPH_LINES_INDIVIDUAL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substringBeforeLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&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="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which results into&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863756166%2FKJo868I_S.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863756166%2FKJo868I_S.png" alt="step 2 - toHtml()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've got the equivalent HTML string to the styled String of the first step so far. However, the final goal is to replace its placeholders with the corresponding values. As you might remember, I mentioned at the beginning of the article that we can use &lt;code&gt;String.format(text: String, vararg args: String)&lt;/code&gt; for that. It would not work with a &lt;code&gt;CharSequence&lt;/code&gt;, but that is why we converted it into its equivalent HTML string in the first place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// step 3 - String.format()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicHtmlString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863883796%2FF2E0L2pW-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863883796%2FF2E0L2pW-.png" alt="step 3 - String.format()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just convert the HTML text into a CharSequence and we get the desired style. Remember to use &lt;code&gt;HtmlCompat.FROM_HTML_MODE_COMPACT&lt;/code&gt;, since it is the inverse of the &lt;code&gt;HtmlCompat.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL&lt;/code&gt; we've previously used&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// step 4 - fromHtml()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;dynamicStyledString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FROM_HTML_MODE_COMPACT&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// fromHtml() adds a new line at the end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863870460%2FBFVdHbZuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1618863870460%2FBFVdHbZuq.png" alt="step 4 - fromHtml()"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well we are almost done... as we have seen at the beginning of this article, if the placeholders are &lt;code&gt;Strings&lt;/code&gt; containing unsafe characters, they do not show up. Therefore, do not forget that we need to encode the string values that will substitute the placeholders. A Kotlin extension function following all the aforementioned steps would look like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHtmlStyledText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@StringRes&lt;/span&gt; &lt;span class="n"&gt;htmlStringRes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;vararg&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;CharSequence&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// step 0 - Encode string placeholders  &lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;escapedArgs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&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="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;TextUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;htmlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;toTypedArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// step 1 - getText()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;styledString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;htmlStringRes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// step 2 - toHtml()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;spannedString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SpannedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styledString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;htmlString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;spannedString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TO_HTML_PARAGRAPH_LINES_INDIVIDUAL&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substringBeforeLast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&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="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// step 3 - String.format()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dynamicStyledString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="n"&gt;escapedArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// step 4 - fromHtml()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;dynamicStyledString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;HtmlCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FROM_HTML_MODE_COMPACT&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;//fromHtml() adds one new line at the end&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  BONUS
&lt;/h2&gt;

&lt;p&gt;The same idea applies to plural resources. Simply replace&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// step 1 - getText()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;styledString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// step 1 - getText()&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;styledString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getQuantityText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plural&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the corresponding &lt;a href="https://gist.github.com/sergio-sastre/371191e5067c73af747f3d0939e0db29" rel="noopener noreferrer"&gt;working gist for strings and plurals here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cover photo by &lt;a href="https://unsplash.com/@markusspiske?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Markus Spiske&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/html?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>html</category>
      <category>xml</category>
    </item>
  </channel>
</rss>
