Gå til indholdet

Intro til XUnit

Unit testing er en metode, hvor individuelle dele af softwaren testes for at sikre, at de fungerer som forventet. Det er en grundlæggende praksis i softwareudvikling, der sikrer kvalitet og funktionalitet i koden. Mens der findes mange test frameworks derude, som MSTest og NUnit, skiller Xunit sig ud med sin enkelhed og fleksibilitet. Det er blevet populært blandt C# udviklere for dets klare syntaks og stærke community support.

Xunit

Xunit er et open source test framework, der bruges til at skrive unit tests i .NET. Det er kendt for sin enkle syntaks og effektive udførelse af tests. Xunit giver mulighed for at skrive rene og vedligeholdelsesvenlige tests og understøtter de nyeste features i .NET. Nogle af de mest anvendte attributter i Xunit inkluderer:

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.

public class SimpleTests
{
    [Fact]
    public void TestAddition()
    {
        Assert.Equal(4, 2 + 2);
    }
}

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 for at angive inputværdierne til en testmetode.

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 ejendom eller metode, der returnerer en sådan samling.

public class ComplexTestData : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { 1, 2, 3 };
        yield return new object[] { -4, -6, -10 };
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class CalculatorTests
{
    [Theory]
    [ClassData(typeof(ComplexTestData))]
    public void TestAddition(int a, int b, int expected)
    {
        Assert.Equal(expected, a + b);
    }
}

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.

Eksempel

Det er nemmest at forstå med et eksempel - 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 src
cd src
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

dotnet build

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.csmed 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

dotnet run

Det burde vise at beregningen virker:

16.09

Unit test

I mappen /SpeedConverter.Test kan du fjerne Class1.cs og tilføje SpeedConverterFunctionsTests.csmed 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)

Dette er blot et hurtigt eksempel men det viser ideen bag unit test.

Til orientering er ChatGPT og andre sprogmodeller super gode til at generere test - ovennævnte er klippet direkte fra ChatGPT med en prompt ala Baseret på denne klasse [indsat kode fra SpeedConverterFunctions] bedes du venligst skrive de xunit tests de finder nødvendige.