Level 1 titles

We want to transform a # Title 1 line into an XML element <title>Title 1</title>.

Test Driven Development says we must write a test that describes the business requirement, before coding any production code. XSpec is a framework where we can describe business requirements as scenarios, with context and expectations.

Let's write such a scenario

Example 1. XSpec 1

<x:description 
  xmlns:x="http://www.jenitennison.com/xslt/xspec"
  stylesheet="../../main/xsl/md-to-xml.xsl"
  xslt-version="3.0">

  <x:scenario label="Title 1">
    <x:context select="'# Title 1'" mode="convert"/>
    <x:expect label="A title element with text in">
      <title>Title 1</title>
    </x:expect>
  </x:scenario>
</x:description>

If we run this XSpec scenario, it fails, and says that ../../main/xsl/md-to-xml.xsl does not exist. Correct. A test that does not compile is a failing test. We are allowed to write production code to make the test compile.

So we create the missing file, with the minimum code to make the test compile.

Example 2. XSLT 1

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0">
  
</xsl:stylesheet>

With this file, the unit tests executes, but fails to succeed. We can now write the minimum code to make the test succeed :

Example 3. XSLT 2

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0">
  
  <xsl:template match=".[. eq '# Title 1']" mode="convert">
    <title>Title 1</title>
  </xsl:template>
</xsl:stylesheet>

If we run again the unit test, it now succeeds. First part of TDD loop is completed, the code behaves as expected. But the code is very specific, and not very easy to understand ; business intention can not be quickly understood, and that's a problem. In [Clean Code], Bob Martin explains that we have to improve readability, it makes code easier to maintain.

To refactor code, we can process by hand, and apply manually various refactoring operations, like Extract Function[Refactoring Extract Function] ; we can also use OXygen's implementation of this operation, it will be quicker :

Example 4. XSLT 3

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:prv="http://www.example.com/fn">
  
  <xsl:function name="prv:isTitle1">
    <xsl:value-of select=". eq '# Title 1'"/>
  </xsl:function>
  <xsl:template match=".[prv:isTitle1()]" mode="convert">
    <title>Title 1</title>
  </xsl:template>
</xsl:stylesheet>

Let's run again the unit test, it fails. The function does not have any parameter, no context item, so it can not compare text. Let's introduce a parameter ; OXygen does not provide a Introduce parameter[Refactoring Introduce Parameter] nor a Change function declaration[Refactoring Change Function Declaration], so we have to do it manually :

Example 5. XSLT 4

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:prv="http://www.example.com/fn"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <xsl:function name="prv:isTitle1">
    <xsl:param name="line" as="xs:string"/>
    <xsl:value-of select="$line eq '# Title 1'"/>
  </xsl:function>
  <xsl:template match=".[prv:isTitle1(.)]" mode="convert">
    <title>Title 1</title>
  </xsl:template>
</xsl:stylesheet>

The test succeeds. But function is at the beginning of the code, and it hides the business part. Let's apply the Slide Statement[Refactoring Slide Statement] refactoring operation, to move the function at the bottom of the file :

Example 6. XSLT 5

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:prv="http://www.example.com/fn"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <xsl:template match=".[prv:isTitle1(.)]" mode="convert">
    <title>Title 1</title>
  </xsl:template>
  
  <xsl:function name="prv:isTitle1">
    <xsl:param name="line" as="xs:string"/>
    <xsl:value-of select="$line eq '# Title 1'"/>
  </xsl:function>
</xsl:stylesheet>

Now, we can read the XSL file, and understand that it is used to convert all lines that are a title1, and transform it to a random element. We do not need to know exactly what is a title1, and which technical rules apply to identify a title1. We just know that this template processes all title1 lines.

This second part of TDD loop, the refactoring part, is the most important phase of TDD. It's the phase where we make business rules emerge from the code.

What a pitty, Oxygen is not very precise in refactoring, and it generates code that has to be manually fixed. The generated function should be typed as boolean, but isn't. It's an error, and we know this because we have good skills in XSLT. But we can not write new code without a failing test. So we write a test that shows the function is not correct and must return a xs:boolean.

Example 7. XSpec 2

<x:description 
  xmlns:x="http://www.jenitennison.com/xslt/xspec"
  xmlns:prv="http://www.example.com/fn"
  stylesheet="../../main/xsl/md-to-xml.xsl"
  xslt-version="3.0">
  
  <x:scenario label="Title 1">
    <x:context select="'# Title 1'" mode="convert"/>
    <x:expect label="A title element with text in">
      <title>Title 1</title>
    </x:expect>
  </x:scenario>    
  
  <x:scenario label="testing isTitle1">
    <x:scenario label="# Title 1">
      <x:call function="prv:isTitle1">
        <x:param># Title 1</x:param>
      </x:call>
      <x:expect label="Title 1" select="true()"/>
    </x:scenario>
  </x:scenario>
