diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 14395d8..e5f78e0 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,13 +1,22 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" bootstrap="./tests/bootstrap.php" backupGlobals="true" colors="true" forceCoversAnnotation="true">
-  <coverage>
-    <include>
-      <directory>./src</directory>
-    </include>
-  </coverage>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.0/phpunit.xsd"
+         bootstrap="vendor/autoload.php"
+         executionOrder="depends,defects"
+         forceCoversAnnotation="true"
+         beStrictAboutOutputDuringTests="true"
+         beStrictAboutTodoAnnotatedTests="true"
+         verbose="true">
+
   <testsuites>
     <testsuite name="Unit Tests">
       <directory>tests</directory>
     </testsuite>
   </testsuites>
+
+  <filter>
+    <whitelist processUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">src</directory>
+    </whitelist>
+  </filter>
 </phpunit>
diff --git a/src/Generator/Formatter/MarkdownFormatter.php b/src/Generator/Formatter/MarkdownFormatter.php
new file mode 100644
index 0000000..7d0aafa
--- /dev/null
+++ b/src/Generator/Formatter/MarkdownFormatter.php
@@ -0,0 +1,183 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Generator\Formatter;
+
+use App\Value\Diff;
+use App\Value\Property;
+use App\Value\Sniff;
+use App\Value\Url;
+use App\Value\UrlList;
+use App\Value\Violation;
+use Stringy\Stringy;
+use function Stringy\create as s;
+
+final class MarkdownFormatter
+{
+    public function formatDescription(Sniff $sniff): string
+    {
+        $description = $sniff->getDescription();
+        if ($description === '') {
+            return '';
+        }
+
+        return <<<MD
+        {$description}
+        MD;
+    }
+
+    public function formatDocblock(Sniff $sniff): string
+    {
+        $docblock = $sniff->getDocblock();
+        if ($docblock === '') {
+            return '';
+        }
+
+        return <<<MD
+        ## Docblock
+        
+        {$docblock}
+        MD;
+    }
+
+    /**
+     * @param Diff[] $diffs
+     */
+    public function formatComparisons(array $diffs): string
+    {
+        if ($diffs === []) {
+            return '';
+        }
+
+        $diffBlocks = implode("\n\n", $this->getDiffBlocks($diffs));
+
+        return <<<MD
+        ## Comparisons
+        
+        {$diffBlocks}
+        MD;
+    }
+
+    /**
+     * @param Diff[] $diffs
+     * @return string[]
+     */
+    private function getDiffBlocks(array $diffs): array
+    {
+        return array_map(function (Diff $diff): string {
+            return <<<MD
+            ```diff
+            {$this->prependLinesWith('-', $diff->getBefore())}
+            {$this->prependLinesWith('+', $diff->getAfter())}
+            ```
+            MD;
+        }, $diffs);
+    }
+
+    private function prependLinesWith(string $prefix, string $lines): string
+    {
+        $prependedLines = array_map(function (Stringy $line) use ($prefix) {
+            return (string)$line->prepend($prefix);
+        }, s($lines)->lines());
+
+        return implode("\n", $prependedLines);
+    }
+
+    /**
+     * @param Property[] $properties
+     */
+    public function formatPublicProperties(array $properties): string
+    {
+        if ($properties === []) {
+            return '';
+        }
+
+        $propertyLines = implode("\n", $this->getPublicPropertyLines($properties));
+
+        return <<<MD
+        ## Public Properties
+        
+        {$propertyLines}
+        MD;
+    }
+
+    /**
+     * @param Property[] $properties
+     * @return string[]
+     */
+    private function getPublicPropertyLines(array $properties): array
+    {
+        return array_map(function (Property $property) {
+            return "- `\${$property->getName()}` : {$property->getType()} {$property->getDescription()}";
+        }, $properties);
+    }
+
+    /**
+     * @return string
+     */
+    public function formatSeeAlso(UrlList $urls): string
+    {
+        if ($urls->toArray() === []) {
+            return '';
+        }
+
+        $linkLines = implode("\n", $this->getLinkLines($urls));
+
+        return <<<MD
+        ## See Also
+        
+        {$linkLines}
+        MD;
+    }
+
+    /**
+     * @return string[]
+     */
+    private function getLinkLines(UrlList $urls): array
+    {
+        return array_map(function (Url $url) {
+            return "- [$url]($url)";
+        }, $urls->toArray());
+    }
+
+    /**
+     * @param Violation[] $violations
+     * @return string
+     */
+    public function formatViolations(array $violations): string
+    {
+        if ($violations === []) {
+            return '';
+        }
+
+        $violations = implode("\n", $this->getViolationBlocks($violations));
+
+        return <<<MD
+        ## Troubleshooting
+        
+        {$violations}
+        MD;
+    }
+
+    /**
+     * @param Violation[] $violations
+     * @return string[]
+     */
+    private function getViolationBlocks(array $violations): array
+    {
+        return array_map(function (Violation $violation): string {
+            return <<<MD
+            ```
+            <details>
+            <summary>{$violation->getCode()}</summary>
+            {$violation->getDescription()}
+            
+            {$this->formatComparisons($violation->getDiffs())}
+            
+            {$this->formatSeeAlso($violation->getUrls())}
+            </details>
+            ```
+            MD;
+        }, $violations);
+    }
+}
diff --git a/src/Generator/Generator.php b/src/Generator/Generator.php
index 55438aa..c0aed54 100644
--- a/src/Generator/Generator.php
+++ b/src/Generator/Generator.php
@@ -8,7 +8,7 @@
 
 interface Generator
 {
-    public function createViolationDoc(Violation $doc): string;
+    public function createViolationDoc(Violation $violation): string;
 
     public function createSniffDoc(Sniff $sniff): string;
 }
