Unit-Tests mit KI generieren

von | 28.08.2025

Ein Erfahrungsbericht über die Erstellung von Unit-Tests mit ChatGPT, Claude und Gemini

Unit-Tests sind ein nützliches Hilfsmittel, um die Korrektheit, Qualität und Robustheit von Software sicherzustellen. Leider sind sie relativ zeitaufwendig und oft monoton, was sich negativ auf die Motivation von Entwicklern auswirkt und dazu führt, dass Unit-Tests manchmal vernachlässigt werden. Doch nun betritt Künstliche Intelligenz (KI) die Bühne: Sie verspricht, Entwickler beim Erstellen von Unit-Tests zu entlasten. Die spannende Frage lautet dabei: Liefert KI wirklich nützliche Tests oder sehen diese nur gut aus, ohne echten Nutzen zu bieten?

Werfen wir einen Blick auf die Erstellung von Unit-Tests mit ChatGPT, Claude und Gemini. Als Programmiersprache nutze ich C# (C Sharp).

Was sagen Sprachmodelle zum Thema Unit-Tests mit KI und welche Möglichkeiten gibt es?

Beim Thema KI bietet es sich an, selbige dazu zu befragen.

ChatGPTs Meinung zusammengefasst:

Sprachmodelle können sehr effektiv dabei helfen, Testfälle zu identifizieren, die Code Coverage zu verbessern oder Mocks/Stubs für externe Abhängigkeiten vorszuchlagen. Es gibt die Möglichkeit, Large Language Models wie GPT direkt zu verwenden oder eine Integration via Tools zu nutzen. Beispiele hierfür sind: GitHub Copilot, ReSharper AI oder Integrationen in CI/CD-Pipelines in Kombination mit statischer Analyse.

Die Modelle sind mittlerweile so gut trainiert, dass sie für viele Frameworks und Sprachen Hilfe bieten: Python (pytest, unittest), JavaScript (Jest, Mocha), Java (JUnit, TestNG, Diffblue) und C# (xUnit, NUnit). Unit-Tests sollten jedoch immer manuell überprüft werden. Tests können sinnlos oder unvollständig sein, da eine KI nicht immer die gesamte Businesslogik bzw. den Kontext kennt. Es kann auch zu Über- oder Untertestung kommen.

Und welche Möglichkeiten gibt es?

Es gibt zahlreiche Möglichkeiten, KI als Unterstützung und zur Erstellung von Unit-Tests zu nutzen. Einerseits kann man Large Language Models (LLM) direkt über eine Interaktionsschnittstelle verwenden, die heutzutage in Form von Chats bekannt ist. Zum anderen gibt es KI-Assistenten oder Agents, die bereits in Tools wie Visual Studio integriert sind.

Bekannte Large Language Models sind:

  • ChatGPT von OpenAI [1]
  • Claude von Anthropic [2]
  • DeepSeek-R1 von DeepSeek [3]
  • Gemini von Google [4]

Bekannte KI-Assistenten:

  • GitHub Copilot [5]
  • Resharper AI [6]
  • Cursor [7]
  • Cline [8]
  • Diffblue [9]

KI-Assistenten können natürlich auch weitere Aufgaben übernehmen, wie beispielsweise das Hinzufügen der generierten Unit-Tests zu bereits bestehenden Testdateien.

Was zeichnet gute Unit-Tests aus?

Gute Unit-Tests zeichnen sich durch bestimmte Qualitätsmerkmale aus, die ihre Aussagekraft und ihren praktischen Nutzen sicherstellen. Sie sollten klein und wenig komplex sein, damit sie leicht wartbar bleiben, und gleichzeitig eine gute Verständlichkeit sowie Lesbarkeit aufweisen. Im Idealfall testet jeder Unit-Test nur eine einzelne Komponente und konzentriert sich dabei auf genau einen spezifischen Fall. Wichtig ist außerdem, dass die Tests unabhängig voneinander ausführbar sind und keine externen Abhängigkeiten besitzen.

Darüber hinaus spielt die Abdeckung des Codes eine große Rolle: Gute Unit-Tests berücksichtigen sowohl typische als auch ungewöhnliche Szenarien – also normale Anwendungsfälle, fehlerhafte Eingaben und Randbedingungen. Sie laufen schnell durch, liefern wiederholbare Ergebnisse und sind üblicherweise nach dem AAA-Prinzip (Arrange, Act, Assert) strukturiert. Klare und eindeutige Testnamen erleichtern zusätzlich das Verständnis. Schließlich sollte der Testcode dieselben Qualitätsstandards erfüllen wie der eigentliche Produktivcode, um langfristig zuverlässig und nutzbar zu bleiben.

Der passende Prompt für die Generierung von Unit-Tests

Wie sieht ein sinnvoller, passender Prompt für qualitativ gute Unit-Tests aus? Auch hier bietet es sich an, einfach ChatGPT direkt zu befragen.

