.NET Tools How-To's

Generate Unit Tests Using AI Assistant

Can you make your life easier as a developer by using AI to generate unit tests? AI can provide a bit of automation to this daily development activity and make our development workflow a bit smoother if we use it thoughtfully. So in this blog post, we’ll take a look at using the JetBrains AI Assistant in Rider or ReSharper to generate meaningful unit tests that hopefully make our lives a bit easier.

Considerations when using AI for testing

The major concern when using AI at all is that AI often returns some results that are correct, but some are a bit off, and some results are wildly inaccurate. As always, you must carefully evaluate the results of all your queries and requests to any AI. AI isn’t a shortcut to get out of testing your code – and it can even make it worse. It may generate code for you, but it cannot think for you. Thinking is the job of a software developer, not typing or simply producing code. Anytime you use AI, stop and review the inputs and prompts, as well as the output.

Additionally, to get useful results from AI, you must put in the time and effort to understand what’s really going on. At this point in time, AI is very much “garbage in, garbage out” so if you don’t think about how you use AI, you’ll notice this phenomenon firsthand. 

Prepare to test

Let’s look at a basic function that we want to test that calculates the price of some item with tax. This method calculates the price for a line item in an order by first multiplying the sale price and order amount. It then calculates the tax, then adds the item and tax together:

public decimal CalculateItemPriceWithTax(decimal salePrice, int orderAmount)

{
   const decimal taxRate = .12M;

   if (orderAmount <= 0) { throw new ArgumentException("The amount must be more than zero", nameof(orderAmount)); }
   if (salePrice < 0) { throw new ArgumentException("Sale price cannot be less than zero", nameof(salePrice)); }

   var basePrice = salePrice * orderAmount;
   var priceWithTax = basePrice + (basePrice * taxRate);
   return priceWithTax;

}

We should test the following behaviors in the CalculateItemPriceWithTax method:

  • If the amount is invalid (zero or negative), the method throws an exception.
  • If the price is invalid (negative), the method throws an exception.
  • If the price and amount are both valid, the method calculates the base price.

Now that we have defined the test behaviors, we can use Rider to help us with the rest of our testing needs.

Use Rider’s AI Assistant to create the unit test

At the class definition for the function(s) you want to test, press Alt+Enter and choose Create Unit Tests. This causes the Create Unit Test dialog to appear. Check Generate test content with AI. Whether using AI or not, you can choose the testing framework and location. Press Enter or click OK when you’re ready. 

Image of the test generation dialog. Set the test project, framework, and test class names to whatever you choose.

The AI Assistant generates tests based on the code inside the CalculateItemPriceWithTax method. Notice there are tests for both valid and invalid input.

[TestSubject(typeof(PriceCalculator))]

public class PriceCalculatorTest

{

   private PriceCalculator calculator = new PriceCalculator();

   [Theory]

   [InlineData(100, 1, 112)]

   [InlineData(100, 2, 224)]

   [InlineData(100, 3, 336)]

   [InlineData(0, 1, 0)]

   public void CalculateItemPriceWithTax_PositiveValues_ReturnsCorrectAmount(

       decimal salePrice, int orderAmount, decimal expected)

   {

       decimal result = calculator.CalculateItemPriceWithTax(salePrice, orderAmount);

       Assert.Equal(expected, result);

   }

   [Theory]

   [InlineData(100, 0)]

   [InlineData(100, -1)]

   [InlineData(0, -2)]

   public void CalculateItemPriceWithTax_OrderAmountZeroOrLess_ThrowsArgumentException(

       decimal salePrice, int orderAmount)

   {

       Assert.Throws<ArgumentException>(() => calculator.CalculateItemPriceWithTax(salePrice, orderAmount));

   }

   [Theory]

   [InlineData(-1, 1)]

   [InlineData(-1, -1)]

   [InlineData(-2, -2)]

   public void CalculateItemPriceWithTax_SalePriceLessThanZero_ThrowsArgumentException(

       decimal salePrice, int orderAmount)

   {

       Assert.Throws<ArgumentException>(() => calculator.CalculateItemPriceWithTax(salePrice, orderAmount));

   }

}

Do these tests meet the expectations? Let’s run through them again:

1. If the amount is invalid (zero or negative), the method throws an exception. 
Yes. This is tested in the CalculateItemPriceWithTax_OrderAmountZeroOrLess_ThrowsArgumentException test.

2. If the price is invalid (negative), the method throws an exception.
Yes. This is tested in the CalculateItemPriceWithTax_SalePriceLessThanZero_ThrowsArgumentException test.

3. If the price and amount are both valid, the method calculates the base price.
Yes. This is tested in the CalculateItemPriceWithTax_PositiveValues_ReturnsCorrectAmount test.

Does AI always generate such great results? No. Sometimes it gets even basic math wrong. A few times in running the same code through AI, it forgot to include tax for this sample. Because AI is a statistical way of predicting output, these kinds of errors can happen. It’s also why it’s so important to not blindly accept results from AI.

Tests first

You can also use TDD and ask AI to write the tests first. To set the appropriate context, you’ll need documentation comments, a prompt, or information somewhere that provides the missing contextual information, like so: 

/// <summary>
/// The CalculateItemPrice function performs the following action:
/// It calculates a line item amount by multiplying the quantity and price of an item and adds 12% tax to that.
/// The quantity must be a positive non-zero number.
/// The price must be positive but can be zero since some items are free.
/// </summary>

public class PriceCalculator { // empty }

However, rather than launching the Generate Unit Tests dialog, choose AI Actions | Generate Code and ask the AI Assistant to generate tests based on the documentation comments. The Generate Unit Tests option is a feature in Rider and ReSharper that only generates the test skeletons that have the ability to generate test content when you select the Generate test content with AI option. 

Because in TDD tests are written first before the code, the results will vary quite a bit depending on what the documentation comments say. Even seemingly trivial changes in the comments cause major changes in the way tests are generated. Thoughtfully using AI for TDD with a good informative prompt can produce detailed, clear, and meaningful tests that are similar to the samples in this post.

Conclusion

Using the AI Assistant in Rider is a great way to get a set of tests up and running so you can then fine tune them. They’ll be meaningful and support the application’s requirements as well as verify that the code does what it should.

Write great tests. Download Rider and let JetBrains AI Assistant help you.

image description

Discover more