Unit Testing Succintly Strategies For Unit Tests

Het testen van benaderingen is afhankelijk van waar u zich in het project bevindt en van uw 'budget', in termen van tijd, geld, mankracht, behoefte, enz. Idealiter wordt unit testing gebudgetteerd in het ontwikkelingsproces, maar realistisch gezien komen we vaak bestaande of verouderde programma's tegen die weinig of geen code bevatten, maar die moeten worden bijgewerkt of onderhouden. 

Het slechtste scenario is een product dat momenteel wordt ontwikkeld, maar een toenemend aantal fouten vertoont tijdens de ontwikkeling, opnieuw met weinig of geen codedekking. Als productmanager, hetzij bij het begin van een ontwikkelingsinspanning of als gevolg van het overhandigen van een bestaande applicatie, is het belangrijk om een ​​redelijke teststrategie voor eenheden te ontwikkelen. 

Vergeet niet dat unit tests meetbare voordelen voor uw project moeten bieden om de aansprakelijkheid van hun ontwikkeling, onderhoud en eigen testen te compenseren. Bovendien kan de strategie die u hanteert voor het testen van uw unit van invloed zijn op de architectuur van uw toepassing. Hoewel dit bijna altijd een goede zaak is, kan het onnodige overhead voor uw behoeften introduceren.

Vanaf vereisten

Als u een voldoende complexe toepassing start vanuit een schone lei en alles wat u in handen heeft, een reeks vereisten is, overweeg dan de volgende richtlijnen.

Voorrang geven aan computervereisten

Geef prioriteit aan de rekenvereisten van de toepassing om te bepalen waar de complexiteit ligt. Complexiteit kan worden bepaald door het aantal toestanden te ontdekken dat een bepaalde berekening moet bevatten, of het kan het resultaat zijn van een grote set invoergegevens die nodig zijn om de berekening uit te voeren, of het kan eenvoudigweg algoritmisch complex zijn, zoals het doen van foutgevallenanalyse. op de redundantiering van een satelliet. Overweeg ook waar de code in de toekomst waarschijnlijk zal veranderen als gevolg van onbekende veranderende vereisten. Hoewel dat klinkt alsof het helderziendheid vereist, kan een bekwame software-architect de code categoriseren in een algemeen doel (een gemeenschappelijk probleem oplossen) en domeinspecifiek (een specifiek probleem oplossen). De laatste wordt een kandidaat voor toekomstige verandering.

Hoewel het schrijven van eenheidstests voor triviale functies eenvoudig, snel en bevredigend is in het aantal testgevallen dat door het programma wordt doorlopen, zijn ze de minst kosteneffectieve tests - ze nemen tijd om te schrijven en omdat ze waarschijnlijk correct worden geschreven om te beginnen en ze zullen hoogstwaarschijnlijk niet veranderen in de tijd, ze zijn het minst bruikbaar als de code base van de applicatie groeit. Richt in plaats daarvan de teststrategie van uw eenheid op de code die domeinspecifiek en complex is.

Selecteer een architectuur

Een van de voordelen van het starten van een project vanuit een reeks vereisten is dat je de architectuur kunt maken (of een architectuur van derden kunt selecteren) als onderdeel van het ontwikkelingsproces. Kaders van derden waarmee u gebruik kunt maken van architecturen, zoals de omkering van controle (en het bijbehorende concept van injectie van afhankelijkheid), evenals formele architecturen zoals Model-View-Controller (MVC) en Model-View-ViewModel (MVVM) vergemakkelijken testen van eenheden om de eenvoudige reden dat een modulaire architectuur doorgaans eenvoudiger te testen is. Deze architecturen scheiden zich:

  • De presentatie (bekijk).
  • Het model (verantwoordelijk voor persistentie en gegevensweergave).
  • De controller (waar de berekeningen moeten plaatsvinden).

Hoewel sommige aspecten van het model mogelijk kandidaten voor eenheidscontrole zijn, zullen de meeste eenheidstests waarschijnlijk worden geschreven tegen methoden in de controller of het weergavemodel, waarbij de berekeningen van het model of de weergave worden geïmplementeerd.

Onderhoudsfase