Die zusammengefasste Antwort:

Der Prompt sollte klar, spezifisch, kompakt und vollständig formuliert sein. Er sollte

  • die Codebasis bereitstellen,
  • den Testkontext benennen,
  • das gewünschte Testframework nennen, sowie
  • Ziele formulieren und dabei relevante Pfade, Edge Cases oder Exception Cases spezifizieren.

Darüber hinaus ist es sinnvoll,

  • das gewünschte Mocking-Framework,
  • die Test-Struktur wie bspw. Arrange-Act-Assert,
  • die Struktur der Testnamen sowie
  • nützliche Kommentare

festzulegen bzw. mitzugeben.

Beispiel-Prompt für ein LLM:

„Create Unit Tests for the given [ClassName.MethodName()]. Use the NUnit-Framework. Use the AAA-principal (arrange, act, assert). Cover normal cases, edge cases and exception handling. The naming should be similar to FunctionName_When_X_Then_Y. Comment on the tests in a useful way if necessary. For mocking of dependencies use [Moq/NSubstitue/FakeItIEasy]. Optional: If useful, use [TestCase] for parameterization.“

Dies zeigt, dass Prompt Engineering auch beim Generieren von Unit-Test mit KI ein wichtiger Bestandteil ist.

Code-Grundlage zur Unit-Test-Generierung

Als Grundlage zur KI basierten Unit-Test-Generierung nutze ich zwei verfremdete, praxisnahe Beispiele sowie zwei weitere angepasste Methoden, die in angepasster Form häufig zu finden sind. Den gesamten Code können Sie in diesem Repository einsehen:

https://github.com/Marco2011T2/TestProjectKiUnitTests/tree/main/TestProjectKiUnitTests

Beispiel DataController:

Ein Controller, der über weitere Abhängigkeiten Daten lädt, konvertiert und eine Erfolgs- oder Fehlermeldung als Antwort liefert.

[Route("data")]
public sealed class DataController : Controller
{
    private readonly IProductClient _client;
    private readonly IProductToSpecificProductConverter _productToSpecificProductConverter;
 
    public DataController(
        IProductClient client,
        IProductToSpecificProductConverter productToSpecificProductConverter)
    {
        _client = client;
        _productToSpecificProductConverter = productToSpecificProductConverter;
    }
 
    [HttpGet]
    [ActionName(nameof(GetAsync))]
    public async Task<IActionResult> GetAsync(
        [FromQuery(Name = "filter")] string filter,
        [FromQuery(Name = "page[number]")] int? pageNumber,
        [FromQuery(Name = "page[size]")] int? pageSize,
        CancellationToken cancellationToken = default)
    {
        if (pageNumber.HasValue != pageSize.HasValue)
        {
            return BadRequest(
                new
                {
                    errors = new[]
                    {
                        new ApiError(
                            "Bad Request",
                            "when requesting a page both parameters page[number] and page[size] are required")
                    }
                });
        }
 
        if (pageNumber <= 0)
        {
            return BadRequest(
                new { errors = new[] { new ApiError("Bad Request", "the page number must be >= 1") } });
        }
 
        var products = await _client.GetProductsAsync(
            filter,
            pageNumber,
            pageSize,
            cancellationToken);
 
        return products
            .Match(
                failure: error
                    => error.StatusCode switch
                    {
                        HttpStatusCode.NotFound => ReturnSuccess(
                            ImmutableArray<SpecificProduct>.Empty,
                            0,
                            pageNumber,
                            pageSize),
                        HttpStatusCode.GatewayTimeout => StatusCode(
                            (int)HttpStatusCode.GatewayTimeout,
                            new
                            {
                                errors = new[]
                                {
                                    new ApiError(
                                        HttpStatusCode.GatewayTimeout.ToString(),
                                        error.Message,
                                        HttpStatusCode.GatewayTimeout.ToString())
                                }
                            }),
                        _ => StatusCode(
                            (int)HttpStatusCode.BadGateway,
                            new
                            {
                                errors = new[]
                                {
                                    new ApiError(
                                        HttpStatusCode.BadGateway.ToString(),
                                        $"{error.StatusCode} - {error.Message}",
                                        HttpStatusCode.BadGateway.ToString())
                                }
                            })
                    },
                success: result
                    => ReturnSuccess(
                        _productToSpecificProductConverter.Convert(result.Content.Result),
                        result.Content.TotalCount,
                        pageNumber,
                        pageSize));
    }
 
    private IActionResult ReturnSuccess(
        ImmutableArray<SpecificProduct> products,
        int totalCount,
        int? pageNumber,
        int? pageSize)
    {
        var document = pageNumber.HasValue && pageSize.HasValue
            ? $"PagedResourceDocument-{products.Length}-{totalCount}"
            : "ResourceDocument";
 
        return Ok(document);
    }
}

