在軟體開發領域,AI工具的演進提供新方法來編寫和測試代碼。利用AI來輔助撰寫單元測試已經成為一種新的測試策略。

透過提示AI來撰寫測試代碼,可以更快速地生成測試用例,找出潛在的缺陷並解決問題,從而提高軟體開發的效率和品質。


流程

首先對ChatGPT描述使用的技術架構和想進行的步驟,接著在步驟中和ChatGPT互動並請它提供合適的單元測試代碼。(詳細內容請參考下方的”完整流程”)


優點

採用AI輔助撰寫單元測試的效果比預計還好,特別是測試範圍和效率

前者你可以明確指定AI提供測試的範圍,也可以要求AI提供容易遺漏的測試用例,擴大合理的涵蓋範圍。

後者比起手動輸入測試代碼,透過AI產生測試代碼的速度也會有顯著的提升。

提升速度這一點相當重要,許多排斥撰寫單元測試的開發者,主要原因為在開發時程中很難再排入撰寫單元測試的時間,更不用說當業務邏輯變動之後,與其對應的單元測試也必須重構修改,因此拒絕學習或撰寫單元測試。

若透過AI產生或重構測試代碼,就能大幅降低花費撰寫單元測試的時間,也能順便開啟通往TDD的大道。


缺點

不洩漏敏感訊息的情況下,將想測試的邏輯完整表達給ChatGPT是一個可能的問題,因為我測試的邏輯很簡單,ChatGPT很清楚溫度轉換公式,提供測試代碼相當容易,但若你想測試的演算法或情境特別複雜,要完整的表達清楚就沒這麼簡單了。


總結

在現階段透過AI來輔助撰寫單元測試是一種可以探索的方式,事實上整個過程就是簡單的TDD開發流程,而ChatGPT就是我的結對編程同伴,而且這個同伴很有耐心,有求必應,沒有偏見(?)。

在測試範圍和效率都是AI的優勢,能夠大幅度縮短撰寫測試代碼的時間,但在不洩漏敏感資訊的情況下,如何表達複雜的測試邏輯會是一個可能的問題。


完整流程

以下是透過提示ChatGPT來產生測試代碼,再讓開發者來撰寫產品代碼的過程。

首先告訴ChatGPT系統簡述和技術架構和需求,包含測試庫的要求(MSTest)。

目前的系統是一個溫度轉換程式。
技術架構:C#,Winform,用於模型的測試庫(MSTest)。
所有的程式碼都應該使用上述的技術架構來撰寫。
需求:程式應該提供華氏和攝氏的溫度轉換並以測試驅動開發來撰寫。
請你只提供單元測試,我再回答是否通過你的單元測試,以這種循環方式來完成整個過程

好的,以下是使用 MSTest 撰寫的兩個單元測試。一個用於驗證華氏到攝氏的溫度轉換,另一個用於驗證攝氏到華氏的溫度轉換。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using YourTemperatureConversionNamespace; // 請將此替換為您的溫度轉換類別的命名空間

namespace TemperatureConversionTests
{
    [TestClass]
    public class TemperatureConversionTest
    {
        [TestMethod]
        public void TestFahrenheitToCelsius()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = 32.0;
            double expectedCelsius = 0.0;

            // Act
            double actualCelsius = converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert
            Assert.AreEqual(expectedCelsius, actualCelsius, 0.01);
        }

        [TestMethod]
        public void TestCelsiusToFahrenheit()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = 0.0;
            double expectedFahrenheit = 32.0;

            // Act
            double actualFahrenheit = converter.CelsiusToFahrenheit(inputCelsius);

            // Assert
            Assert.AreEqual(expectedFahrenheit, actualFahrenheit, 0.01);
        }
    }
}

這裡假設你有一個 TemperatureConverter 類別,並且該類別有兩個方法:FahrenheitToCelsiusCelsiusToFahrenheit。這些方法都接受一個雙精度浮點數(double)並返回一個雙精度浮點數。這些方法應該進行溫度轉換。

請注意,這些測試中的精確度為 0.01,這意味著如果實際結果與預期結果之間的差異小於或等於 0.01,則該測試將被認為是通過的。

