Introduktion til test i softwareudvikling
Test er en central del af softwareudviklingsprocessen, der sikrer, at applikationer fungerer som forventet og opfylder de specificerede krav. Gennem systematisk testning kan udviklere identificere og rette fejl tidligt i udviklingscyklussen, hvilket forbedrer softwarekvaliteten og reducerer omkostningerne ved fejlrettelser senere i processen.
Historisk perspektiv på softwaretest
Softwaretestning har udviklet sig betydeligt siden computerens tidlige dage:
-
1950’erne: I denne periode blev testning ofte betragtet som synonymt med debugging. Fokus var primært på at rette fejl frem for systematisk at identificere dem.
-
1960’erne og 1970’erne: Med den stigende kompleksitet af softwaresystemer begyndte testning at blive mere formaliseret. Adskillelsen mellem debugging og testning blev tydeligere, og der blev lagt vægt på at verificere, at software opfyldte de specificerede krav.
-
1980’erne og fremefter: Testning udviklede sig yderligere med introduktionen af forskellige testniveauer og -typer, hvilket førte til en mere struktureret tilgang til kvalitetssikring i softwareudvikling.
Typer af test
Der findes flere forskellige testtyper, hver med sit specifikke fokus og formål:
Enhedstest (Unit Test)
Enhedstest fokuserer på at verificere funktionaliteten af individuelle komponenter eller funktioner i koden isoleret fra resten af systemet. Målet er at sikre, at hver enkelt del fungerer korrekt under forskellige betingelser.
Eksempel: Test af en enkelt metode i en klasse for at sikre, at den returnerer det forventede resultat for givne input.
Integrationstest
Integrationstest evaluerer, hvordan forskellige komponenter i systemet arbejder sammen. Disse tests sikrer, at moduler eller services integreres korrekt og kommunikerer som forventet.
Eksempel: Test af dataflowet mellem en database og en webservice for at sikre, at data hentes og sendes korrekt.
Systemtest
Systemtest indebærer en fuldstændig test af det samlede system (typisk inkl. brugerflade) for at sikre, at det opfylder de specificerede krav. Denne testtype fokuserer på både funktionelle og ikke-funktionelle aspekter af applikationen.
Eksempel: Test af en applikations samlede brugerflade, funktionalitet, ydeevne og sikkerhed for at sikre, at den fungerer korrekt under realistiske forhold.
Accepttest
Accepttest udføres for at bekræfte, at systemet opfylder de forretningsmæssige krav og er klar til implementering. Disse tests udføres ofte af slutbrugere eller kunder for at sikre, at systemet lever op til deres forventninger.
Eksempel: En kunde tester en ny funktion i en softwareapplikation for at sikre, at den opfylder deres behov og fungerer som forventet i deres arbejdsgange.
Testværktøjer og -rammer
I C#-udvikling er der flere populære testværktøjer til rådighed:
MSTest
MSTest er Microsofts egen testframework, der integreres tæt med Visual Studio. Det tilbyder enkle attributter og metoder til at definere og køre tests.
NUnit
NUnit er en populær open source-testframework inspireret af JUnit for Java. Det tilbyder en bred vifte af funktioner og er kendt for sin fleksibilitet og brugervenlighed.
xUnit.net
xUnit.net er en moderne testframework designet til .NET-applikationer. Det introducerer flere forbedringer i forhold til tidligere frameworks og fremmer bedste praksis inden for testudvikling.
Playwright
Playwright er et open source værktøj til browserautomatisering, der gør det muligt at skrive og køre browserbaserede tests i C# og andre sprog. Det understøtter automatisering af forskellige browsere og enheder.
Moq
Moq er et populært mock-framework til .NET, der gør det nemt at oprette og administrere mockobjekter til testformål. Det er nyttigt til at simulere eksterne afhængigheder og isolere enhedstests.
Moq er smart, fordi det hjælper dig med at isolere din kode i unit tests ved at simulere (mocke) afhængigheder, så du kan teste en komponent uden at være afhængig af dens rigtige implementeringer. Det gør dine tests hurtigere, mere stabile og lettere at skrive. Her er et par fordele:
- Isolation af testede komponenter
-
Når du tester en klasse, vil du ofte have afhængigheder (f.eks. database, API’er eller eksterne tjenester). Moq giver dig mulighed for at erstatte disse afhængigheder med mocks, så du kun tester din egen kode.
-
Undgå tunge afhængigheder
-
Hvis din kode interagerer med en database eller en fil, kan det gøre testen langsom og upålidelig. Moq lader dig simulere data i stedet for at bruge rigtige databasekald.
-
Forudsigelige testforhold
-
Med Moq kan du definere præcis, hvordan en afhængighed skal opføre sig i en test. Det betyder, at dine tests ikke påvirkes af eksterne faktorer (som netværksfejl eller databaseændringer).
-
Lettere test af kanttilfælde
- Du kan simulere edge cases som fejl, null-værdier eller undtagelser, hvilket kan være svært med rigtige afhængigheder.
Hvornår skal man bruge Moq?
- Når du laver unit tests for klasser, der har eksterne afhængigheder.
- Når du ønsker at teste logik uden at være afhængig af rigtige data.
- Når du arbejder med dependency injection, hvor services ofte er interfaces eller abstrakte klasser.
- Når du har komplekse scenarier, der kræver bestemte svar fra afhængigheder.
Test runners
Test runners er værktøjer, der udfører tests og rapporterer resultaterne. I .NET-økosystemet er der flere test runners tilgængelige:
Visual Studio Test Platform (VSTest)
VSTest er en fleksibel og udvidelig testplatform fra Microsoft, der understøtter kørsel af tests skrevet i forskellige testframeworks. Det integreres med Visual Studio og kan også bruges fra kommandolinjen.
ReSharper
ReSharper er et produktivitetsværktøj til Visual Studio, der inkluderer en test runner, som understøtter flere testframeworks og tilbyder avancerede funktioner som kodeanalyse og refaktorering.
TestDriven.Net
TestDriven.Net er en Visual Studio-udvidelse, der gør det muligt at køre tests direkte fra kodeeditoren med understøttelse af flere testframeworks.
Test-Driven Development (TDD)
Test-Driven Development (TDD) er en softwareudviklingsmetode (del af agil udvikling), hvor udviklere skriver tests før selve koden. Denne tilgang sikrer, at koden opfylder de specificerede krav og letter vedligeholdelsen. TDD-processen involverer typisk følgende trin:
- Skriv en test: Udvikleren skriver en enhedstest for den ønskede funktionalitet.
- Kør alle tests: Den nye test skal fejle, da den tilsvarende funktionalitet endnu ikke er implementeret.
- Implementér koden: Skriv den minimale kode, der er nødvendig for at få testen til at bestå.
- Kør alle tests igen: Hvis alle tests består, kan udvikleren fortsætte; ellers rettes koden, indtil testen består.
- Refaktorér koden: Forbedr koden uden at ændre dens funktionalitet for at sikre renhed og vedligeholdelse.
Denne cyklus gentages for hver ny funktionalitet, hvilket resulterer i en omfattende testsuite, der dækker hele applikationen.
TDD har fået en stigende popularitet på grund af den større og større brug af AI, hvor det er vigtigt at kunne dokumentere hvad man ønsker at opnå - og det er netop hvad TDD gør.
Test i Continuous Integration/Continuous Deployment (CI/CD) pipelines
I moderne softwareudvikling er test en integreret del af Continuous Integration (CI) og Continuous Deployment (CD) pipelines. CI/CD-pipelines automatiserer processen med at bygge, teste og implementere kode, hvilket sikrer hurtigere og mere pålidelige releases.
Ved at inkludere automatiserede tests i CI/CD-pipelines kan udviklingsteams:
- Identificere fejl tidligt: Automatiserede tests køres ved hver kodeændring, hvilket muliggør hurtig opdagelse og rettelse af fejl.
- Sikre kodekvalitet: Regelmæssig testning sikrer, at ny kode ikke introducerer regressioner eller bryder eksisterende funktionalitet.
- Fremskynde leverancer: Automatisering reducerer den tid, det tager at teste og implementere kode, hvilket muliggør hyppigere releases.
Implementering af tests i CI/CD-pipelines understøtter DevOps-praksis og fremmer en kultur med kontinuerlig forbedring og samarbejde mellem udviklings- og driftsteams.
Xunit
xUnit er et open source testframework, der bruges til at skrive enhedstests i .NET. Det er kendt for sin enkle syntaks, moderne arkitektur og effektive eksekvering af tests. xUnit er designet til at understøtte nyeste features i .NET og tilbyder bedre isolation af tests sammenlignet med ældre frameworks som MSTest og NUnit.
I modsætning til MSTest og NUnit benytter xUnit konstruktører til test-setup, hvilket eliminerer behovet for [TestInitialize]
- og [TestCleanup]
-metoder. Dette giver en mere objektorienteret og intuitiv tilgang til testning. Derudover understøtter xUnit fuldt ud asynkrone tests, hvilket gør det ideelt til test af kode, der arbejder med f.eks. HTTP-forespørgsler eller databaser.
xUnit understøtter også delte testkontekster gennem [Collection]
og [ClassFixture]
, hvilket gør det muligt at genbruge testopsætning på tværs af flere tests. Dette er særligt nyttigt i integrationstest, hvor flere tests har brug for adgang til den samme testinstans, f.eks. en databaseforbindelse eller en mocket tjeneste.
Nogle af de mest anvendte funktioner i xUnit inkluderer [Fact]
til almindelige testmetoder, [Theory]
til datadrevne tests samt [ClassFixture]
til at håndtere fælles testopsætning. I det følgende gennemgås de vigtigste testfunktioner i xUnit med eksempler.
Fact
Fact
-attributten angiver, at en metode er en testmetode, der ikke tager parametre. Den bruges til at teste en specifik adfærd eller en tilstand i koden.
Forventet exception
I xUnit kan du bruge metoden Assert.Throws
til at verificere, at en bestemt undtagelse kastes under en test. Her er et eksempel:
public class ExceptionTests
{
[Fact]
public void TestForArgumentNullException()
{
// Arrange
var service = new MyService();
// Act & Assert
Assert.Throws<ArgumentNullException>(() => service.MyMethod(null));
}
}
I dette eksempel tester vi, at metoden MyMethod
i klassen MyService
kaster en ArgumentNullException
, når den kaldes med en null
-parameter. Assert.Throws
sikrer, at testen kun består, hvis den specificerede undtagelse kastes.
Theory
Theory
-attributten bruges sammen med InlineData
, ClassData
, eller MemberData
attributterne for at køre den samme testmetode med forskellige input. Det er nyttigt, når du ønsker at teste en funktion med forskellige værdier.
public class CalculatorTests
{
[Theory]
[InlineData(2, 2, 4)]
[InlineData(2, -2, 0)]
public void TestAddition(int a, int b, int expected)
{
Assert.Equal(expected, a + b);
}
}
InlineData
InlineData
-attributten bruges sammen med [Theory]
til at køre en testmetode med forskellige inputværdier. Dette gør det muligt at genbruge testlogik, hvor den samme test køres flere gange med forskellige parametre.
Grundlæggende eksempel
Her testes en simpel addition med forskellige værdier:
public class MathTests
{
[Theory]
[InlineData(2, 3, 5)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
public void Add_ShouldReturnExpectedSum(int a, int b, int expected)
{
Assert.Equal(expected, a + b);
}
}
Her testes en metode, der afgør, om et tal er lige:
public static class NumberUtils
{
public static bool IsEven(int number) => number % 2 == 0;
}
public class NumberUtilsTests
{
[Theory]
[InlineData(2, true)]
[InlineData(3, false)]
[InlineData(0, true)]
[InlineData(-4, true)]
public void IsEven_ShouldReturnCorrectResult(int input, bool expected)
{
bool result = NumberUtils.IsEven(input);
Assert.Equal(expected, result);
}
}
InlineData
er også nyttigt til at teste metoder, der manipulerer strenge:
public static class StringUtils
{
public static string ToUpperCase(string input) => input.ToUpper();
}
public class StringUtilsTests
{
[Theory]
[InlineData("hello", "HELLO")]
[InlineData("world", "WORLD")]
[InlineData("C#", "C#")]
public void ToUpperCase_ShouldConvertToUppercase(string input, string expected)
{
string result = StringUtils.ToUpperCase(input);
Assert.Equal(expected, result);
}
}
Når man tester beregninger med double eller float, kan InlineData
stadig bruges, men med en tolerance på decimaler:
public static class MathOperations
{
public static double Multiply(double a, double b) => a * b;
}
public class MathOperationsTests
{
[Theory]
[InlineData(2.5, 2, 5.0)]
[InlineData(1.1, 3.0, 3.3)]
[InlineData(-2.0, 2.5, -5.0)]
public void Multiply_ShouldReturnCorrectProduct(double a, double b, double expected)
{
double result = MathOperations.Multiply(a, b);
Assert.Equal(expected, result, precision: 1);
}
}
Assert.Equal(expected, result, precision: 1)
bruges til at håndtere afrundingsfejl i floating-point operationer.
ClassData og MemberData
Disse attributter giver mulighed for at definere mere komplekse testdata. ClassData
henviser til en klasse, der implementerer IEnumerable<object[]>
, og MemberData
henviser til en metode, der returnerer en sådan samling.
Collection og ClassFixture i xUnit
Når der testes kode, der kræver delt state (f.eks. databaseforbindelser, eksterne API-kald eller mockede services), kan xUnit håndtere dette via Collection
og ClassFixture
, Disse mekanismer sikrer, at testmiljøet kan deles mellem flere tests uden at geninitialisere det unødigt.
Skip
Skip
-attributten bruges til midlertidigt at springe en test over. Dette kan være nyttigt, hvis en test er brudt på grund af en midlertidig kodeændring, og du vil undgå at markere den som fejlende, mens du arbejder på en løsning.
public class TemporarySkippedTests
{
[Fact(Skip = "Midlertidigt deaktiveret under udvikling")]
public void TestSkipped()
{
// Test logik
}
}
Disse attributter er blot nogle få eksempler på, hvordan Xunit kan bruges til at skrive effektive og vedligeholdelsesvenlige unit tests. Ved at anvende disse kan udviklere skabe robuste testsuites, der sikrer, at softwaren fungerer korrekt og fortsat vil gøre det gennem fremtidige ændringer.
Brug af async i xUnit
Når man tester asynkrone metoder i .NET, er det vigtigt at sikre, at testene kører korrekt og håndterer async
/await
uden blokering. xUnit understøtter asynkrone tests naturligt, da det tillader testmetoder at returnere en Task
.
Du bør bruge asynkrone tests, når du:
- Tester ægte asynkrone operationer (f.eks. databasekald, HTTP-forespørgsler, filhåndtering).
- Vil undgå deadlocks ved at bruge
await
korrekt. - Forhindre blokering af testtråden, hvilket kan gøre testene langsommere.
xUnit tillader, at [Fact]
- og [Theory]
-testmetoder returnerer en Task
. Dette gør det muligt at teste asynkrone metoder direkte uden brug af Task.Wait()
eller Task.Result()
, som kan forårsage deadlocks.
public class AsyncTests
{
private async Task<int> FetchDataAsync()
{
await Task.Delay(100); // Simulerer en async operation
return 42;
}
[Fact]
public async Task FetchDataAsync_ShouldReturn42()
{
int result = await FetchDataAsync();
Assert.Equal(42, result);
}
}
Test af async-metoder, der smider undtagelser
Når man tester asynkrone metoder, der kan kaste undtagelser, skal await
bruges direkte med Assert.ThrowsAsync
.
public class ExceptionService
{
public async Task<string> FailAsync()
{
await Task.Delay(50);
throw new InvalidOperationException("Fejl opstod!");
}
}
public class AsyncExceptionTests
{
[Fact]
public async Task FailAsync_ShouldThrowInvalidOperationException()
{
var service = new ExceptionService();
await Assert.ThrowsAsync<InvalidOperationException>(() => service.FailAsync());
}
}
Brug af IDisposable i xUnit
Når man tester kode, der bruger ressourcer, der kræver oprydning (f.eks. databaseforbindelser, filer, netværksforbindelser eller mockede tjenester), kan IDisposable
-interfacet** bruges til at rydde op efter tests. Dette sikrer, at ressourcer frigives korrekt, og at tests ikke påvirker hinanden negativt.
- Forhindrer ressourcelæk (f.eks. åbne databaseforbindelser eller filer).
- Sikrer ren testtilstand ved at rydde op mellem tests.
- Bruges ofte i
ClassFixture
- ellerCollectionFixture
-mønstre til at håndtere ressourcer, der skal deles mellem tests.
I dette eksempel har vi en klasse, der simulerer en databaseforbindelse, og vi bruger IDisposable
til at lukke den korrekt efter brug.
Testklasse med IDisposable
public class DatabaseConnection : IDisposable
{
public bool IsConnected { get; private set; }
public DatabaseConnection()
{
// Simulerer åbning af en databaseforbindelse
IsConnected = true;
Console.WriteLine("Databaseforbindelse åbnet.");
}
public void Dispose()
{
// Simulerer lukning af databaseforbindelse
IsConnected = false;
Console.WriteLine("Databaseforbindelse lukket.");
}
}
public class DatabaseTests : IDisposable
{
private readonly DatabaseConnection _database;
public DatabaseTests()
{
_database = new DatabaseConnection();
}
[Fact]
public void Database_ShouldBeConnected()
{
Assert.True(_database.IsConnected);
}
public void Dispose()
{
_database.Dispose(); // Lukker forbindelse efter testen
}
}
- Databaseforbindelsen åbnes i testklassens konstruktør.
- Testen kører og verificerer, at forbindelsen er aktiv.
Dispose
kaldes automatisk efter testen og lukker forbindelsen.
Når xUnit eksekverer testen: - DatabaseTests()
opretter DatabaseConnection
. - Testmetoderne kører. - Dispose()
kaldes automatisk, hvilket sikrer oprydning.
Eksempler
Eksempel på en simpel test
Her er et eksempel på en simpel test, der tester en metode til at tilføje to tal. Normalt vil man have typer og test i separate projekter, men bare for at prøve det kan du på terminal i en mappe skrive:
og så erstatte UnitTest1.cs
med følgende:
namespace SimpleCalculator;
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
// Bør nok være checked - eller?? (se testen Add_ShouldOverflow)
// checked
// {
// return a + b;
// }
}
}
public class CalculatorTests
{
[Fact]
public void Add_ShouldReturnCorrectSum()
{
// Arrange
var calculator = new Calculator();
// Act
int result = calculator.Add(2, 3);
// Assert
Assert.Equal(5, result);
}
[Theory]
[InlineData(0, 0, 0)]
[InlineData(1, 1, 2)]
[InlineData(-1, -1, -2)]
[InlineData(-1, 1, 0)]
[InlineData(1, -1, 0)]
public void Add_ShouldReturnCorrectSumMultible(int a, int b, int expected)
{
// Arrange
var calculator = new Calculator();
// Act
double result = calculator.Add(a, b);
// Assert
Assert.Equal(expected, result);
}
// BEMÆRK - Denne test fejler
[Fact]
public void Add_ShouldOverflow()
{
// Arrange
var calculator = new Calculator();
// Assert
Assert.Throws<OverflowException>(() => calculator.Add(int.MaxValue, 1));
}
}
Bemærk, at Add_ShouldOverflow
-testen fejler, da der ikke er håndteret overflow i Add
-metoden. Dette kan løses ved at bruge checked
-blokken til at generere en undtagelse, hvis der opstår en overflow.
Eksempel på en simpel test med flere projekter
Her konsol/vscode men det samme er muligt i VS hvor der i øvrigt er indbygget avanceret funktionalitet til at afvikle og debugge tests (mv). Udgangspunktet er en metode der konverterer hastighed MPH til KMT.
Opret projekter
Lad os først oprette projekter og løsning. I en konsol i en tom mappe skriv følgende (kopier det hele til prompt):
mkdir SpeedConverter
cd SpeedConverter
dotnet new sln -n SpeedConverter
dotnet new classlib -n SpeedConverter.Core
dotnet sln SpeedConverter.sln add SpeedConverter.Core/SpeedConverter.Core.csproj
dotnet new console -n SpeedConverter.UI
dotnet sln SpeedConverter.sln add SpeedConverter.UI/SpeedConverter.UI.csproj
dotnet new classlib -n SpeedConverter.Test
dotnet sln SpeedConverter.sln add SpeedConverter.Test/SpeedConverter.Test.csproj
dotnet add SpeedConverter.UI/SpeedConverter.UI.csproj reference SpeedConverter.Core/SpeedConverter.Core.csproj
dotnet add SpeedConverter.Test/SpeedConverter.Test.csproj reference SpeedConverter.Core/SpeedConverter.Core.csproj
cd SpeedConverter.Test
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package xunit
dotnet add package xunit.runner.visualstudio
dotnet add package xunit.runner.console
cd ..
Det vil oprette en løsning og tre projekter med de ønskede referencer (UI -> Core og Test -> Core)
- Core (til kode der ønskes at deles)
- UI (til en hurtige console UI test)
- Test (til unit test)
Prøv lige en
for at sikre at alt er ok.
Tilføj beregning til test
I mappen /SpeedConverter.Core
kan du fjerne Class1.cs
og tilføje SpeedConverterFunctions.cs
med følgende indhold:
namespace SpeedConverter.Core
{
public static class SpeedConverterFunctions
{
public static double ConvertMphToKmt(double mph)
{
if (mph < 0) throw new ArgumentException("mph must be positive");
return mph * 1.60934;
}
}
}
UI test
I mappen /SpeedConverter.UI
kan du ændre Program.cs
til
var res = SpeedConverter.Core.SpeedConverterFunctions.ConvertMphToKmt(10);
System.Console.WriteLine(res.ToString("N2"));
og prøv fra UI-mappe en
Det burde vise at beregningen virker:
Unit test
I mappen /SpeedConverter.Test
kan du fjerne Class1.cs
og tilføje SpeedConverterFunctionsTests.cs
med følgende indhold:
using SpeedConverter.Core;
using Xunit;
namespace SpeedConverter.Tests
{
public class SpeedConverterFunctionsTests
{
// Test to ensure positive mph values are correctly converted to kmt.
[Theory]
[InlineData(0, 0)]
[InlineData(1, 1.60934)]
[InlineData(60, 96.5604)]
public void ConvertMphToKmt_ReturnsCorrectValue(double mph, double expectedKmt)
{
// Act
double result = SpeedConverterFunctions.ConvertMphToKmt(mph);
// Assert
Assert.Equal(expectedKmt, result, 5); // 5 is the number of decimal places for precision
}
// Test to ensure negative mph values throw an ArgumentException.
[Fact]
public void ConvertMphToKmt_ThrowsArgumentException_ForNegativeValue()
{
// Act & Assert
Assert.Throws<ArgumentException>(() => SpeedConverterFunctions.ConvertMphToKmt(-1));
}
// Test to ensure very large mph values are correctly converted to kmt.
[Fact]
public void ConvertMphToKmt_HandlesLargeValues()
{
// Arrange
double largeMphValue = 1e6; // 1 million mph
double expectedKmt = largeMphValue * 1.60934;
// Act
double result = SpeedConverterFunctions.ConvertMphToKmt(largeMphValue);
// Assert
Assert.Equal(expectedKmt, result);
}
}
}
Fra /SpeedConverter
burde du nu kunne køre tests med dotnet test
C:\Temp\SpeedConverter>dotnet test
Determining projects to restore...
All projects are up-to-date for restore.
SpeedConverter.Core -> C:\Temp\SpeedConverter\SpeedConverter.Core\bin\Debug\net7.0\SpeedConverter.Core.dll
SpeedConverter.Test -> C:\Temp\SpeedConverter\SpeedConverter.Test\bin\Debug\net7.0\SpeedConverter.Test.dll
Test run for C:\Temp\SpeedConverter\SpeedConverter.Test\bin\Debug\net7.0\SpeedConverter.Test.dll (.NETCoreApp,Version=v7.0)
Microsoft (R) Test Execution Command Line Tool Version 17.5.0 (x64)
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
Passed! - Failed: 0, Passed: 5, Skipped: 0, Total: 5, Duration: 9 ms - SpeedConverter.Test.dll (net7.0)
Eksempel på brug af MOQ
Her er et eksempel på en simpel test, der “mocker” en ekstern service ved hjælp af Moq.
Erstat UnitTest1.cs
med følgende:
using System;
using System.Threading.Tasks;
using Moq;
using Xunit;
// Interface der simulerer det eksterne API
public interface IWeatherApiClient
{
Task<string> GetWeatherAsync(string city);
}
// Service, der bruger API'et
public class WeatherService
{
private readonly IWeatherApiClient _weatherApiClient;
public WeatherService(IWeatherApiClient weatherApiClient)
{
_weatherApiClient = weatherApiClient;
}
public async Task<string> GetWeatherForCityAsync(string city)
{
return await _weatherApiClient.GetWeatherAsync(city);
}
}
// Unit test med Moq
public class UnitTest1
{
[Fact]
public async Task GetWeatherForCityAsync_ShouldReturnRandomWeather()
{
// Arrange
var weatherTypes = new[] { "Sunny", "Rainy", "Snowy", "Cloudy", "Windy" };
var random = new Random();
// Mock setup for IWeatherApiClient
var mockApi = new Mock<IWeatherApiClient>();
// Configure the mock to return a random weather type for any city
mockApi.Setup(api => api.GetWeatherAsync(It.IsAny<string>()))
.ReturnsAsync(() => weatherTypes[random.Next(weatherTypes.Length)]);
var weatherService = new WeatherService(mockApi.Object);
// Act
string weather1 = await weatherService.GetWeatherForCityAsync("Copenhagen");
string weather2 = await weatherService.GetWeatherForCityAsync("New York");
string weather3 = await weatherService.GetWeatherForCityAsync("Tokyo");
// Assert
Assert.Contains(weather1, weatherTypes);
Assert.Contains(weather2, weatherTypes);
Assert.Contains(weather3, weatherTypes);
// Udskriv til debugging (valgfrit)
Console.WriteLine($"Weather in Copenhagen: {weather1}");
Console.WriteLine($"Weather in New York: {weather2}");
Console.WriteLine($"Weather in Tokyo: {weather3}");
}
}
Denne test bruger Moq til at simulere et eksternt API-kald og returnere tilfældige vejrtyper for forskellige byer. Ved at mocke API’et kan testen køres uafhængigt af det faktiske API og fokusere på at teste WeatherService-logikken.
Eksempel på brug af DI
Her er et eksempel på en simpel test, der bruger Dependency Injection (DI) til at injicere en mockservice i testen.
Erstat UnitTest1.cs
med følgende:
public interface ITilfældighedsGenerator {
int FindTilfældigtTal();
}
public class Terning
{
public int Værdi { get; private set; }
private ITilfældighedsGenerator rnd;
public void Ryst()
{
this.Værdi = rnd.FindTilfældigtTal(); ;
}
public Terning(ITilfældighedsGenerator generator)
{
this.rnd = generator;
this.Ryst();
}
}
//
class TilfældighedsGeneratorRandom : ITilfældighedsGenerator
{
public int FindTilfældigtTal()
{
return new System.Random().Next(1, 7);
}
}
class TilfældighedsGeneratorSnyd : ITilfældighedsGenerator
{
private readonly int snyd;
public TilfældighedsGeneratorSnyd(int snyd)
{
this.snyd = snyd;
}
public int FindTilfældigtTal()
{
return this.snyd;
}
}
public class TestAfTerning
{
[Fact]
public void BurdeReturnereEnSekser()
{
Terning t = new Terning(new TilfældighedsGeneratorSnyd(6));
t.Ryst();
Assert.Equal(6, t.Værdi);
}
}