unit-testing
Enhetstestning: bästa metoder
Sök…
Introduktion
Ett enhetstest är den minsta testbara delen av en applikation som funktioner, klasser, procedurer, gränssnitt. Enhetstestning är en metod genom vilken enskilda källkodsenheter testas för att avgöra om de är lämpliga att använda. Enhetstester skrivs och utförs i princip av programvaruutvecklare för att säkerställa att koden uppfyller dess design och krav och fungerar som förväntat.
Bra namngivning
Vikten av bra namngivning kan bäst illustreras med några dåliga exempel:
[Test]
Test1() {...} //Cryptic name - absolutely no information
[Test]
TestFoo() {...} //Name of the function - and where can I find the expected behaviour?
[Test]
TestTFSid567843() {...} //Huh? You want me to lookup the context in the database?
Bra tester behöver namn och namn. Bra test testar inte metoder, testscenarier eller krav.
Bra namngivning ger också information om kontext och förväntat beteende. Idealt, när testet misslyckas på din byggmaskin, bör du kunna bestämma vad som är fel utan att titta på testkoden, eller ännu hårdare, med nödvändigheten att felsöka den.
Bra namngivning sparar tid för att läsa kod och felsöka:
[Test]
public void GetOption_WithUnkownOption_ReturnsEmptyString() {...}
[Test]
public void GetOption_WithUnknownEmptyOption_ReturnsEmptyString() {...}
För nybörjare kan det vara bra att starta testnamnet med EnsureThat_ eller liknande prefix. Börja med ett "Säkerställ att_" hjälper till att börja tänka på scenariot eller kravet, som behöver ett test:
[Test]
public void EnsureThat_GetOption_WithUnkownOption_ReturnsEmptyString() {...}
[Test]
public void EnsureThat_GetOption_WithUnknownEmptyOption_ReturnsEmptyString() {...}
Namngivning är också viktigt för testfixturer. Namnge testfixturen efter klassen som testades:
[TestFixture]
public class OptionsTests //tests for class Options
{
...
}
Den slutliga slutsatsen är:
Bra namngivning leder till goda tester som leder till god design i produktionskoden.
Från enkel till komplex
Samma som med skrivklasser - börja med enkla fall, lägg sedan till krav (aka tester) och implementering (aka produktionskod) från fall till fall:
[Test]
public void EnsureThat_IsLeapYearIfDecimalMultipleOf4() {...}
[Test]
public void EnsureThat_IsNOTLeapYearIfDecimalMultipleOf100 {...}
[Test]
public void EnsureThat_IsLeapYearIfDecimalMultipleOf400 {...}
Glöm inte refactoring-steget, när du är klar med kraven - först refactor koden, sedan refactor testerna
När du är klar bör du ha en fullständig, uppdaterad och läSBAR dokumentation av din klass.
MakeSut-koncept
Testkod har samma kvalitetskrav som produktionskod. MakeSut ()
- förbättrar läsbarheten
- kan lätt refactored
- stöder perfekt beroendeinjektion.
Här är konceptet:
[Test]
public void TestSomething()
{
var sut = MakeSut();
string result = sut.Do();
Assert.AreEqual("expected result", result);
}
Den enklaste MakeSut () returnerar bara den testade klassen:
private ClassUnderTest MakeSUT()
{
return new ClassUnderTest();
}
När beroenden behövs kan de injiceras här:
private ScriptHandler MakeSut(ICompiler compiler = null, ILogger logger = null, string scriptName="", string[] args = null)
{
//default dependencies can be created here
logger = logger ?? MockRepository.GenerateStub<ILogger>();
...
}
Man kan säga att MakeSut bara är ett enkelt alternativ för inställnings- och nedbrytningsmetoder som tillhandahålls av Testrunner-ramverk och kanske anser att dessa metoder är en bättre plats för testspecifik installation och nedbrytning.
Alla kan själv bestämma vilket sätt de ska använda. För mig ger MakeSut () bättre läsbarhet och mycket mer flexibilitet. Sist men inte minst är konceptet oberoende av alla testrunner-ramar.