請將此單元測試代碼添加到你的測試項目中,並運行這兩個測試。如果你的溫度轉換代碼正確,則這兩個測試都應該通過。

接著就可以建立Winform專案,並在該專案中新增一個TemperatureConversionTest測試類別並直接貼上ChatGPT提供的完整代碼。

當然,執行測試一定會失敗,因為根本還沒有TemperatureConverter類別。

(如果對測試驅動開發有點了解,應該可以發現這也是驅動測試開發的流程,甚至也可以把ChatGPT當作結對編程的對象)

最後再根據ChatGPT提供的單元測試,實作被測代碼。為了避免洩漏敏感訊息,被測代碼不會提供給ChatGPT。

被測代碼如下

    public class TemperatureConverter
    {
        public double FahrenheitToCelsius(double fahrenheit)
        {
            if (fahrenheit < -459.67)
            {
                throw new ArgumentOutOfRangeException("The Fahrenheit temperature cannot be less than -459.67");
            }
            return (fahrenheit - 32) * 5 / 9;
        }

        public double CelsiusToFahrenheit(double celsius)
        {
            if (celsius < -273.15)
            {
                throw new ArgumentOutOfRangeException("The Celsius temperature cannot be less than -273.15");
            }
            return (celsius * 9 / 5) + 32;
        }
    }

接著回答ChatGPT,已可通過單元測試,請再提供更多的單元測試。而且在這裡明確指定測試範圍為極限值


我已可通過你提供的單元測試,請再提供更多的單元測試,主要測試範圍為極限值
---
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YourTemperatureConversionNamespace; // 請將此替換為您的溫度轉換類別的命名空間

namespace TemperatureConversionTests
{
    [TestClass]
    public class TemperatureConversionTest
    {
        [TestMethod]
        public void TestFahrenheitToCelsiusAbsoluteZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = -459.67;
            double expectedCelsius = -273.15;
            
            // Act
            double actualCelsius = converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert
            Assert.AreEqual(expectedCelsius, actualCelsius, 0.01);
        }

        [TestMethod]
        public void TestCelsiusToFahrenheitAbsoluteZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = -273.15;
            double expectedFahrenheit = -459.67;
            
            // Act
            double actualFahrenheit = converter.CelsiusToFahrenheit(inputCelsius);

            // Assert
            Assert.AreEqual(expectedFahrenheit, actualFahrenheit, 0.01);
        }
        
        [TestMethod]
        public void TestFahrenheitToCelsiusHighTemperature()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = 212.0;
            double expectedCelsius = 100.0;
            
            // Act
            double actualCelsius = converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert
            Assert.AreEqual(expectedCelsius, actualCelsius, 0.01);
        }

        [TestMethod]
        public void TestCelsiusToFahrenheitHighTemperature()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = 100.0;
            double expectedFahrenheit = 212.0;
            
            // Act
            double actualFahrenheit = converter.CelsiusToFahrenheit(inputCelsius);

            // Assert
            Assert.AreEqual(expectedFahrenheit, actualFahrenheit, 0.01);
        }
    }
}

把上述的單元測試新增到TemperatureConversionTest類別,並執行所有的單元測試,被測代碼可以成功通過單元測試!

接下來想對輸入的不合法數值做一些處理,所以要求ChatGPT對溫度低於絕對零度撰寫測試代碼

我已經可以通過你提供的單元測試,但如果輸入的溫度低於絕對零度,我想丟出一個異常,你可以寫出這種測試嗎?

當然可以。在這裡,我將為溫度低於絕對零度的情況寫出兩個測試。我們期望方法在這種情況下拋出一個異常。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using YourTemperatureConversionNamespace; // 請將此替換為您的溫度轉換類別的命名空間

namespace TemperatureConversionTests
{
    [TestClass]
    public class TemperatureConversionTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void TestFahrenheitToCelsiusBelowAbsoluteZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = -500.0; // Less than absolute zero

            // Act
            converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert is handled by the ExpectedException attribute
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void TestCelsiusToFahrenheitBelowAbsoluteZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = -300.0; // Less than absolute zero

            // Act
            converter.CelsiusToFahrenheit(inputCelsius);