Beispiel DataConverter:

Ein Daten-Konverter mit Validierung.

internal sealed class ProductMoneyApiDataConverter :
    IProductMoneyApiDataConverter
{
    public ProductMoneyData Convert(string id, ProductMoneyApiData data)
        => new()
        {
            Id = id,
            SubValueOne = TryCreate(
                data.SubValueOneCurrency,
                data.SubValueOneValue),
            SubValueTwo = TryCreate(
                data.SubValueTwoCurrency,
                data.SubValueTwoValue),
            Quantity = data.Quantity
        };
 
    private static Money? TryCreate(string? currency, decimal amount)
        => string.IsNullOrWhiteSpace(currency)
            ? null
            : Money.Create(currency, amount);
}

Beispiel DataProcessor mit LINQ:

Eine Methode, die Daten mit Hilfe von LINQ filtert und umwandelt.

public List<string> GetPremiumCustomerEmails(List<Customer> customers)
{
    if (customers == null)
        throw new ArgumentNullException(nameof(customers));
 
    return customers
        .Where(customer => customer.IsPremium && !string.IsNullOrWhiteSpace(customer.Email))
        .Select(customer => customer.Email.ToLowerInvariant())
        .Distinct()
        .ToList();
}

Beispiel Baum-Datenstruktur:

Eine Methode, die Daten über die übergebende Baum-Struktur summiert.

public int SumTree(TreeNode node, int? maxDepth = null)
{
    if (node == null) return 0;
 
    int depth = node.GetDepth();
    if (maxDepth.HasValue && depth > maxDepth.Value)
        return 0;
 
    var sum = node.Value;
    foreach (var child in node.Children)
    {
        sum += SumTree(child, maxDepth);
    }
    return sum;
}

Unit-Test-Generierung mit ChatGPT-4.1, ChatGPT-5, Claude Sonnet 3.5, Claude Sonnet 4 sowie Gemini 2.5

Wie bereits beschrieben, gibt es zahlreiche Möglichkeiten, um Unit-Test mit Hilfe von KI zu generieren. Hinzu kommt, dass es jedes genannte Tool in verschiedene Versionen gibt. Für meinen Test nutze ich folgende Tools bzw. Versionen:

  • ChatGPT-4.1
  • Chat GPT-5
  • Claude Sonnet 3.5
  • Claude Sonnet 4 und
  • Gemini 2.5.

Jedes Tool wird mit folgendem Prompt gefüttert:

Create Unit Tests for the given [ClassName.MethodName()]. Use the NUnit-Framework. Use the AAA-principal (arrange, act, assert). Cover normal cases, edge cases and exception handling. The naming should be similar to FunctionName_When_X_Then_Y. Comment on the tests in a useful way if necessary. For mocking of dependencies use FakeItIEasy.

Da alle erzeugten Tests eine ähnliche, strukturelle Qualität aufweisen, im Folgenden ein Beispiel zur Veranschaulichung. Alle generierten Unit-Tests können Sie im ebenfalls bereits genannten Repository einsehen.

Beispiel DataCoverter happy path

[Test]
public void Convert_WhenValidDataWithAllCurrencies_ThenReturnsCompleteProductMoneyData()
{
	// Arrange
	const string id = "123";
	var moneyApiData = new ProductMoneyApiData
	{
		Id = "123",
		SubValueTwoValue = 100.50m,
		SubValueTwoCurrency = "USD",
		SubValueOneValue = 120.75m,
		SubValueOneCurrency = "EUR",
		Quantity = 24
	};

	// Act
	var result = _converter.Convert(id, moneyApiData);

	// Assert
	Assert.That(result.Id, Is.EqualTo(id));
	Assert.That(result.SubValueTwo, Is.Not.Null);
	Assert.That(result.SubValueTwo!.Currency, Is.EqualTo("USD"));
	Assert.That(result.SubValueTwo.Amount, Is.EqualTo(100.50m));
	Assert.That(result.SubValueOne, Is.Not.Null);
	Assert.That(result.SubValueOne!.Currency, Is.EqualTo("EUR"));
	Assert.That(result.SubValueOne.Amount, Is.EqualTo(120.75m));
	Assert.That(result.Quantity, Is.EqualTo(24));
}

Zur weiteren Übersicht finden Sie hier alle Testnamen der erzeugten Unit-Tests. Tests, welche grob einen ähnlichen Teil abdecken, sind entsprechend farblich markiert.

Testnamen der erzeugten Unit-Tests

DataControllerTests

