利用ChatGPT协助编写单元测试

这篇具有很好参考价值的文章主要介绍了利用ChatGPT协助编写单元测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ChatGPT自从2022年推出以来受到很多人的喜欢,此篇博客重点介绍如何修改Prompt来自动生成较理想的单元测试。如下图所示的一段代码,该class中有一个public方法toLocale(),其余都是private方法,toLocale()方法会调用private的方法。(备注:下面的方法特地写了比较多的分支逻辑,来验证chatGPT编写的单元测试的覆盖率情况)

package com.github.secondCourse;
import java.util.Locale;
public class LocaleUtils {
    private static final String EMPTY = "";
    public Locale toLocale(final String str) {
        if (str == null) {
            return null;
        }
        if (str.isEmpty()) { // LANG-941 - JDK 8 introduced an empty locale where all fields are blank
            return new Locale(EMPTY, EMPTY);
        }
        if (str.contains("#")) { // LANG-879 - Cannot handle Java 7 script & extensions
            throw new IllegalArgumentException("Invalid locale format: " + str);
        }
        final int len = str.length();
        if (len < 2) {
            throw new IllegalArgumentException("Invalid locale format: " + str);
        }
        final char ch0 = str.charAt(0);
        if (ch0 == '_') {
            if (len < 3) {
                throw new IllegalArgumentException("Invalid locale format: " + str);
            }
            final char ch1 = str.charAt(1);
            final char ch2 = str.charAt(2);
            if (!Character.isUpperCase(ch1) || !Character.isUpperCase(ch2)) {
                throw new IllegalArgumentException("Invalid locale format: " + str);
            }
            if (len == 3) {
                return new Locale(EMPTY, str.substring(1, 3));
            }
            if (len < 5) {
                throw new IllegalArgumentException("Invalid locale format: " + str);
            }
            if (str.charAt(3) != '_') {
                throw new IllegalArgumentException("Invalid locale format: " + str);
            }
            return new Locale(EMPTY, str.substring(1, 3), str.substring(4));
        }

        return parseLocale(str);
    }

    private Locale parseLocale(final String str) {
        if (isISO639LanguageCode(str)) {
            return new Locale(str);
        }

        final String[] segments = str.split("_", -1);
        final String language = segments[0];
        if (segments.length == 2) {
            final String country = segments[1];
            if (isISO639LanguageCode(language) && isISO3166CountryCode(country) ||
                    isNumericAreaCode(country)) {
                return new Locale(language, country);
            }
        } else if (segments.length == 3) {
            final String country = segments[1];
            final String variant = segments[2];
            if (isISO639LanguageCode(language) &&
                    (country.length() == 0 || isISO3166CountryCode(country) || isNumericAreaCode(country)) &&
                    variant.length() > 0) {
                return new Locale(language, country, variant);
            }
        }
        throw new IllegalArgumentException("Invalid locale format: " + str);
    }

    private boolean isISO639LanguageCode(final String str) {
        return isAllLowerCase(str) && (str.length() == 2 || str.length() == 3);
    }

    private boolean isISO3166CountryCode(final String str) {
        return isAllUpperCase(str) && str.length() == 2;
    }

    private boolean isNumericAreaCode(final String str) {
        return isNumeric(str) && str.length() == 3;
    }

