"BDD is TDD done right" har någon sagt och jag är benägen att stämma in i den kören. Jag har inte jättelång erfarenhet av testdriven utveckling men 2 saker har hjälpt mig mycket i att minska underhållet av testerna när man lägger till funktionalitet till metoder som redan har tester.
1. Att bara ha ett logiskt test i varje testmetod. Tex att bara ha ett mockat object och låt de andra objekten som skall interagera vara stubbar som bara hjälper till att sätt upp förutsättningarna för att kunna testa interaktionen med det mockade objektet. Visst kan man ha flera asserts i en testmetod men de skall testa samma sak, man kanske kollar att metoden inte returnerar null och sedan att den returnerar rätt värde/objekt men 2 asserts men det är bara för att underlätta när det går fel genom att felmeddelandet blir tydligare.
2. Att med hjälp av klassnamn och metodnamn för testerna utförlig beskriva vad som testas, i c# kan både klassnamn och metodnamn vara 512 tecken långa så det kan bli en ordentlig menig. Detta gör man för att så detaljerat som möjligt beskriva det beteende som testas så att den som senare får ett felande test med hjälp av den texten kan förstå vad som testas utan att behöva analysera varken testkod eller kod under test. För i ärlighetens namn så är det inte roligt att läsa någon annans kod eller sin egen gamla heller för den delen.
Om man med hjälp av testklasser och testmetoder kan beskriva vad som testas så får man ett bra underlag för vad system kan och gör. Med lite xslt transformering kan testresultatet bli en riktigt läsbar upplevelse och alltid aktuell dokumentation.
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="smallcase" select="'abcdefghijklmnopqrstuvwxyz'" />
<xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" />
<xsl:template match="test-results">
<html>
<head>
<style>
<![CDATA[
body { font : 1em verdana; }
li { list-style-type : none; padding-top:0.5em; }
li li { padding-top:0; font-size : .9em; }
li.failed { color:red; }
]]>
</style>
</head>
<body>
<ul>
<xsl:apply-templates select="//test-suite"/>
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="test-suite">
<xsl:if test="not(contains(@name, '\'))">
<xsl:variable name="class-name" select="@name" />
<li>
<xsl:variable name="context">
<xsl:call-template name="remove-under-score">
<xsl:with-param name="text" select="$class-name" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$context"/>
<ul>
<xsl:for-each select=".//test-case">
<xsl:variable name="test-name">
<xsl:call-template name="test-case">
<xsl:with-param name="class-name" select="$class-name"/>
<xsl:with-param name="full-method-name" select="@name"/>
</xsl:call-template>
</xsl:variable>
<xsl:element name="li">
<xsl:if test="@success = 'False'">
<xsl:attribute name="class">
<xsl:value-of select="'failed'"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="$test-name"/>
</xsl:element>
</xsl:for-each>
</ul>
</li>
</xsl:if>
</xsl:template>
<xsl:template name="test-case">
<xsl:param name="class-name"/>
<xsl:param name="full-method-name"/>
<xsl:variable name="method-name">
<xsl:call-template name="remove-namespace">
<xsl:with-param name="class-name" select="$class-name"/>
<xsl:with-param name="full-method-name" select="$full-method-name"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="method-sentence">
<xsl:call-template name="remove-under-score">
<xsl:with-param name="text" select="$method-name"/>
</xsl:call-template>
</xsl:variable>
<xsl:text>- </xsl:text>
<xsl:value-of select="$method-sentence"/>
</xsl:template>
<xsl:template name="remove-under-score">
<xsl:param name="text"/>
<xsl:value-of select="translate($text, '_', ' ')"/>
</xsl:template>
<xsl:template name="remove-namespace">
<xsl:param name="class-name"/>
<xsl:param name="full-method-name"/>
<xsl:variable name="namespace-end" select="concat($class-name, '.')"/>
<xsl:value-of select="substring-after($full-method-name, $namespace-end)"/>
</xsl:template>
<xsl:template name="remove-keywords">
<xsl:param name="text" />
<xsl:variable name="removed-Behavior">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="string($text)"/>
<xsl:with-param name="replace" select="string('Behavior')"/>
<xsl:with-param name="by" select="string('')"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="removed-Tests">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="string($removed-Behavior)"/>
<xsl:with-param name="replace" select="string('Tests')"/>
<xsl:with-param name="by" select="string('')"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$removed-Tests"/>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text, $replace)"/>
<xsl:value-of select="$by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text, $replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>