DataControllerTestsChatGpt41 (9 tests)

  • GetAsync_WhenProductClientReturnsSuccessWithPaging_ThenReturnsPagedResourceDocument
  • GetAsync_WhenProductClientReturnsSuccessWithoutPaging_ThenReturnsResourceDocument
  • GetAsync_WhenPageNumberWithoutPageSize_ThenReturnsBadRequest
  • GetAsync_WhenPageSizeWithoutPageNumber_ThenReturnsBadRequest
  • GetAsync_WhenPageNumberIsZeroOrNegative_ThenReturnsBadRequest(0)
  • GetAsync_WhenPageNumberIsZeroOrNegative_ThenReturnsBadRequest(-1)
  • GetAsync_WhenProductClientReturnsNotFound_ThenReturnsEmptyPagedResourceDocument
  • GetAsync_WhenProductClientReturnsGatewayTimeout_ThenReturnsGatewayTimeoutStatus
  • GetAsync_WhenProductClientReturnsOtherError_ThenReturnsBadGatewayStatus

DataControllerTestsChatGpt5 (8 tests)

  • GetAsync_WhenValidPagingAndResultsFound_ThenReturnsOkWithPagedResourceDocument
  • GetAsync_WhenValidNoPagingAndResultsFound_ThenReturnsOkWithResourceDocument
  • GetAsync_WhenPageNumberProvidedWithoutPageSize_ThenReturnsBadRequest
  • GetAsync_WhenPageSizeProvidedWithoutPageNumber_ThenReturnsBadRequest
  • GetAsync_WhenPageNumberIsLessThanOrEqualZero_ThenReturnsBadRequest
  • GetAsync_WhenNotFoundReturnedFromProductClient_ThenReturnsEmptyListWithOk
  • GetAsync_WhenGatewayTimeoutFromProductClient_ThenReturnsGatewayTimeoutResult
  • GetAsync_WhenOtherErrorFromProductClient_ThenReturnsBadGatewayResult

DataControllerTestsClaude35 (9 tests)

  • GetAsync_WhenSuccessfulWithPaging_ThenReturnsPagedDocument
  • GetAsync_WhenSuccessfulWithoutPaging_ThenReturnsUnpagedDocument
  • GetAsync_WhenOnlyPageNumberProvided_ThenReturnsBadRequest
  • GetAsync_WhenOnlyPageSizeProvided_ThenReturnsBadRequest
  • GetAsync_WhenPageNumberIsInvalid_ThenReturnsBadRequest(0)
  • GetAsync_WhenPageNumberIsInvalid_ThenReturnsBadRequest(-1)
  • GetAsync_WhenProductClientReturnsNotFound_ThenReturnsEmptyPagedDocument
  • GetAsync_WhenProductClientReturnsGatewayTimeout_ThenReturnsGatewayTimeoutError
  • GetAsync_WhenProductClientReturnsOtherError_ThenReturnsBadGatewayError

DataControllerTestsClaude4 (19 tests)

  • GetAsync_When_ValidParametersWithPaging_Then_ReturnsOkWithPagedResourceDocument
  • GetAsync_When_ValidParametersWithoutPaging_Then_ReturnsOkWithResourceDocument
  • GetAsync_When_SuccessfulExecution_Then_CallsAllDependenciesInCorrectOrder
  • GetAsync_When_FilterIsEmpty_Then_PassesEmptyStringToProductClient
  • GetAsync_When_FilterIsNull_Then_PassesNullToProductClient
  • GetAsync_When_PageNumberProvidedWithoutPageSize_Then_ReturnsBadRequest
  • GetAsync_When_PageSizeProvidedWithoutPageNumber_Then_ReturnsBadRequest
  • GetAsync_When_PageNumberIsZero_Then_ReturnsBadRequest
  • GetAsync_When_PageNumberIsNegative_Then_ReturnsBadRequest
  • GetAsync_When_ProductClientReturnsEmptyProductArray_Then_ReturnsOkWithEmptyResult
  • GetAsync_When_ProductClientReturnsNotFound_Then_ReturnsOkWithEmptyResult
  • GetAsync_When_ProductClientReturnsGatewayTimeout_Then_ReturnsGatewayTimeoutStatus
  • GetAsync_When_ProductClientReturnsInternalServerError_Then_ReturnsBadGatewayStatus
  • GetAsync_When_ProductClientReturnsBadRequest_Then_ReturnsBadGatewayStatus
  • GetAsync_When_ProductClientThrowsException_Then_ExceptionPropagates
  • GetAsync_When_ProductClientFails_Then_ConverterIsNotCalled
  • GetAsync_When_ConverterReturnsEmptyArray_Then_ReturnsOkWithEmptyResult
  • GetAsync_When_ConverterThrowsException_Then_ExceptionPropagates
  • GetAsync_When_CancellationTokenIsCancelled_Then_OperationCancelledExceptionThrown