    private boolean isAllLowerCase(final CharSequence cs) {
        if (cs == null || isEmpty(cs)) {
            return false;
        }
        final int sz = cs.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isLowerCase(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private boolean isAllUpperCase(final CharSequence cs) {
        if (cs == null || isEmpty(cs)) {
            return false;
        }
        final int sz = cs.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isUpperCase(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }

    private boolean isNumeric(final CharSequence cs) {
        if (isEmpty(cs)) {
            return false;
        }
        final int sz = cs.length();
        for (int i = 0; i < sz; i++) {
            if (!Character.isDigit(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }
}

下面是原来为这个class编写的单元测试,运行测试,覆盖率在80%左右。

利用ChatGPT协助编写单元测试
public class LocalUtilsTest {
    private LocaleUtils localeUtils;
    @Rule
    public ExpectedException exception = ExpectedException.none();
    @Before
    public void setUp() {
        localeUtils= new LocaleUtils();
    }

    @Test()
    public void should_return_null_when_str_is_null() {

        assertThat(localeUtils.toLocale(null)).isEqualTo(null);
    }

    @Test()
    public void should_call_isEmpty_when_str_is_empty() {
        assertThat(localeUtils.toLocale("").getLanguage().isEmpty());
        assertThat(localeUtils.toLocale("").getCountry().isEmpty());
    }

    @Test
    public void should_throw_exception_when_str_is_not_valid() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: #");
        localeUtils.toLocale("#");
    }

    @Test
    public void should_throw_exception_when_strLength_is_less_2(){
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: a");
        localeUtils.toLocale("a");
    }

    @Test
    public void should_throw_exception_when_strLength_is_less_3() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: _a");
        localeUtils.toLocale("_a");
    }
    @Test
    public void should_throw_exception_when_strLength_is_3_and_is_lowercase() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: _Aa");
        localeUtils.toLocale("_Aa");
    }

    @Test
    public void should_return_locale_when_strLength_is_3() {
      assertThat(localeUtils.toLocale("_AB").getCountry()).isEqualTo("AB");
    }
    @Test
    public void should_throw_exception_when_strLength_is_4() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: _ABC");
        localeUtils.toLocale("_ABC");
    }

    @Test
    public void should_throw_exception_when_str_3_is_not_valid(){
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: _ABC_");
        localeUtils.toLocale("_ABC_");
    }

    @Test
    public void should_return_locale_when_strLength_is_5() {
        assertThat(localeUtils.toLocale("_AB_DE").getCountry()).isEqualTo("AB");
    }

    @Test
    public void should_return_locale_when_str_is_ISO639LanguageCode_and_length_is_2() {
        assertThat(localeUtils.toLocale("ab").getLanguage()).isEqualTo("ab");
    }

    @Test
    public void should_return_locale_when_str_is_ISO639LanguageCode_and_length_is_3() {
        assertThat(localeUtils.toLocale("abc").getLanguage()).isEqualTo("abc");
    }

    @Test
    public void should_return_locale_include_language_country_when_str_is_abc_AB() {
        assertThat(localeUtils.toLocale("abc_AB").getLanguage()).isEqualTo("abc");
        assertThat(localeUtils.toLocale("abc_AB").getCountry()).isEqualTo("AB");
    }

    @Test
    public void should_return_locale_include_language_country_when_str_is_abc_123() {
        assertThat(localeUtils.toLocale("abc_123").getLanguage()).isEqualTo("abc");
        assertThat(localeUtils.toLocale("abc_123").getCountry()).isEqualTo("123");
    }

    @Test
    public void should_return_locale_include_language_country_variant_when_str_is_abc_123_ef() {
        assertThat(localeUtils.toLocale("abc_123_ab").getLanguage()).isEqualTo("abc");
        assertThat(localeUtils.toLocale("abc_123_ab").getCountry()).isEqualTo("123");
        assertThat(localeUtils.toLocale("abc_123_ef").getVariant()).isEqualTo("ef");
    }

    @Test
    public void should_throw_exception_when_str_is_abc_123_ef_d() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: abc_123_ef_d");
        localeUtils.toLocale("abc_123_ef_d");
    }
    @Test
    public void should_throw_exception_when_str_substring_is_not_ISO3166CountryCode() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: abc_aB");
        localeUtils.toLocale("abc_aB");
    }
    @Test
    public void should_throw_exception_when_str_is_not_ISO639LanguageCode() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: abC");
        localeUtils.toLocale("abC");
    }
    @Test
    public void should_throw_exception_when_str_substring_is_not_NumericAreaCode() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: abc_");
        localeUtils.toLocale("abc_");
    }
    @Test
    public void should_throw_exception_when_parsed_variant_length_is_0() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid locale format: abc_AB_");
        localeUtils.toLocale("abc_AB_");
    }
}

删除上面的单元测试,尝试用ChatGPT来自动化为上面的class编写单元测试,如下图所示:左边是输入的prompt,右边是ChatGPT生成的代码。

利用ChatGPT协助编写单元测试

生成的单元测试的名称不是用下滑线分割,但是我更喜欢用下滑线来分割单元测试名称,另外,默认是用Assert来进行断言,我更希望用AssertJ来作为断言库,那么可以在上面的promp的基础上进行修改,结果如下所示:除了修改单元测试名称和断言库外,上一版本生成的单元测试中对于异常的验证使用了assertThrows方法,实际该方法不存在,所以再次修改promp,让chatGPT用ExpectedException来编写异常情况的case。

利用ChatGPT协助编写单元测试

经过上面的修改后,编写全新的prompt,让chatGPT再次生成新的单元测试,修改后的Prompt如下所示:,copy单元测试到IDE工具上,虽然得到的覆盖率有点低(如下所示),但可直接运行,无任何报错:

利用ChatGPT协助编写单元测试
利用ChatGPT协助编写单元测试

此时,再修改prompt添加了覆盖率的要求,此时,chatGPT对私有方法编写了单元测试,但同时也给出了提示信息“不建议对私有方法编写单元测试,应该直接调用公有方法进行覆盖”,具体如下所示:

利用ChatGPT协助编写单元测试

另外,因为ChatGPT默认返回的tokens数量是4096,这包括输入的prompt的tokens个数和返回的response的tokens个数,所以,对于很长的代码,一次性生成完整的单元测试有难度,针对这种情况,建议在生成的基础版本上有针对的添加剩余的单元测试,即给ChatGPT更多的上下文信息来驱动生成单元测试。以下图为例,查看未覆盖的代码,针对性的给出prompt,让单元测试进一步完善。

利用ChatGPT协助编写单元测试
利用ChatGPT协助编写单元测试

