.NET Tools How-To's

How To Use AI Assistant to Generate Test Data For .NET Applications

In software development, writing tests is equivalent to exercising and eating vegetables; everyone knows they should be doing more but likely don’t do enough. One of the reasons folks avoid writing tests is the tedium of constructing test cases that help make a test valuable and worth maintaining. A test that is too simple can steer towards uselessness, while a valuable test can be complex to construct. As developers, we need to balance value and time spent producing the value. Luckily, now is the perfect time to reevaluate how we write tests and, more specifically, how we generate test data for our test suites. 

In this post, we’ll walk you through scenarios to generate test data with the JetBrains AI Assistant for .NET unit tests and how you can leverage generated data to improve the isolation of your tests and ultimately produce a more valuable and less brittle test suite.

Working with Unit Tests

We’ll use the xUnit test framework and JetBrains Annotations in the following examples. These are the libraries I typically use, but all examples apply to other libraries.

Regardless of your style of testing, it’s essential to maintain a consistent approach across tests. In the cross-section of tests and generative assistants, there are three factors to consider when constructing a test suite:

  1. Always think about the “why”. Tests are not inherently valuable, and carelessly adding tests to hit a metric can lead to bloat and a long-term maintenance burden. 
  2. When writing tests, you should see a test fail before it succeeds. It may seem obvious that a test may pass, but it’s a good habit to form to avoid bugs. Seeing a failure is especially important when AI Assistant can generate assertion statements.
  3. Test isolation increases the reliability of tests by reducing interdependency between each test.

JetBrains AI Assistant can accelerate your test creation, but please be mindful of how and when you use it. I’ll show you techniques to generate and maintain tests against a service carefully

These techniques will help keep you engaged and focused rather than autocompleting your way to a suite of tests you don’t understand.

Let’s first look at the class we’ll be testing, a PersonService with several methods of varying complexity.

using System.Text;

namespace GeneratedDataTests;

public record Person(string Name, int Age);

public class PersonService
{
    public double AverageAge(List<Person> people)
    {
        return people.Average(p => p.Age);
    }

    public string? LongestName(List<Person> people)
    {
        return people
            .OrderByDescending(p => p.Name)
            .ThenByDescending(p => p.Name.Length)
            .Select(p => p.Name)
            .FirstOrDefault();
    }

    public string CreateCsv(List<Person> person)
    {
        var sb = new StringBuilder($"{nameof(Person.Age)},{nameof(Person.Name)}");
        foreach (var (name, age) in person)
        {
            sb.AppendLine($@"""{name}"",""{age}""");
        }
        return sb.ToString();
    }

    public List<Person> ProcessCsv(string csv)
    {
        var lines = csv.Split(Environment.NewLine, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
        var people = new List<Person>();
        // skip header
        foreach (var line in lines.Skip(1))
        {
            var values = line.Split(',');
            if (values.Length == 2)
            {
                var name = values[0].Trim('"');
                var age = int.Parse(values[1].Trim('"'));
                people.Add(new Person(name, age));
            }
        }
        return people;
    }
}

You may find similar services in your applications, with logic encapsulated within helper methods. Let’s start by first testing the AverageAge method. In a test class, start by adding this boilerplate code.

using JetBrains.Annotations;

namespace GeneratedDataTests;

[TestSubject(typeof(PersonService))]
public class PersonServiceTests
{
    private readonly PersonService sut = new();
    
    [Fact]
    public void Find_Average_Age()
    {
        // Arrange: a 6 entry list of Person with random odd and even
        // ages between 30 to 90 and Japanese names.
        
        // Act
        var actual = sut.AverageAge(people);
        
        // Assert
        Assert.Equal(0, actual);
    }
}

A few critical elements to mention about this boilerplate:

  1. The Arrange portion of our test is an AI Assistant prompt that will generate our dataset.
  2. The Act portion executes the method of AverageAge as expected.
  3. The Assert portion already contains an assertion that is intentionally incorrect.

While AI Assistant could undoubtedly generate an entire test, we want to remain thoughtful about what we’re creating.

After the prompting comment, we can press Enter to see what JetBrains AI Assistant offers as a potential dataset.

Test class with recommended test data from JetBrains AI Assistant

Our next step is to run the functioning test to see if it fails.

Unit test tool window with a assertion failure.

Here, we can see both a failure of the test and a simplification in our assertion. Let’s update our test to account for the precision of our double value.

[Fact]
public void Find_Average_Age()
{
    // Arrange: a 6 entry list of Person with random odd and even
    // ages between 30 to 90 and Japanese names.
    var people = new List<Person>
    {
        new Person("Yuki", 35),
        new Person("Hiroshi", 42),
        new Person("Akiko", 51),
        new Person("Satoshi", 66),
        new Person("Naomi", 73),
        new Person("Kenji", 88)
    };
        
    // Act
    var actual = sut.AverageAge(people);
        
    // Assert
    Assert.Equal(59.17, Math.Round(actual, 2));
}

Rerunning the test shows we now have a passing test.

Unit test tool window with a passing test

Now, let’s write another test. Under the Find_Average_Age method, hit the Enter key again. You’ll notice something surprising.

JetBrains AI Assistant recommending the next test in its entirety

JetBrains AI Assistant’s code completion has already determined the next test. In this case, it’s very similar to our original test. Accepting this code may be tempting if you’re in a rush, but it could also be a mistake. In this case, running the generated test leads to failure.

The recommended test with a failed run

Remember, you’re still an essential part of the test-writing process. AI Assistant only proposes a “possible” solution, but you must consider the proposal critically. This test fails because AI Assistant could not account for the implementation of LongestName. After all, we did not provide the implementation within the context. Let’s look at it again.

public string? LongestName(List<Person> people)
{
    return people
        .OrderByDescending(p => p.Name)
        .ThenByDescending(p => p.Name.Length)
         .Select(p => p.Name)
        .FirstOrDefault();
}

As you notice, we sort names alphabetically from Z to A and then by length. Generating test data is good, but you should be wary of automatically generated assertions. That is why I recommend writing your assertions before generating test data. Slowing down gives you time to think about the goal and value of the test. Let’s fix the assertion and move on to a more interesting test case scenario, parsing comma-separated value strings.

Let’s start with the boilerplate for a test similar to the previous ones.

[Fact]
public void Parse_Csv()
{
    // Arrange: a string CSV with a header row "Name","Age". Quote all values like "12". 5 rows only.
    
    // Act
    var actual = sut.ProcessCsv(csv);

    // Assert
    Assert.Equal(0, actual.Count);
}

Pressing Enter after the comment produces a string literal for our method.

JetBrains AI Assistant recommending a CSV string in Csharp

You may use the quick fixes provided by ReSharper to change the string to use newer C# 12 features, but for the purposes of this test, it’s unnecessary.

Running the test fails because our assertion checks for a value of 0. Let’s change the assertion after a failed test run to the following.

// Assert
Assert.Equal(5, actual.Count);

Now we should have a passing test. Hooray!

If you must assert the items in the collection parsed correctly, you can use AI Assistant to generate the assertions based on the test data.

More assertion recommendations for elements in the collection

The added assertions can undoubtedly add to more accurate tests, but they are lengthy, and you should use them sparingly. A new test asserting the accuracy of parsing would be more beneficial. Use your judgment here regarding how to approach this in your test suite.

More Test Data Scenarios

When working within complex data-driven systems, you’ll likely represent data in multiple formats. Some standard formats that come to mind include JSON, XML, and SQL schema. The need to work with numerous formats is where the JetBrains AI Assistant can shine, cutting out the doldrums of creating meaningful data and converting between formats.

In a new AI Assistant chat, let’s use the Person type to generate a JSON object with a collection of items.

We’ll use the following prompt:

Given the following record type, generate a JSON object with a `results` field that contains a 2-entry collection of Person objects. 

```csharp public record Person(string Name, int Age);```

The JetBrains AI Assistant generates a JSON object ready to use for our tests that you can now place in a raw-string literal or within a file.

JSON sample generated in AI Chat

We can continue the chat session with the following prompt:

Give me the same JSON object but now as XML with all values as attributes on elements of Person

Which results in the following XML file.

XML data recommended in chat

Finally, let’s create a SQL table to store valuable new information. Within the same chat session, we can add yet another prompt. Remember, not all SQL is created equal. When working with SQL, add a hint denoting the desired syntax.

Could you write a SQL Schema creation script for a Person table with an additional Id column that is an identity column? Make sure it is SQLite syntax.

The resulting output is as you’d expect.

SQLite schema recommended in chat

Let’s go further and generate new test data for our new SQLite table, maybe from a galaxy far far away.

Could you generate 5 insert statements for the Person table where the names are Star Wars characters?

SQL inserts recommended by AI Chat

Wow! That’s great because less time spent typing out test data means more time playing with my Star Wars figures. I mean doing serious work.

Conclusion

As you’ve seen in this post, AI Assistant can speed up your existing workflows by removing some of the tedium around generating test data. You could undoubtedly type all this yourself, but these are typically the boring parts of software development. The crucial parts are problem-solving and delivering reliable solutions to your users, and I think this post demonstrates that you can do just that with JetBrains AI Assistant. 

Combined with the two decades of expertise baked into JetBrains products like ReSharper and JetBrains Rider, we believe AI Assistants can be another step towards building better applications. Read more on the official website to learn more about JetBrains AI Assistant.

We hope you enjoyed this post, and if you have any comments, questions, or suggestions, please feel free to leave a comment.

image credit: Katya Ross

image description

Discover more