DataControllerTestsGemini25 (9 tests)

  • GetAsync_WhenPagingParametersAreValid_ThenReturnsOkWithPagedResult
  • GetAsync_WhenPagingParametersAreNull_ThenReturnsOkWithUnpagedResult
  • GetAsync_WhenPageNumberIsZero_ThenReturnsBadRequest
  • GetAsync_WhenPageNumberIsNegative_ThenReturnsBadRequest
  • GetAsync_WhenOnlyOnePagingParameterIsProvided_ThenReturnsBadRequest(1,null)
  • GetAsync_WhenOnlyOnePagingParameterIsProvided_ThenReturnsBadRequest(null,10)
  • GetAsync_WhenClientReturnsNotFound_ThenReturnsOkWithEmptyArray
  • GetAsync_WhenClientReturnsGatewayTimeout_ThenReturnsGatewayTimeout
  • GetAsync_WhenClientReturnsUnhandledError_ThenReturnsBadGateway

DataConverterTests

ProductMoneyApiDataConverterTestsChatGpt41 (7 tests)

  • Convert_WhenAllFieldsAreValid_ThenReturnsExpectedProductMoneyData
  • Convert_WhenDataIsNull_ThenThrowsArgumentNullException
  • Convert_WhenIdIsNull_ThenIdIsNull
  • Convert_WhenCurrencyIsInvalid_ThenThrowsArgumentException
  • Convert_WhenQuantityIsZero_ThenQuantityIsZero
  • Convert_WhenSubValueOneCurrencyIsNullOrWhitespace_ThenSubValueOneCurrencyIsNull
  • Convert_WhenSubValueTwoCurrencyIsNullOrWhitespace_ThenSubValueTwoIsNull

ProductMoneyApiDataConverterTestsChatGpt5 (6 tests)

  • Convert_When_AllFieldsValid_Then_ReturnsExpectedProductMoneyData
  • Convert_When_CurrencyIsEmptyString_Then_MoneyPropertyIsNull
  • Convert_When_CurrencyIsInvalid_Then_ThrowsArgumentException
  • Convert_When_CurrencyIsLowercaseValidCode_Then_ParsesSuccessfully
  • Convert_When_CurrencyIsNull_Then_MoneyPropertyIsNull
  • Convert_When_CurrencyIsSpecialCurrency_Then_MapsToMXN

ProductMoneyApiDataConverterTestsClaude35 (4 tests)

  • Convert_WhenDataIsNull_ShouldThrowArgumentNullException
  • Convert_WhenIdIsNull_ShouldReturnObjectWithNullId
  • Convert_WhenCurrencyIsInvalid_ShouldThrowArgumentException
  • Convert_WhenQuantityIsZero_ShouldReturnObjectWithZeroQuantity

ProductMoneyApiDataConverterTestsClaude4 (15 tests)

  • Convert_WhenValidDataWithAllCurrencies_ThenReturnsCompleteProductMoneyData
  • Convert_WhenValidDataWithMXNCurrency_ThenReturnsMXNCurrency
  • Convert_WhenValidDataWithSpecialCurrency_ThenConvertsMXPToMXN
  • Convert_WhenZeroValues_ThenReturnsMoneyWithZeroAmount
  • Convert_WhenNegativeValues_ThenReturnsMoneyWithNegativeAmount
  • Convert_WhenEmptyId_ThenSetsIdToEmptyString
  • Convert_WhenNullId_ThenSetsIdToNull
  • Convert_WhenNullProductMoneyApiData_ThenThrowsArgumentNullException
  • Convert_WhenEmptyCurrencies_ThenReturnsNullMoneyValues
  • Convert_WhenInvalidSubValueOneCurrency_ThenThrowsArgumentException
  • Convert_WhenInvalidSubValueTwoCurrency_ThenThrowsArgumentException
  • Convert_WhenLowerCaseCurrency_ThenCreatesMoneySuccessfully
  • Convert_WhenMixedCaseCurrency_ThenCreatesMoneySuccessfully
  • Convert_WhenMixedCurrencyAvailability_ThenReturnsPartialMoneyValues
  • Convert_WhenNullCurrencies_ThenReturnsNullMoneyValues

ProductMoneyApiDataConverterTestsGemini25 (6 tests)

  • Convert_When_ValidDataProvided_Then_ReturnsCorrectlyMappedProductMoneyData
  • Convert_When_AllCurrenciesAreNullOrWhitespace_Then_BothMoneyObjectsAreNull
  • Convert_When_InvalidCurrencyCodeIsProvided_Then_ThrowsArgumentException
  • Convert_When_SpecialCurrencyCodeMxpIsProvided_Then_ReturnsMappedProductMoneyDataWithMxn
  • Convert_When_SubValueOneCurrencyIsNull_Then_SubValueOneIsNull
  • Convert_When_SubValueTwoCurrencyIsWhitespace_Then_SubValueTwoIsNull

OrderProcessorTests