修改Prompt,针对性的补充未覆盖的单元测试,修改后的prompt和自动生产的单元测试结果如下所示:可以看到单元测试中生成了len==3的case,另外还生成了len大于4的case,而对于边界值校验来说,真正需要的len是等于5和小于5且不等于3的情况,例如len==4的case,所以,在自动生成的基础上稍微修改下input就可以达到这个效果。

利用ChatGPT协助编写单元测试

总结而言,在prompt中基础的输入信息是"用junit,assertjs编写单元测试,且单元测试方法名称用下划线分割,方法名称以should开头,异常验证部分使用Junit中的ExpectedException",在基础prompt上,再结合实际情况输入针对性信息,即可借助chatGPT编写单元测试。

另外,需要注意一点:chatGPT有tokens的限制,所以,对于比较大的class,需要分段输入给chatGPT,否则返回的response结果有限。文章来源地址https://www.toymoban.com/news/detail-509803.html

到了这里,关于利用ChatGPT协助编写单元测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【单元测试】--编写单元测试

    一、编写第一个单元测试 编写第一个单元测试通常包括以下步骤。以下示例以C#和NUnit为例: 创建测试项目 : 在Visual Studio中,创建一个新的Class Library项目,这将是你的单元测试项目。 在解决方案资源管理器中,右键点击项目,选择 “管理 NuGet 包”,然后搜索并安装NUnit框

    2024年02月07日
    浏览(44)
  • 单元测试:优雅编写Kotlin单元测试

    一、MockK简介 MockK是一款功能强大、易于使用的Kotlin mocking框架。在编写 单元测试 时,MockK能够帮助我们简化代码、提高测试覆盖率,并改善测试的可维护性。除了基本用法外,MockK还提供了许多额外的功能和灵活的用法,让我们能够更好地模拟对象行为、验证函数调用,并在

    2024年02月10日
    浏览(46)
  • 【go语言开发】编写单元测试

    本文主要介绍使用go语言编写单元测试用例,首先介绍如何编写单元测试,然后介绍基本命令的使用,最后给出demo示例 在go语言中编写单元测试时,使用说明 测试文件命名 :在 Go 语言中,测试文件的命名应与被测试的源代码文件相同,但以 “_test” 结尾。例如,如果你的源

    2024年02月04日
    浏览(45)
  • springboot基础学习 之编写单元测试和集成测试。

    编写单元测试和集成测试是保障应用程序质量的关键步骤。单元测试主要用于测试单个组件或模块的功能,而集成测试则关注不同组件之间的协作。下面分别介绍如何编写单元测试和集成测试。 单元测试(Unit Testing): 1. 选择测试框架: 选择适合你项目的测试框架,例如

    2024年02月02日
    浏览(44)
  • 软件测试中如何编写单元测试用例(白盒测试)

    目录 前言: 一、 单元测试的概念 二、开始测试前的准备 三、开始测试 四、完成测试 前言: 单元测试是软件测试中一种重要的测试方法,它是在代码级别进行测试,通过对每个模块或功能进行独立测试来保障代码的正确性和可靠性。单元测试可以有效地避免产生隐藏的代

    2024年02月09日
    浏览(51)
  • 系统学习Python——单元测试unittest:编写测试用例

    分类目录:《系统学习Python》总目录 相关文章: · 单元测试unittest:框架结构 · 单元测试unittest:测试固件 · 单元测试unittest:编写测试用例 · 单元测试unittest:执行测试用例 · 单元测试unittest:用例执行次序 · 单元测试unittest:内置装饰器 · 单元测试unittest:命令行执行测

    2023年04月08日
    浏览(62)
  • 5个编写技巧,有效提高单元测试实践

    “在计算机编程中,单元测试又称为模块测试,是针对程序模块来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类、抽象类、或者派生类中的方法。

    2023年04月26日
    浏览(43)
  • 单元测试:如何编写可测试的代码?

    在编写代码的时候,大部分时间想的都是如何实现功能,很少会考虑到代码的可测试性。 又因为大部分公司没有要求写单元测试,完成的功能都是通过服务模拟的方式测试,更加不会考虑代码的可测试性了。 常见的可测试性不好的代码,几种情况(取自极客时间王铮设计模

    2024年02月08日
    浏览(48)
  • Go单元测试与集成测试:编写可靠的Go测试用例

    Go语言是一种现代编程语言,它具有简洁的语法、强大的性能和易于使用的并发特性。随着Go语言的发展和广泛应用,编写高质量的测试用例变得越来越重要。在Go语言中,我们可以使用内置的testing包来编写单元测试和集成测试。本文将涵盖Go单元测试与集成测试的核心概念、

    2024年02月19日
    浏览(39)
  • 使用Mockito针对多线程场景编写单元测试

    Mockito是一个Java的Mocking框架,主要用于编写单元测试。针对多线程编程的单元测试,可以使用Mockito的一些特性和技巧来完成。  Mockito支持异步调用技术,可以使用Mockito.when().thenReturnAsync()方法来模拟异步调用的返回值。这样可以模拟多线程编程的情况。 例如,假设有一个异

    2024年02月09日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包