</x:description>

Here, we precisely define we expect a xs:boolean. We we run the test, it fails. Types are different. Let's correct production code just add as attribute :

Example 8. XSLT 6

  <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="line" as="xs:string"/>
    <xsl:value-of select="$line eq '# Title 1'"/>
  </xsl:function>

And here, Oxygen shows a warning on mis-use of value-of. We replace it with a sequence :

Example 9. XSLT 7

  <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="line" as="xs:string"/>
    <xsl:sequence select="$line eq '# Title 1'"/>
  </xsl:function>

Now, all tests are green.

We now can add new test cases for titles. XSpec allows to wrap scenarios into scenarios, it is very useful to group scenarios together ; that's what we do here, to check if our isTitle1 function is correct :

Example 10. XSpec 3

  <x:scenario label="testing isTitle1">
    <x:scenario label="# Title 1">
      <x:call function="prv:isTitle1">
        <x:param># Title 1</x:param>
      </x:call>
      <x:expect label="true" select="true()"/>
    </x:scenario>
    <x:scenario label="# Another title 1">
      <x:call function="prv:isTitle1">
        <x:param># Another title 1</x:param>
      </x:call>
      <x:expect label="true" select="true()"/>
    </x:scenario>
  </x:scenario>

The new scenario fails, our function is not correctly written. As we have read the Markdown specification, we know that a level 1 title must starts with # , so we code this :

Example 11. XSLT 8

  <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="line" as="xs:string"/>
    <xsl:sequence select="$line => starts-with('# ')"/>
  </xsl:function>

And all tests succeed.

Last, we have to test that the produced title element contains the correct text. So we write a new scenario with a different text. And we refactor scenarios titles, to be clearer :

Example 12. XSpec 4

  <x:scenario label="Exercise Book title">
    <x:scenario label="Title 1">
      <x:context select="'# Title 1'" mode="convert"/>
      <x:expect label="A title element with text in">
        <title>Title 1</title>
      </x:expect>
    </x:scenario>
    <x:scenario label="Another title 1">
      <x:context select="'# Another title 1'" mode="convert"/>
      <x:expect label="A title element with Another title 1 in">
        <title>Another title 1</title>
      </x:expect>
    </x:scenario>
  </x:scenario>

This new scenario fails, we can correct the template to produce the expected output :

Example 13. XSLT 9

  <xsl:template match=".[prv:isTitle1(.)]" mode="convert" expand-text="true">
    <title>{. => substring(3)}</title>
  </xsl:template>

This new code make all scenarios succeed. We are done with implementation for level 1 titles, but code is not very understandable : what does . => substring(3) mean from a business point of view ? Nothing, it's too technical. Let's add clarity, and extract all the thecnical code in a function :

Example 14. XSLT 10

  <xsl:function name="prv:getTitle1Content">
    <xsl:param name="line" as="xs:string"/>
    <xsl:value-of select="$line => substring(3)"/>
  </xsl:function>
  <xsl:template match=".[prv:isTitle1(.)]" mode="convert" expand-text="true">
    <title>{prv:getTitle1Content(.)}</title>
  </xsl:template>

And again, move function to the bottom of file, it's only technical details... Now, if we look at our code, we have a template where the match attribute contains an understable business rule, and it produces an output that is also described by a business rule. All rules are clearly defined in the code. This code will be much simpler to maintain if business rules evolve in the future, and if developers who write the initial code are not still active on the project. We can read the code from top to bottom, as a book.

Example 15. XSLT 11

<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:prv="com:oxiane:courses:mdtoxml:private"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  <xsl:mode name="convert" on-no-match="fail"/>
  
  <xsl:template match=".[prv:isTitle1(.)]" mode="convert" expand-text="true">
    <title>{prv:getTitle1Content(.)}</title>
  </xsl:template>
  
  <xsl:function name="prv:isTitle1" as="xs:boolean">
    <xsl:param name="line" as="xs:string"/>
    <xsl:sequence select="$line => starts-with('# ')"/>
  </xsl:function>

  <xsl:function name="prv:getTitle1Content">
    <xsl:param name="line" as="xs:string"/>
    <xsl:value-of select="$line => substring(3)"/>
  </xsl:function>
</xsl:stylesheet>