OrderProcessorTestsChatGpt41 (5 tests)

  • GetPremiumCustomerEmails_WhenPremiumCustomersExist_ThenReturnsLowercaseDistinctEmails
  • GetPremiumCustomerEmails_WhenNoPremiumCustomers_ThenReturnsEmptyList
  • GetPremiumCustomerEmails_WhenPremiumCustomersHaveNullOrWhitespaceEmails_ThenIgnoresInvalidEmails
  • GetPremiumCustomerEmails_WhenCustomersIsNull_ThenThrowsArgumentNullException
  • GetPremiumCustomerEmails_WhenCustomerListIsEmpty_ThenReturnsEmptyList

OrderProcessorTestsChatGpt5 (8 tests)

  • GetPremiumCustomerEmails_When_OnlyOnePremium_Then_ReturnsEmail
  • GetPremiumCustomerEmails_When_NoPremiums_Then_ReturnsEmpty
  • GetPremiumCustomerEmails_When_OnePremiumWithValidEmail_Then_ReturnsLowercaseEmail
  • GetPremiumCustomerEmails_When_PremiumWithNullEmail_Then_IgnoresIt
  • GetPremiumCustomerEmails_When_PremiumWithWhitespaceEmail_Then_IgnoresIt
  • GetPremiumCustomerEmails_When_CustomersIsNull_Then_ThrowsArgumentNullException
  • GetPremiumCustomerEmails_When_NoCustomers_Then_ReturnsEmptyList
  • GetPremiumCustomerEmails_When_MultiplePremiumsWithSameEmailDifferentCase_Then_ReturnsSingleLowercaseEmail

OrderProcessorTestsClaude35 (6 tests)

  • GetPremiumCustomerEmails_WhenListContainsPremiumCustomers_ThenReturnsTheirEmails
  • GetPremiumCustomerEmails_WhenMixedCustomerTypes_ThenReturnsOnlyPremiumEmails
  • GetPremiumCustomerEmails_WhenNullOrEmptyEmails_ThenSkipsThoseCustomers
  • GetPremiumCustomerEmails_WhenEmptyList_ThenReturnsEmptyList
  • GetPremiumCustomerEmails_WhenCustomersIsNull_ThenThrowsArgumentNullException
  • GetPremiumCustomerEmails_WhenDuplicateEmails_ThenReturnsDistinctEmails

OrderProcessorTestsClaude4 (11 tests)

  • GetPremiumCustomerEmails_WhenPremiumCustomersExist_ThenReturnsTheirEmails
  • GetPremiumCustomerEmails_WhenPremiumCustomersHaveEmptyEmails_ThenFiltersThemOut
  • GetPremiumCustomerEmails_WhenAllPremiumCustomersHaveInvalidEmails_ThenReturnsEmptyList
  • GetPremiumCustomerEmails_WhenNoPremiumCustomers_ThenReturnsEmptyList
  • GetPremiumCustomerEmails_WhenComplexScenarioWithAllEdgeCases_ThenHandlesCorrectly
  • GetPremiumCustomerEmails_WhenPremiumCustomersHaveNullEmails_ThenFiltersThemOut
  • GetPremiumCustomerEmails_WhenPremiumCustomersHaveWhitespaceEmails_ThenFiltersThemOut
  • GetPremiumCustomerEmails_WhenCustomersListIsNull_ThenThrowsArgumentNullException
  • GetPremiumCustomerEmails_WhenEmptyList_ThenReturnsEmptyList
  • GetPremiumCustomerEmails_WhenDuplicateEmails_ThenReturnsDistinctEmails
  • GetPremiumCustomerEmails_WhenMixedCaseEmails_ThenReturnsLowercaseEmails

OrderProcessorTestsGemini25 (7 tests)

  • GetPremiumCustomerEmails_When_CustomersListContainsPremiumAndNonPremium_Then_ReturnsOnlyPremiumEmails
  • GetPremiumCustomerEmails_When_NoCustomersArePremium_Then_ReturnsEmptyList
  • GetPremiumCustomerEmails_When_PremiumCustomersHaveNullOrWhitespaceEmails_Then_TheyAreExcluded
  • GetPremiumCustomerEmails_When_CustomersListIsEmpty_Then_ReturnsEmptyList
  • GetPremiumCustomerEmails_When_CustomersListIsNull_Then_ThrowsArgumentNullException
  • GetPremiumCustomerEmails_When_DuplicateEmailsExist_Then_ReturnsDistinctEmails
  • GetPremiumCustomerEmails_When_EmailsHaveMixedCasing_Then_ReturnsEmailsInLowercase

TreeProcessorTests

TreeProcessorTestsChatGpt41 (6 tests)

  • SumTree_WhenMaxDepthIsOne_ThenIgnoresNodesDeeperThanOne
  • SumTree_WhenMaxDepthIsZero_ThenReturnsZeroForAllNodesExceptRoot
  • SumTree_WhenNodeIsNull_ThenReturnsZero
  • SumTree_WhenSingleNode_ThenReturnsNodeValue
  • SumTree_WhenTreeHasMultipleLevels_ThenReturnsSumOfAllValues
  • SumTree_WhenTreeHasNegativeValues_ThenReturnsCorrectSum