Het testen van eenheden kan van nut zijn, zelfs als u betrokken bent bij het onderhoud van een applicatie, een die ofwel nieuwe functies aan een bestaande applicatie moet toevoegen of eenvoudig bugs van een oude applicatie moet oplossen. Er zijn verschillende manieren om een ​​bestaande applicatie te benaderen en vragen die ten grondslag liggen aan die benaderingen die de kosteneffectiviteit van unit testing kunnen bepalen:

  • Schrijft u unit tests alleen voor nieuwe functies en bugfixes? Lost de functie of het probleem iets op dat baat zal hebben bij regressietests, of is het een eenmalig, geïsoleerd probleem dat gemakkelijker wordt getest tijdens integratietests??
  • Begin je unit tests te schrijven tegen bestaande functies? Als dat het geval is, hoe stelt u dan prioriteit in welke functies u als eerste wilt testen?
  • Werkt de bestaande codebasis goed met unit-testen of moet de code eerst worden gerefactureerd om code-eenheden te isoleren?
  • Welke opstellingen of demontages zijn nodig voor de functie of het testen van bugs?
  • Welke afhankelijkheden kunnen worden ontdekt over de codewijzigingen die kunnen resulteren in bijwerkingen in andere code, en moeten de unit tests worden verbreed om het gedrag van de afhankelijke code te testen?

Het is niet triviaal om in de onderhoudsfase van een oudere applicatie te komen die geen unit-testing heeft - de planning, overweging en het onderzoek naar de code vereisen vaak meer middelen dan alleen het repareren van de bug. Het oordeelkundige gebruik van unit testing kan echter kosteneffectief zijn en hoewel dit niet altijd eenvoudig te bepalen is, is het de oefening waard, alleen al om een ​​dieper inzicht in de codebasis te krijgen.


Bepaal uw proces

Er zijn drie strategieën die kunnen worden gevolgd met betrekking tot het eenheidstestproces: "Test-driven development", "Code First", en hoewel het antithetisch lijkt aan het thema van dit boek, is het "No Unit Test" -proces.

Test gedreven ontwikkeling

Eén kamp is 'Testgestuurde ontwikkeling', samengevat in de volgende workflow:

Gegeven een computervereiste (zie vorige paragraaf), schrijf eerst een stomp voor de methode.

  • Als afhankelijkheden van andere objecten die nog niet zijn geïmplementeerd, vereist zijn (objecten die zijn doorgegeven als parameters aan de methode of zijn geretourneerd door de methode), implementeer deze dan als lege interfaces.
  • Als er eigenschappen ontbreken, implementeer dan stubs voor eigenschappen die nodig zijn om de resultaten te verifiëren.
  • Schrijf alle test- of testvereisten voor de set-up of demontage.
  • Schrijf de tests. De redenen om een ​​stomp te schrijven voor het schrijven van de test zijn: ten eerste, voordeel halen uit IntelliSense bij het schrijven van de test; ten tweede, om vast te stellen dat de code nog steeds compileert; en ten derde, om zeker te stellen dat de te testen methode, de parameters, interfaces en eigenschappen ervan allemaal synchroon zijn met betrekking tot naamgeving.
  • Voer de tests uit en controleer of ze falen.
  • Codeer de implementatie.
  • Voer de tests uit en controleer of ze slagen.

In de praktijk is dit moeilijker dan het lijkt. Het is gemakkelijk om ten prooi te vallen aan het schrijven van tests die niet kosteneffectief zijn, en vaak ontdekt men dat de te testen methode geen voldoende fijnmazige eenheid is om daadwerkelijk een goede kandidaat voor een test te zijn. Misschien doet de methode te veel, vereist het teveel setup of demontage, of heeft het afhankelijkheden van te veel andere objecten die allemaal moeten worden geïnitialiseerd naar een bekende staat. Dit zijn allemaal dingen die gemakkelijker worden ontdekt bij het schrijven van de code, niet bij de test.

Een voordeel van een testgestuurde aanpak is dat het proces de discipline van het testen van eenheden en het schrijven van de eenheidscontroles als eerste inboezemt. Het is eenvoudig om te bepalen of de ontwikkelaar het proces volgt. Door oefening kan men gemakkelijk worden om het proces ook kosteneffectief te maken.

Een ander voordeel van een testgestuurde aanpak is dat het van nature een soort architectuur afdwingt. Het zou absurd maar haalbaar zijn om een ​​eenheidscontrole uit te voeren die een formulier initialiseert, waarden in een controle plaatst en vervolgens een methode aanroept waarvan wordt verwacht dat deze enige berekening uitvoert op de waarden, zoals deze code zou vereisen (die hier feitelijk wordt gevonden):