diff --git a/src/Generator/JekyllPageGenerator.php b/src/Generator/JekyllPageGenerator.php
index 08c7aed..a7bdbd0 100644
--- a/src/Generator/JekyllPageGenerator.php
+++ b/src/Generator/JekyllPageGenerator.php
@@ -3,28 +3,52 @@
 
 namespace App\Generator;
 
+use App\Generator\Formatter\MarkdownFormatter;
 use App\Value\Sniff;
+use App\Value\Violation;
 
-class JekyllPageGenerator extends MarkdownGenerator implements Generator
+final class JekyllPageGenerator implements Generator
 {
+    private MarkdownFormatter $formatter;
+    private const LINE_ENDING_REGEX = '\r\n|\n';
+
+    public function __construct(MarkdownFormatter $formatter)
+    {
+        $this->formatter = $formatter;
+    }
+
     public function createSniffDoc(Sniff $sniff): string
     {
-        $sniffDoc = $this->getFrontMatter($sniff) . "\n";
-        $sniffDoc .= parent::createSniffDoc($sniff);
+        $sniffDoc = <<<MD
+        {$this->getFrontMatter($sniff)}
+        # {$sniff->getCode()}
+        
+        {$this->formatter->formatDescription($sniff)}
+        {$this->formatter->formatDocblock($sniff)}
+        {$this->formatter->formatComparisons($sniff->getDiffs())}
+        {$this->formatter->formatPublicProperties($sniff->getProperties())}
+        {$this->formatter->formatSeeAlso($sniff->getUrls())}
+        {$this->formatter->formatViolations($sniff->getViolations())}
+        MD;
 
-        return $sniffDoc;
+        $sniffDoc = preg_replace('`'.self::LINE_ENDING_REGEX.'{3,}`', "\n\n", $sniffDoc);
+        return preg_replace('`'.self::LINE_ENDING_REGEX.'{2,}$`', "\n", $sniffDoc);
+    }
+
+    public function createViolationDoc(Violation $violation): string
+    {
+        return <<<MD
+        {$violation->getDescription()}
+        
+        {$this->formatter->formatComparisons($violation->getDiffs())}
+        
+        {$this->formatter->formatSeeAlso($violation->getUrls())}
+        MD;
     }
 
     private function getFrontMatter(Sniff $sniff): string
     {
         $sniffName = $sniff->getSniffName();
-        if ($sniffName === '') {
-            return <<<'MD'
-            ---
-            ---
-
-            MD;
-        }
 
         return <<<MD
         ---
diff --git a/src/Generator/MarkdownGenerator.php b/src/Generator/MarkdownGenerator.php
index c73b8da..1635e4d 100644
--- a/src/Generator/MarkdownGenerator.php
+++ b/src/Generator/MarkdownGenerator.php
@@ -3,209 +3,50 @@
 
 namespace App\Generator;
 
-use App\Value\Diff;
-use App\Value\Property;
+use App\Generator\Formatter\MarkdownFormatter;
 use App\Value\Sniff;
-use App\Value\Url;
-use App\Value\UrlList;
 use App\Value\Violation;
-use Stringy\Stringy;
-use function Stringy\create as s;
 
-class MarkdownGenerator implements Generator
+final class MarkdownGenerator implements Generator
 {
-    public function createSniffDoc(Sniff $sniff): string
-    {
-        $sniffDoc = <<<MD
-        # {$sniff->getCode()}
-        
-        {$this->getDescription($sniff)}
-        {$this->getDocblock($sniff)}
-        {$this->getComparisons($sniff->getDiffs())}
-        {$this->getPublicProperties($sniff->getProperties())}
-        {$this->getSeeAlso($sniff->getUrls())}
-        {$this->getViolations($sniff->getViolations())}
-        MD;
-
-        $sniffDoc = preg_replace('/\n{3,}/', "\n\n",$sniffDoc);
-        return preg_replace('/\n{2,}$/', "\n",$sniffDoc);
-    }
-
-    private function getDescription(Sniff $sniff): string
-    {
-        $description = $sniff->getDescription();
-        if ($description === '') {
-            return '';
-        }
-
-        return <<<MD
-        {$description}
-        
-        MD;
-    }
-
-    private function getDocblock(Sniff $sniff): string
-    {
-        $docblock = $sniff->getDocblock();
-        if ($docblock === '') {
-            return '';
-        }
-
-        return <<<MD
-        ## Docblock
-        
-        {$docblock}
-        MD;
-    }
-
-    /**
-     * @param Diff[] $diffs
-     */
-    private function getComparisons(array $diffs): string
-    {
-        if ($diffs === []) {
-            return '';
-        }
-
-        $diffBlocks = implode("\n\n", $this->getDiffBlocks($diffs));
-
-        return <<<MD
-        ## Comparisons
-        
-        {$diffBlocks}
-        MD;
-    }
-
-    /**
-     * @param Diff[] $diffs
-     * @return string[]
-     */
-    private function getDiffBlocks(array $diffs): array
-    {
-        return array_map(function (Diff $diff): string {
-            return <<<MD
-            ```diff
-            {$this->prependLinesWith('-', $diff->getBefore())}
-            {$this->prependLinesWith('+', $diff->getAfter())}
-            ```
-            MD;
-        }, $diffs);
-    }
+    private MarkdownFormatter $formatter;
+    private const LINE_ENDING_REGEX = '\r\n|\n';
 
-    private function prependLinesWith(string $prefix, string $lines): string
+    public function __construct(MarkdownFormatter $formatter)
     {
-        $prependedLines = array_map(function (Stringy $line) use ($prefix) {
-            return (string)$line->prepend($prefix);
-        }, s($lines)->lines());
-
-        return implode("\n", $prependedLines);
+        $this->formatter = $formatter;
     }
 
-    /**
-     * @param Property[] $properties
-     */
-    private function getPublicProperties(array $properties): string
+    public function createSniffDoc(Sniff $sniff): string
     {
-        if ($properties === []) {
-            return '';
-        }
-
-        $propertyLines = implode("\n", $this->getPublicPropertyLines($properties));
-
-        return <<<MD
-        ## Public Properties
+        $sniffDoc = <<<MD
+        # {$sniff->getCode()}
         
-        {$propertyLines}
+        {$this->formatter->formatDescription($sniff)}
         
-        MD;
-    }
-
-    /**
-     * @param Property[] $properties
-     * @return string[]
-     */
-    private function getPublicPropertyLines(array $properties): array
-    {
-        return array_map(function (Property $property) {
-            return "- `\${$property->getName()}` : {$property->getType()} {$property->getDescription()}";
-        }, $properties);
-    }
-
-    /**
-     * @return string
-     */
-    private function getSeeAlso(UrlList $urls): string
-    {
-        if ($urls->toArray() === []) {
-            return '';
-        }
-
-        $linkLines = implode("\n", $this->getLinkLines($urls));
-
-        return <<<MD
-        ## See Also
+        {$this->formatter->formatDocblock($sniff)}
         
-        {$linkLines}
+        {$this->formatter->formatComparisons($sniff->getDiffs())}
         
-        MD;
-    }
-
-    /**
-     * @return string[]
-     */
-    private function getLinkLines(UrlList $urls): array
-    {
-        return array_map(function (Url $url) {
-            return "- [$url]($url)";
-        }, $urls->toArray());
-    }
-
-    /**
-     * @param Violation[] $violations
-     * @return string
-     */
-    private function getViolations(array $violations): string
-    {
-        if ($violations === []) {
-            return '';
-        }
-
-        $violations = implode("\n", $this->getViolationBlocks($violations));
-
-        return <<<MD
-        ## Troubleshooting
+        {$this->formatter->formatPublicProperties($sniff->getProperties())}
         
-        {$violations}
+        {$this->formatter->formatSeeAlso($sniff->getUrls())}
         
+        {$this->formatter->formatViolations($sniff->getViolations())}
         MD;
-    }
 
-    /**
-     * @param Violation[] $violations
-     * @return string[]
-     */
-    private function getViolationBlocks(array $violations): array
-    {
-        return array_map(function (Violation $violation): string {
-            return <<<MD
-            ```
-            <details>
-            <summary>{$violation->getCode()}</summary>
-            {$this->createViolationDoc($violation)}
-            </details>
-            ```
-            MD;
-        }, $violations);
+        $sniffDoc = preg_replace('`'.self::LINE_ENDING_REGEX.'{3,}`', "\n\n", $sniffDoc);
+        return preg_replace('`'.self::LINE_ENDING_REGEX.'{2,}$`', "\n", $sniffDoc);
     }
 
-    public function createViolationDoc(Violation $doc): string
+    public function createViolationDoc(Violation $violation): string
     {
         return <<<MD
-        {$doc->getDescription()}
+        {$violation->getDescription()}
         
-        {$this->getComparisons($doc->getDiffs())}
+        {$this->formatter->formatComparisons($violation->getDiffs())}
         
-        {$this->getSeeAlso($doc->getUrls())}
+        {$this->formatter->formatSeeAlso($violation->getUrls())}
         MD;
     }
 }
diff --git a/src/Value/Sniff.php b/src/Value/Sniff.php
index 46d89cc..ebc8947 100644
--- a/src/Value/Sniff.php
+++ b/src/Value/Sniff.php
@@ -71,21 +71,25 @@ public function __construct(
         $this->violations = $violations;
     }
 
+    /** @return non-empty-string */
     public function getCode(): string
     {
         return $this->code;
     }
 
+    /** @return non-empty-string */
     public function getStandardName(): string
     {
         return $this->standardName;
     }
 
+    /** @return non-empty-string */
     public function getCategoryName(): string
     {
         return $this->categoryName;
     }
 
+    /** @return non-empty-string */
     public function getSniffName(): string
     {
         return $this->sniffName;
@@ -129,4 +133,104 @@ public function getViolations(): array
     {
         return $this->violations;
     }
+
+    public function withCode(string $newValue): self
+    {
+        return new self(
+            $newValue,
+            $this->docblock,
+            $this->properties,
+            $this->urls,
+            $this->description,
+            $this->diffs,
+            $this->violations
+        );
+    }
+
+    public function withDocblock(string $newValue): self
+    {
+        return new self(
+            $this->code,
+            $newValue,
+            $this->properties,
+            $this->urls,
+            $this->description,
+            $this->diffs,
+            $this->violations
+        );
+    }
+
+    /**
+     * @param Property[] $newValue
+     */
+    public function withProperties(array $newValue): self
+    {
+        return new self(
+            $this->code,
+            $this->docblock,
+            $newValue,
+            $this->urls,
+            $this->description,
+            $this->diffs,
+            $this->violations
+        );
+    }
+
+    public function withUrls(UrlList $newValue): self
+    {
+        return new self(
+            $this->code,
+            $this->docblock,
+            $this->properties,
+            $newValue,
+            $this->description,
+            $this->diffs,
+            $this->violations
+        );
+    }
+
+    public function withDescription(string $newValue): self
+    {
+        return new self(
+            $this->code,
+            $this->docblock,
+            $this->properties,
+            $this->urls,
+            $newValue,
+            $this->diffs,
+            $this->violations
+        );
+    }
+
+    /**
+     * @param Diff[] $newValue
+     */
+    public function withDiffs(array $newValue): self
+    {
+        return new self(
+            $this->code,
+            $this->docblock,
+            $this->properties,
+            $this->urls,
+            $this->description,
+            $newValue,
+            $this->violations
+        );
+    }
+
+    /**
+     * @param Violation[] $newValue
+     */
+    public function withViolations(array $newValue): self
+    {
+        return new self(
+            $this->code,
+            $this->docblock,
+            $this->properties,
+            $this->urls,
+            $this->description,
+            $this->diffs,
+            $newValue
+        );
+    }
 }
diff --git a/tests/App/Generator/Formatter/MarkdownFormatterTest.php b/tests/App/Generator/Formatter/MarkdownFormatterTest.php
new file mode 100644
index 0000000..0a22b32
--- /dev/null
+++ b/tests/App/Generator/Formatter/MarkdownFormatterTest.php
@@ -0,0 +1,237 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Tests\App\Generator\Formatter;
+
+use App\Generator\Formatter\MarkdownFormatter;
+use App\Value\Diff;
+use App\Value\Property;
+use App\Value\Sniff;
+use App\Value\Url;
+use App\Value\UrlList;
+use App\Value\Violation;
+use PHPUnit\Framework\TestCase;
+
+/** @covers \App\Generator\Formatter\MarkdownFormatter */
+final class MarkdownFormatterTest extends TestCase
+{
+    private MarkdownFormatter $formatter;
+
+    private function createSniff(): Sniff
+    {
+        return new Sniff(
+            'Standard.Category.My',
+            '',
+            [],
+            new UrlList([]),
+            '',
+            [],
+            []
+        );
+    }
+
+    /** @test */
+    public function formatDescription(): void
+    {
+        self::assertEquals(
+            <<<MD
+            Description
+            MD,
+            $this->formatter->formatDescription(
+                $this->createSniff()->withDescription('Description')
+            )
+        );
+    }
+
+    /** @test */
+    public function formatDescription_WithBlankString_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatDescription(
+                $this->createSniff()->withDescription('')
+            )
+        );
+    }
+
+    /** @test */
+    public function formatDocblock(): void
+    {
+        self::assertEquals(
+            <<<MD
+            ## Docblock
+            
+            Docblock
+            MD,
+            $this->formatter->formatDocblock(
+                $this->createSniff()->withDocblock('Docblock')
+            )
+        );
+    }
+
+    /** @test */
+    public function formatDocblock_WithBlankString_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatDocblock(
+                $this->createSniff()->withDocblock('')
+            )
+        );
+    }
+
+    /** @test */
+    public function formatComparisons(): void
+    {
+        self::assertEquals(
+            <<<MD
+            ## Comparisons
+            
+            ```diff
+            -a();
+            +b();
+            ```
+            
+            ```diff
+            -a();
+            +b();
+            ```
+            MD,
+            $this->formatter->formatComparisons(
+                $this->createSniff()->withDiffs([
+                    new Diff('a();', 'b();'),
+                    new Diff('a();', 'b();')
+                ])->getDiffs()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatComparisons_WithEmptyList_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatComparisons(
+                $this->createSniff()->withDiffs([])->getDiffs()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatPublicProperties(): void
+    {
+        self::assertEquals(
+            <<<MD
+            ## Public Properties
+            
+            - `\$a` : string DescriptionA
+            - `\$b` : int DescriptionB
+            MD,
+            $this->formatter->formatPublicProperties(
+                $this->createSniff()->withProperties([
+                    new Property('a', 'string', 'DescriptionA'),
+                    new Property('b', 'int', 'DescriptionB')
+                ])->getProperties()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatPublicProperties_WithEmptyList_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatPublicProperties(
+                $this->createSniff()->withProperties([])->getProperties()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatSeeAlso(): void
+    {
+        self::assertEquals(
+            <<<MD
+            ## See Also
+            
+            - [http://link1.com](http://link1.com)
+            - [http://link2.com](http://link2.com)
+            MD,
+            $this->formatter->formatSeeAlso(
+                $this->createSniff()->withUrls(new UrlList([
+                    new Url('http://link1.com'),
+                    new Url('http://link2.com')
+                ]))->getUrls()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatSeeAlso_WithEmptyList_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatSeeAlso(
+                $this->createSniff()->withUrls(new UrlList([]))->getUrls()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatViolations(): void
+    {
+        self::assertEquals(
+            <<<MD
+            ## Troubleshooting
+            
+            ```
+            <details>
+            <summary>Standard.Category.My.ErrorCode</summary>
+            Description
+            
+            ## Comparisons
+            
+            ```diff
+            -a();
+            +b();
+            ```
+            
+            ## See Also
+            
+            - [http://link1.com](http://link1.com)
+            </details>
+            ```
+            MD,
+            $this->formatter->formatViolations(
+                $this->createSniff()->withViolations([
+                    new Violation(
+                        'Standard.Category.My.ErrorCode',
+                        'Description',
+                        [
+                            new Diff('a();', 'b();')
+                        ],
+                        new UrlList([
+                            new Url('http://link1.com')
+                        ])
+                    )
+                ])->getViolations()
+            )
+        );
+    }
+
+    /** @test */
+    public function formatViolations_WithEmptyList_ReturnBlankString(): void
+    {
+        self::assertEquals(
+            '',
+            $this->formatter->formatViolations(
+                $this->createSniff()->withViolations([])->getViolations()
+            )
+        );
+    }
+
+    protected function setUp(): void
+    {
+        $this->formatter = new MarkdownFormatter();
+    }
+}
diff --git a/tests/Generator/JekyllPageGeneratorTest.php b/tests/Generator/JekyllPageGeneratorTest.php
index eae2d38..70d2750 100644
--- a/tests/Generator/JekyllPageGeneratorTest.php
+++ b/tests/Generator/JekyllPageGeneratorTest.php
@@ -3,18 +3,22 @@
 
 namespace App\Tests\Generator;
 
+use App\Generator\Formatter\MarkdownFormatter;
 use App\Generator\JekyllPageGenerator;
+use App\Value\Diff;
 use App\Value\Sniff;
+use App\Value\Url;
 use App\Value\UrlList;
+use App\Value\Violation;
 use PHPUnit\Framework\TestCase;
 
-/** @covers \App\Generator\JekyllPage */
-class JekyllPageTest extends TestCase
+/** @covers \App\Generator\JekyllPageGenerator */
+class JekyllPageGeneratorTest extends TestCase
 {
     private JekyllPageGenerator $generator;
 
     /** @test */
-    public function fromSniff_WithMinimalData_WriteMinimalDetails()
+    public function createSniffDoc_WithMinimalData_WriteMinimalDetails()
     {
         $doc = new Sniff(
             'Standard.Category.My',
@@ -39,8 +43,41 @@ public function fromSniff_WithMinimalData_WriteMinimalDetails()
         );
     }
 
+    /** @test */
+    public function createViolationDoc_WriteMinimalDetails()
+    {
+        self::assertEquals(
+            <<<'MD'
+            Description
+            
+            ## Comparisons
+            
+            ```diff
+            -a();
+            +b();
+            ```
+            
+            ## See Also
+            
+            - [http://link1.com](http://link1.com)
+            MD,
+            $this->generator->createViolationDoc(
+                new Violation(
+                    'Standard.Category.My.ErrorCode',
+                    'Description',
+                    [
+                        new Diff('a();', 'b();'),
+                    ],
+                    new UrlList([
+                        new Url('http://link1.com')
+                    ])
+                )
+            )
+        );
+    }
+
     protected function setUp(): void
     {
-        $this->generator = new JekyllPageGenerator();
+        $this->generator = new JekyllPageGenerator(new MarkdownFormatter());
     }
 }
diff --git a/tests/Generator/MarkdownGeneratorTest.php b/tests/Generator/MarkdownGeneratorTest.php
index b79647c..cd6d85b 100644
--- a/tests/Generator/MarkdownGeneratorTest.php
+++ b/tests/Generator/MarkdownGeneratorTest.php
@@ -3,6 +3,7 @@
 
 namespace App\Tests\Generator;
 
+use App\Generator\Formatter\MarkdownFormatter;
 use App\Generator\MarkdownGenerator;
 use App\Value\Diff;
 use App\Value\Property;
@@ -18,7 +19,7 @@ class MarkdownGeneratorTest extends TestCase
     private MarkdownGenerator $generator;
 
     /** @test */
-    public function fromSniff_WithMinimalData_WriteMinimalDetails()
+    public function createSniffDoc_WithMinimalData_WriteMinimalDetails()
     {
         $doc = new Sniff(
             'Standard.Category.My',
@@ -40,18 +41,16 @@ public function fromSniff_WithMinimalData_WriteMinimalDetails()
     }
 
     /** @test */
-    public function fromSniff_WithCompleteData_WriteAllDetails()
+    public function createSniffDoc_WithCompleteData_WriteAllDetails()
     {
         $doc = new Sniff(
             'Standard.Category.My',
             'DocBlock',
             [
                 new Property('a', 'string', 'DescriptionA'),
-                new Property('b', 'int', 'DescriptionB')
             ],
             new UrlList([
-                new Url('http://link1.com'),
-                new Url('http://link2.com')
+                new Url('http://link1.com')
             ]),
             'Description',
             [],
@@ -61,11 +60,9 @@ public function fromSniff_WithCompleteData_WriteAllDetails()
                     'Description',
                     [
                         new Diff('a();', 'b();'),
-                        new Diff('a();', 'b();')
                     ],
                     new UrlList([
-                        new Url('http://link1.com'),
-                        new Url('http://link2.com')
+                        new Url('http://link1.com')
                     ])
                 )
             ]
@@ -84,12 +81,10 @@ public function fromSniff_WithCompleteData_WriteAllDetails()
             ## Public Properties
             
             - `$a` : string DescriptionA
-            - `$b` : int DescriptionB
             
             ## See Also
             
             - [http://link1.com](http://link1.com)
-            - [http://link2.com](http://link2.com)
             
             ## Troubleshooting
             
@@ -105,6 +100,25 @@ public function fromSniff_WithCompleteData_WriteAllDetails()
             +b();
             ```
             
+            ## See Also
+            
+            - [http://link1.com](http://link1.com)
+            </details>
+            ```
+            MD,
+            $this->generator->createSniffDoc($doc)
+        );
+    }
+
+    /** @test */
+    public function createViolationDoc_WriteMinimalDetails()
+    {
+        self::assertEquals(
+            <<<'MD'
+            Description
+            
+            ## Comparisons
+            
             ```diff
             -a();
             +b();
@@ -113,18 +127,24 @@ public function fromSniff_WithCompleteData_WriteAllDetails()
             ## See Also
             
             - [http://link1.com](http://link1.com)
-            - [http://link2.com](http://link2.com)
-            
-            </details>
-            ```
-            
             MD,
-            $this->generator->createSniffDoc($doc)
+            $this->generator->createViolationDoc(
+                new Violation(
+                    'Standard.Category.My.ErrorCode',
+                    'Description',
+                    [
+                        new Diff('a();', 'b();'),
+                    ],
+                    new UrlList([
+                        new Url('http://link1.com')
+                    ])
+                )
+            )
         );
     }
 
     protected function setUp(): void
     {
-        $this->generator = new MarkdownGenerator();
+        $this->generator = new MarkdownGenerator(new MarkdownFormatter());
     }
 }
diff --git a/tests/Value/SniffTest.php b/tests/Value/SniffTest.php
index f1f3e1a..b1284d5 100644
--- a/tests/Value/SniffTest.php
+++ b/tests/Value/SniffTest.php
@@ -1,176 +1 @@
-<?php
-declare(strict_types=1);
-
-namespace App\Tests\Value;
-
-use App\Value\Diff;
-use App\Value\Property;
-use App\Value\Sniff;
-use App\Value\Url;
-use App\Value\UrlList;
-use App\Value\Violation;
-use InvalidArgumentException;
-use PHPUnit\Framework\TestCase;
-
-/** @covers \App\Value\Sniff */
-class SniffTest extends TestCase
-{
-    const CODE = 'Standard.Category.Code';
-    const STANDARD = 'Standard';
-    const CATEGORY = 'Category';
-    const SNIFFNAME = 'Code';
-    const DOCBLOCK = 'Docblock';
-    const DESCRIPTION = 'Description';
-    /**
-     * @var Property[]
-     */
-    private array $PROPERTIES;
-    /**
-     * @var Url[]
-     */
-    private array $URLS;
-    /**
-     * @var Diff[]
-     */
-    private array $DIFFS;
-    /**
-     * @var Violation[]
-     */
-    private array $VIOLATIONS;
-
-    /** @test */
-    public function constructor_WithBlankCode_ThrowException()
-    {
-        $this->expectException(InvalidArgumentException::class);
-        $params = $this->getValidParams();
-        $params[0] = '';
-        new Sniff(...$params);
-    }
-
-    private function getValidParams(): array
-    {
-        return [
-            self::CODE,
-            self::DOCBLOCK,
-            $this->PROPERTIES,
-            new UrlList($this->URLS),
-            self::DESCRIPTION,
-            $this->DIFFS,
-            $this->VIOLATIONS
-        ];
-    }
-
-    /** @test */
-    public function getProperties()
-    {
-        $properties = [
-            new Property('name', 'int', 'description')
-        ];
-        self::assertEquals(
-            $properties,
-            $this->createSniff()->getProperties()
-        );
-    }
-
-    private function createSniff(): Sniff
-    {
-        return new Sniff(...$this->getValidParams());
-    }
-
-    /** @test */
-    public function getDocblock()
-    {
-        self::assertEquals(
-            self::DOCBLOCK,
-            $this->createSniff()->getDocblock()
-        );
-    }
-
-    /** @test */
-    public function getDescription()
-    {
-        self::assertEquals(
-            self::DESCRIPTION,
-            $this->createSniff()->getDescription()
-        );
-    }
-
-    /** @test */
-    public function getViolations()
-    {
-        self::assertEquals(
-            $this->VIOLATIONS,
-            $this->createSniff()->getViolations()
-        );
-    }
-
-    /** @test */
-    public function getDiffs()
-    {
-        self::assertEquals(
-            $this->DIFFS,
-            $this->createSniff()->getDiffs()
-        );
-    }
-
-    /** @test */
-    public function getUrls()
-    {
-        self::assertEquals(
-            $this->URLS,
-            $this->createSniff()->getUrls()->toArray()
-        );
-    }
-
-    /** @test */
-    public function getCode()
-    {
-        self::assertEquals(
-            self::CODE,
-            $this->createSniff()->getCode()
-        );
-    }
-
-    /** @test */
-    public function getStandardName()
-    {
-        self::assertSame(
-            self::STANDARD,
-            $this->createSniff()->getStandardName()
-        );
-    }
-
-    /** @test */
-    public function getCategoryName()
-    {
-        self::assertSame(
-            self::CATEGORY,
-            $this->createSniff()->getCategoryName()
-        );
-    }
-
-    /** @test */
-    public function getSniffName()
-    {
-        self::assertSame(
-            self::SNIFFNAME,
-            $this->createSniff()->getSniffName()
-        );
-    }
-
-    protected function setUp(): void
-    {
-        $this->PROPERTIES = [
-            new Property('name', 'int', 'description')
-        ];
-        $this->URLS = [
-            new Url('https://link.com')
-        ];
-        $this->DIFFS = [
-            new Diff('a();', 'b();')
-        ];
-        $this->VIOLATIONS = [
-            new Violation('code', '', [], new UrlList([]))
-        ];
-    }
-}
+<?php
declare(strict_types=1);

namespace App\Tests\Value;

use App\Value\Diff;
use App\Value\Property;
use App\Value\Sniff;
use App\Value\Url;
use App\Value\UrlList;
use App\Value\Violation;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;

/** @covers \App\Value\Sniff */
class SniffTest extends TestCase
{
    /** @test */
    public function constructor_WithBlankCode_ThrowException()
    {
        $this->expectException(InvalidArgumentException::class);
        $this->createSniff()->withCode('');
    }

    /** @test */
    public function getProperties()
    {
        $properties = [
            new Property('name', 'int', 'description')
        ];
        self::assertEquals(
            $properties,
            $this->createSniff()->withProperties($properties)->getProperties()
        );
    }

    /** @test */
    public function getDocblock()
    {
        self::assertEquals(
            'Docblock',
            $this->createSniff()->withDocblock('Docblock')->getDocblock()
        );
    }

    /** @test */
    public function getDescription()
    {
        self::assertEquals(
            'Description',
            $this->createSniff()->withDescription('Description')->getDescription()
        );
    }

    /** @test */
    public function getViolations()
    {
        $violations = [
            new Violation('code', '', [], new UrlList([]))
        ];
        self::assertEquals(
            $violations,
            $this->createSniff()->withViolations($violations)->getViolations()
        );
    }

    /** @test */
    public function getDiffs()
    {
        $diffs = [
            new Diff('a();', 'b();')
        ];
        self::assertEquals(
            $diffs,
            $this->createSniff()->withDiffs($diffs)->getDiffs()
        );
    }

    /** @test */
    public function getUrls()
    {
        $urls = new UrlList([
            new Url('https://link.com')
        ]);
        self::assertEquals(
            $urls,
            $this->createSniff()->withUrls($urls)->getUrls()
        );
    }

    /** @test */
    public function getCode()
    {
        self::assertEquals(
            'Standard.Category.SniffName',
            $this->createSniff()->withCode('Standard.Category.SniffName')->getCode()
        );
    }

    /** @test */
    public function getStandardName()
    {
        self::assertSame(
            'Standard',
            $this->createSniff()->withCode('Standard.Category.SniffName')->getStandardName()
        );
    }

    /** @test */
    public function getCategoryName()
    {
        self::assertSame(
            'Category',
            $this->createSniff()->withCode('Standard.Category.SniffName')->getCategoryName()
        );
    }

    /** @test */
    public function getSniffName()
    {
        self::assertSame(
            'SniffName',
            $this->createSniff()->withCode('Standard.Category.SniffName')->getSniffName()
        );
    }

    private function createSniff(): Sniff
    {
        return new Sniff(
            'Standard.Category.SniffName',
            '',
            [],
            new UrlList([]),
            '',
            [],
            []
        );
    }
}
\ No newline at end of file