TreeProcessorTestsChatGpt5 (6 tests)

  • SumTree_WhenMaxDepthApplied_ThenReturnsExpectedSum(0,1)
  • SumTree_WhenMaxDepthApplied_ThenReturnsExpectedSum(1,6)
  • SumTree_WhenMaxDepthApplied_ThenReturnsExpectedSum(2,10)
  • SumTree_WhenNodeIsNull_ThenReturnsZero
  • SumTree_WhenTreeHasMultipleLevels_ThenReturnsSumOfAllValues
  • SumTree_WhenTreeHasSingleNode_ThenReturnsNodeValue

TreeProcessorTestsClaude35 (5 tests)

  • SumTree_WhenMaxDepthIsOne_ThenIncludesRootAndDirectChildren
  • SumTree_WhenMaxDepthIsZero_ThenOnlyIncludesRoot
  • SumTree_WhenSingleNode_ThenReturnsNodeValue
  • SumTree_WhenTreeHasMultipleNodes_ThenReturnsSumOfAllNodes
  • SumTree_WhenTreeIsNull_ThenReturnsZero

TreeProcessorTestsClaude4 (19 tests)

  • SumTree_WhenComplexTreeWithMultipleLevels_ThenReturnsSumOfAllNodes
  • SumTree_WhenMaxDepthIs0_ThenReturnsOnlyRootValue
  • SumTree_WhenMaxDepthIs1_ThenReturnsRootAndFirstLevelChildren
  • SumTree_WhenMaxDepthIsIntMaxValue_ThenIncludesAllNodes
  • SumTree_WhenMaxDepthIsLargerThanTreeDepth_ThenReturnsSumOfAllNodes
  • SumTree_WhenMaxDepthIsNegative_ThenReturnsZero
  • SumTree_WhenMaxDepthIsZeroAndStartFromNonRootNode_ThenReturnsOnlyThatNodeValue
  • SumTree_WhenNodeHasNoChildren_ThenReturnsNodeValue
  • SumTree_WhenNodeIsNull_ThenReturnsZero
  • SumTree_WhenNodeValueIsIntMaxValue_ThenHandlesLargeValue
  • SumTree_WhenNodeValueIsIntMinValue_ThenHandlesLargeNegativeValue
  • SumTree_WhenSimpleTreeWithTwoLevels_ThenReturnsSumOfAllNodes
  • SumTree_WhenSingleNodeWithValue10_ThenReturns10
  • SumTree_WhenStartingFromMiddleNode_ThenReturnsSumFromThatNodeDown
  • SumTree_WhenTreeHasDeepNesting_ThenHandlesRecursionCorrectly
  • SumTree_WhenTreeHasOnlyOnePathDeep_ThenReturnsSumOfLinearPath
  • SumTree_WhenTreeHasWideStructureWithManyChildren_ThenReturnsSumOfAllNodes
  • SumTree_WhenTreeWithNegativeValues_ThenReturnsSumIncludingNegatives
  • SumTree_WhenTreeWithZeroValues_ThenReturnsSumIncludingZeros

TreeProcessorTestsGemini25 (7 tests)

  • SumTree_When_MaxDepthIsNegative_Then_ReturnsZero
  • SumTree_When_MaxDepthIsSet_Then_ReturnsSumOfNodesUpToDepth
  • SumTree_When_MaxDepthIsZero_Then_ReturnsOnlyRootValue
  • SumTree_When_NodeIsNull_Then_ReturnsZero
  • SumTree_When_TreeHasOnlyRoot_Then_ReturnsRootValue
  • SumTree_When_TreeIsDeepAndSkewed_Then_ReturnsCorrectSum
  • SumTree_When_TreeIsValid_Then_ReturnsCorrectSum

Die Auswertung der generierten Unit-Tests

Der Vergleich der eingesetzten Modelle zeigt, dass die generierten Unit-Tests insgesamt eine solide Qualität aufweisen und die erwarteten Standards weitgehend erfüllen. Sie sind kompakt, verständlich, unabhängig voneinander ausführbar, laufen in weniger als einer Sekunde und bieten eine grundlegende Abdeckung sowohl für Standard- als auch für Ausnahmefälle. Zudem sind sie nach dem Arrange-Act-Assert-Prinzip aufgebaut, verfügen über aussagekräftige Testnamen und enthalten hilfreiche Kommentare.

Ein Großteil der Tests lässt sich ohne manuelle Nacharbeit direkt ausführen. Lediglich in Einzelfällen treten bei ChatGPT-4.1, ChatGPT-5, Claude 3.5 und Gemini 2.5 Probleme auf, wenn Assertions mit veralteten Methoden (ClassicAssert) generiert werden, die nicht mehr kompilierbar sind.