private void btnCalculate_Click (object afzender, System.EventArgs e) double Principal, AnnualRate, InterestEarned; dubbele FutureValue, RatePerPeriod; int NumberOfPeriods, CompoundType; Principal = Double.Parse (txtPrincipal.Text); AnnualRate = Double.Parse (txtInterest.Text) / 100; if (rdoMonthly.Checked) CompoundType = 12; else if (rdoQuarterly.Checked) CompoundType = 4; else if (rdoSemiannually.Checked) CompoundType = 2; anders CompoundType = 1; NumberOfPeriods = Int32.Parse (txtPeriods.Text); dubbel i = Jaarlijks / CompoundType; int n = CompoundType * NumberOfPeriods; RatePerPeriod = AnnualRate / NumberOfPeriods; FutureValue = Principal * Math.Pow (1 + i, n); InterestEarned = FutureValue - Principal; txtInterestEarned.Text = InterestEarned.ToString ("C"); txtAmountEarned.Text = FutureValue.ToString ("C"); 

De voorgaande code is niet te testen omdat deze verward is met de gebeurtenishandler en de gebruikersinterface. Integendeel, men zou de samengestelde renteberekeningsmethode kunnen schrijven:

public enum CompoundType Annually = 1, SemiAnnually = 2, Quarterly = 4, Monthly = 12 private double CompoundInterestCalculation (double principal, double annualRate, CompoundType compoundType, int periodes) double annualRateDecimal = annualRate / 100.0; dubbel i = annualRateDecimal / (int) compoundType; int n = (int) samengesteldeType * perioden; double ratePerPeriod = annualRateDecimal / periods; dubbele futureValue = principal * Math.Pow (1 + i, n); double interestEaned = futureValue - principal; return interestEaned; 

waardoor een eenvoudige test kan worden geschreven:

[TestMethod] public void CompoundInterestTest () double interest = CompoundInterestCalculation (2500, 7.55, CompoundType.Monthly, 4); Assert.AreEqual (878.21, rente, 0.01); 

Bovendien zou het eenvoudig zijn om met behulp van geparametriseerde tests elk type verbinding, een reeks van jaren en verschillende rente- en hoofdbedragen te testen..

De testgestuurde aanpak eigenlijk vergemakkelijkt een meer geformaliseerd ontwikkelingsproces door de ontdekking van werkelijke testbare eenheden en deze te isoleren van grensverleggende afhankelijkheden.

Code eerst, test tweede

Eerst coderen is natuurlijker, alleen al omdat dat de gebruikelijke manier is waarop applicaties worden ontwikkeld. De vereisten en de implementatie ervan lijken op het eerste gezicht ook gemakkelijk genoeg, zodat het schrijven van verschillende unit-tests een slecht tijdsbestek lijkt. Andere factoren, zoals deadlines, kunnen een project dwingen tot een "krijg de code geschreven om het ontwikkelingsproces te kunnen verzenden".

Het probleem met de code-first benadering is dat het gemakkelijk is om code te schrijven die de soort test vereist die we eerder zagen. Code vereist eerst een actieve discipline om de geschreven code te testen. Deze discipline is ongelooflijk moeilijk te bereiken, vooral omdat er altijd de volgende nieuwe functie moet worden geïmplementeerd.

Het vereist ook intelligentie, zo u wilt, om te voorkomen dat u verwarde, grensovergangscode schrijft en de discipline om dit te doen. Wie heeft niet op een knop in de Visual Studio-ontwerper geklikt en de berekening van de gebeurtenis gecodeerd daar in het deel dat Visual Studio voor u maakt? Het is gemakkelijk en omdat de tool je in die richting stuurt, zal de naïeve programmeur denken dat dit de juiste manier is om te coderen.

Deze aanpak vereist een zorgvuldige afweging van de vaardigheden en discipline van uw team en vereist nauwere monitoring van het team, vooral tijdens stressvolle periodes waarin gedisciplineerde benaderingen de neiging hebben om in te storten. Toegegeven, een testgestuurde discipline kan ook worden weggegooid als deadlines opdoemen, maar dat is meestal een bewuste keuze om een ​​uitzondering te maken, terwijl dit in een code first approach gemakkelijk regel kan worden..

Geen unit-tests

Alleen omdat je geen eenheidsonderzoeken hebt, wil dat nog niet zeggen dat je testen opgeeft. Het kan eenvoudigweg zo zijn dat het testen de nadruk legt op acceptatietestprocedures of integratietesten.

Testsstrategieën in balans brengen

Een kosteneffectief testproces voor eenheden vereist een balans tussen testgestuurde ontwikkeling, code als eerste, test als tweede en 'test sommige andere wegen'-strategieën. De kosteneffectiviteit van het testen van eenheden moet altijd worden overwogen, evenals factoren zoals de ervaring van de ontwikkelaars in het team. Als manager wil je misschien niet horen dat een testgestuurde aanpak een goed idee is als je team redelijk groen is en je het proces nodig hebt om discipline en aanpak bij te brengen.