            // Assert is handled by the ExpectedException attribute
        }
    }
}

在這兩個測試中,我們使用了 ExpectedException 屬性來指定我們期望方法在給定的情況下拋出的異常類型。在這種情況下,我們期望方法在溫度低於絕對零度時拋出 ArgumentOutOfRangeException。如果你的溫度轉換方法正確地處理了這種情況,那麼這些測試應該會通過。

把ChatGPT新增的測試代碼貼到測試類別之後,執行單元測試應該會失敗。因為在TemperatureConverter沒有根據輸入的不合法數據丟出異常。

回頭修改TemperatureConverter,主要針對華氏低於-459.67和攝氏低於-243.15,丟出異常

public class TemperatureConverter
{
    public double FahrenheitToCelsius(double fahrenheit)
    {
        if (fahrenheit < -459.67)
        {
            throw new ArgumentOutOfRangeException("The Fahrenheit temperature cannot be less than -459.67");
        }
        return (fahrenheit - 32) * 5 / 9;
    }

    public double CelsiusToFahrenheit(double celsius)
    {
        if (celsius < -273.15)
        {
            throw new ArgumentOutOfRangeException("The Celsius temperature cannot be less than -273.15");
        }
        return (celsius * 9 / 5) + 32;
    }
}

完成後順利通過單元測試!

最後想請ChatGPT提供它覺得缺乏的單元測試。

我已經可以通過單元測試了,最後我想請提供你覺得缺乏的單元測試

很好,你已經涵蓋了一些重要的測試案例。除了我們已經討論過的測試案例,我們還可以添加一些邊界測試和特殊值測試。以下是一些我們可以添加的額外測試:

  1. 輸入為零的情況:零度華氏對應的攝氏溫度為 -17.78°C,零度攝氏對應的華氏溫度為 32°F。
  2. 一些隨機值的測試:選擇一些隨機的華氏和攝氏溫度值,檢查轉換是否正確。

以下是相應的單元測試:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using YourTemperatureConversionNamespace; // 請將此替換為您的溫度轉換類別的命名空間

namespace TemperatureConversionTests
{
    [TestClass]
    public class TemperatureConversionTest
    {
        [TestMethod]
        public void TestFahrenheitToCelsiusZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = 0.0;
            double expectedCelsius = -17.78;

            // Act
            double actualCelsius = converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert
            Assert.AreEqual(expectedCelsius, actualCelsius, 0.01);
        }

        [TestMethod]
        public void TestCelsiusToFahrenheitZero()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = 0.0;
            double expectedFahrenheit = 32.0;

            // Act
            double actualFahrenheit = converter.CelsiusToFahrenheit(inputCelsius);

            // Assert
            Assert.AreEqual(expectedFahrenheit, actualFahrenheit, 0.01);
        }

        [TestMethod]
        public void TestFahrenheitToCelsiusRandom()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputFahrenheit = 45.0; // A random Fahrenheit value
            double expectedCelsius = 7.22; // Corresponding Celsius value

            // Act
            double actualCelsius = converter.FahrenheitToCelsius(inputFahrenheit);

            // Assert
            Assert.AreEqual(expectedCelsius, actualCelsius, 0.01);
        }

        [TestMethod]
        public void TestCelsiusToFahrenheitRandom()
        {
            // Arrange
            TemperatureConverter converter = new TemperatureConverter();
            double inputCelsius = 20.0; // A random Celsius value
            double expectedFahrenheit = 68.0; // Corresponding Fahrenheit value

            // Act
            double actualFahrenheit = converter.CelsiusToFahrenheit(inputCelsius);

            // Assert
            Assert.AreEqual(expectedFahrenheit, actualFahrenheit, 0.01);
        }
    }
}

這些測試將增加對你的溫度轉換程式碼的信心。如果所有這些測試都通過了,那麼你的溫度轉換程式碼在這些測試覆蓋的範圍內應該是正確的。請注意,單元測試能幫助我們提高對程式碼的信心,但並不能保證程式碼百分之百的正確。例如,有可能存在我們尚未考慮到的邊界情況或特殊情況。因此,雖然這些測試是一個很好的起點,但可能仍需要進一步的測試和審查來確保程式碼的健壯性。