Die neueren Modelle – insbesondere GPT-5, Claude 4 und Gemini 2.5 – erkennen Edge Cases sehr zuverlässig, wobei Claude 4 mit zwei- bis dreimal so vielen Tests besonders hervorsticht. Die generierten Fälle entsprechen überwiegend dem Beispielcode, sind sinnvoll gewählt und weisen nur wenig Übertestung auf. Ein Schwachpunkt besteht jedoch darin, dass einige Modelle zu wenige Tests für Eingabeparameter erstellen; ein Problem, das bei Claude 4 nicht auftritt.

Ein weiteres Hindernis zeigt sich während der Ausführung: sogenannte Halluzinationen [10] führen dazu, dass manche Tests fehlschlagen, obwohl ihre Logik grundsätzlich korrekt ist. So wertet Gemini 2.5 in den DataControllerTests Fehler auf unzutreffende Weise aus, während Claude 3.5, GPT-4.1 und Claude 4 in den ApiDataConverterTests sowie den TreeProcessorTests falsche Exceptions prüfen.

Positiv hervorzuheben sind die von Claude 4 generierten Tests, die zusätzlich externe Abhängigkeiten verifizieren (etwa mittels MustHaveHappened()), weitere Edge- und Ausnahmefälle abdecken und den Testcode in übersichtliche Regionen gliederen. Auch die Kommentare sind bei allen Modellen überwiegend prägnant und hilfreich, einzig Gemini neigt dazu, unnötig ausführlich zu erklären.

Fazit

Die Ergebnisse zeigen: KI kann Entwickler bei der Generierung von Unit-Tests spürbar entlasten. Aktuelle Modelle liefern Tests von hoher Qualität, die in vielen Fällen mit denen erfahrener Entwickler vergleichbar sind.

Die Vorteile liegen auf der Hand: KI übernimmt monotone und zeitaufwändige Aufgaben, steigert dadurch die Produktivität und reduziert gleichzeitig das Risiko, dass Fehler übersehen werden. Hinzu kommt eine hohe Flexibilität, die es ermöglicht, Vorschläge schnell und unkompliziert anzupassen.

Dennoch gibt es auch Einschränkungen. So erfordert der Einsatz zusätzlichen Aufwand beim Prompt Engineering, und es besteht die Gefahr von Fehlern durch Halluzinationen oder die Generierung unnötigen oder fehlerhaften Codes. Ein abschließendes menschliches Review ist daher unverzichtbar. Zudem weisen die Systeme selbst darauf hin, dass ihre Ergebnisse potenziell unvollständig oder fehlerhaft sein können. Auch der Schutz sensibler Daten muss berücksichtigt werden.

Trotz dieser Herausforderungen ist die Entwicklung vielversprechend. Sprachmodelle werden kontinuierlich verbessert und erscheinen in kurzen Abständen in leistungsfähigeren Versionen. Erste Praxiserfahrungen – auch in unserem Unternehmen – zeigen bereits eine solide Testabdeckung. Die entscheidende Frage lautet nun, wie sich dieser Prozess weiter optimieren lässt. Der gezielte Einsatz von KI könnte dabei der richtige Weg sein.

 

Hinweise:

[1] ChatGPT Overview von OpenAI
[2] Claude von Anthropic
[3] DeepSeek-R1 von DeepSeek
[4] Gemini von Google
[5] GitHub Copilot
[6] Resharper AI
[7] Cursor
[8] Cline
[9] Diffblue
[10] GitHub Copilot Security and Privacy Concerns: Understanding the Risks and Best Practices

Hier finden Sie einen Beitrag über die Entwicklung einer iOS-App mit generativer KI.

Und hier ein Pro und Kontra zur Nutzung von ChatGPT in der Softwareentwicklung.

Wollen Sie als Meinungsmacherin oder Kommunikator über das Thema diskutieren? Dann teilen Sie den Beitrag gerne auf Social Media oder in Ihrem Netzwerk.

Marco Menzel
Marco Menzel

Marco Menzel ist Junior-Softwareentwickler bei t2informatik. Schon in seiner Kindheit entdeckte er seine Begeisterung für Computer und die Entwicklung von Software. Erste kleine Programme schrieb er noch in der Schulzeit, und schnell wurde klar, dass er sein Hobby später auch beruflich verfolgen möchte. Folgerichtig studierte er Informatik an der BTU Cottbus-Senftenberg, wo er seine Kenntnisse systematisch vertiefte und praktische Erfahrungen in verschiedenen Projekten sammelte. Heute setzt er dieses Wissen in seiner täglichen Arbeit ein und verbindet damit Leidenschaft und Beruf.

Im t2informatik Blog veröffentlichen wir Beträge für Menschen in Organisationen. Für diese Menschen entwickeln und modernisieren wir Software. Pragmatisch. ✔️ Persönlich. ✔️ Professionell. ✔️ Ein Klick hier und Sie erfahren mehr.