实战案例|利用MarsCode内置的DeepSeek服务,单元测试耗时缩短70%!

大模型向量数据库容器

单元测试总在奇怪的地方卡 bug?Mock 配置像解谜游戏、边界条件比数学题还烧脑?

没关系!

MarsCode 编程助手 X 三款大模型(DeepSeek V3、DeepSeek R1、豆包大模型1.5 )帮你解决所有问题

无需配置,性能Top,代码准确率嗖嗖🚀

用过的朋友都说: “以前写测试像开手动挡,现在像开了自动巡航” ,速看~👇👇👇

准备工作

在正式开始单元测试之前,我们先做好相关准备👇

1. 下载/更新MarsCode 编程助手

1️⃣如果你是新用户,以Visual Studio Code中为例,打开VSCode 扩展窗口,在搜索窗口搜索MarsCode,找到MarsCode 插件单击「install」,完成安装,登录即可使用MarsCode 编程助手。

VSCode下载地址:https://code.visualstudio.com/Download

JetBrains 下载地址:https://www.jetbrains.com.cn/

picture.image

2️⃣ 如果你是老用户,请更新MarsCode 编程助手到最新版本(若开启了自动更新,则将会自动更新),更新后重启IDE即可

*VSCode:1.1.62

*JetBrains:1.2.1.15

picture.image

2. 克隆案例项目

本次案例使用的是AI生成的一个最基础的React项目,目的是模拟学习工作中最真实的场景,方便大家迅速掌握快速搭建单元测试环境以及生成单元测试用例的技巧。


          
              

            git clone https://github.com/ylx911229/unit-test\_back.git
          
        

picture.image

3. 单元测试基础

*因为后续案例选用的是Jest作为单元测试框架,所以介绍的基础内容主要以Jest框架为标准

  • 概念介绍

1️⃣ 断言(Assertion) :用于验证测试结果是否符合预期的语句,如 Expect

2️⃣ 测试替身(Test Double) :用于替代真实依赖的模拟对象,确保测试的隔离性,如 Mock、Jest.fn

3️⃣ 测试覆盖率(Test Coverage) :用于衡量测试用例覆盖代码的比例,如 行覆盖率、分支覆盖率

  • 单元测试文件命名规范

生成的单元测试文件名必须以 .test.ts/.test.js 作为结尾,否则单元测试框架无法读取并执行单元测试用例

  • 运行单元测试用例

          
              

            npx jest --coverage
          
        

实战跟练

STEP1:搭建测试环境

首先来搭建一下单元测试的环境,向MarsCode输入以下提示词:


          
              

            Workspace 帮我为整个项目搭建一下单元测试的环境
          
        

picture.image

可以看到MarsCode给我们推荐的单元测试框架是Jest,这是一个流行的JavaScript测试框架,特别适合用于单元测试,可以点击https://jestjs.io/docs/getting-started学习Jest相关内容

另外,React组件的单元测试,依赖 React Testing Library ,RTL是当前 React 生态中最流行的组件测试解决方案,它提供了一套更贴近真实用户行为的测试工具链。相关信息可查询👉https://testing-library.com/docs/react-testing-library/intro

STEP2:单元测试用例编写

关于单元测试用例,将从函数类和UI类等不同的方式类型来举例实现

  • 函数类

1️⃣ 纯函数 & 工具类 对于纯函数的工具类单元测试,特点是输入输出明确,无副作用,整体测试重点是 输出格式验证和唯一性检查,接下来以 生成唯一 ID 为例:


            
// 生成唯一 ID
            
function generateId(prefix) {
            
  return `${prefix}_${Math.random().toString(36).slice(2, 9)}`;
            
}
        

我们可以打开src\utils\tool-utils.js,选中代码,在对话框直接选择/test功能形成单元测试:

picture.image

将生成的单测代码另存为tool-utils.test.js,保存后执行得到如下效果:

picture.image

结果显示覆盖率100%,但是有一个用例并未通过,显示特殊字符作为前缀输出的结果未匹配正则。

picture.image

我们可以切换到DeepSeek R1模型并选中文件,将出现的问题告诉MarsCode后将生成的代码替换进原来的tool-utils.test.js,再次运行 npx jest --coverage 可以发现问题已解决~

picture.image picture.image

如果test代码未运行成功出现报错,可以将报错内容复制给MarsCode,利用AI问答继续解决问题。

2️⃣ 数据转换 & 验证

接下来以用户资料表单处理器为例,处理结构化数据或验证规则,这则测试案例重点在于正常数据清洗(trim、类型转换)、异常输入(空值、非法邮箱、年龄不足)、 及错误消息准确性,打开validate-utils.js,选择生成单测:


            
// 转换并验证用户输入
            
export const processUserInput = (formData) => {
            
  const result = {
            
      name: formData.name.trim(),
            
      age: parseInt(formData.age, 10),
            
      email: formData.email.toLowerCase()
            
  };
            

            
  if (isNaN(result.age)) {
            
    throw new Error('Invalid age: must be a number');
            
  }
            

            
  if (!/^[\w.+]+@\w+\.\w+$/.test(result.email)) throw new Error('Invalid email');
            
  if (result.age < 18) throw new Error('Underage');
            
  
            
  return result;
            
}
        

picture.image

同样将MarsCode生成的单测代码保存为validate-utils.test.js后运行,效果如下:

picture.image

3️⃣ 状态管理 & 业务逻辑 现在我们来探讨更复杂业务逻辑下的单测,以购物车 Redux reducer为例,涉及核心业务规则或全局状态变更,这个案例中我们的测试重点在

  • 商品添加逻辑(新增 vs 增量)
  • 促销码有效性验证
  • 不可变数据检查

            
// cartReducer 函数
            
export const cartReducer = (state = { items: [] }, action) => {
            
  switch (action.type) {
            
    case 'ADD_ITEM':
            
      const existing = state.items.find(item => item.id === action.payload.id);
            
      if (existing) {
            
        return {
            
          ...state,
            
          items: state.items.map(item => 
            
            item.id === action.payload.id 
            
              ? { ...item, qty: item.qty + 1 }
            
              : item
            
          )
            
        };
            
      }
            
      return { ...state, items: [...state.items, action.payload] };
            
      
            
    case 'APPLY_PROMO':
            
      if (!action.payload.isValid) return state;
            
      return { ...state, promoCode: action.payload.code };
            
      
            
    default:
            
      return state;
            
  }
            
}
        

现在打开businuss-utils.js,同样选中/test 生成单测:

picture.image

将MarsCode 生成的新文件保存为businuss-utils.test.js,运行单测命令,得到如下效果:

picture.image

  • UI类

我们以用户输入花费金额的REACT组件为例,我们单测的重点在于事件是否能正确触发以及UI是否能正常显示,打开react-test.jsx文件:


            
import React from 'react';
            
import { render, fireEvent } from '@testing-library/react';
            
import { CostInput } from './react-test';
            

            
describe('CostInput Component', () => {
            
  beforeEach(() => {
            
    jest.clearAllMocks();
            

            
    // 设置全局变量
            
    global.navigator = {
            
      // 传入navigator的值
            
    };
            

            
    global.document = {
            
      // 传入document的值
            
    };
            

            
    global.window = {
            
      // 传入window的值
            
    };
            

            
    global.otherVariables = {
            
      // 传入otherVariables的值
            
    };
            
  });
            

            
  test('renders without crashing', () => {
            
    render(<CostInput />);
            
  });
            

            
  test('calls handleChange when the input changes with valid number', () => {
            
    const handleChange = jest.fn();
            
    const { getByLabelText } = render(<CostInput handleChange={handleChange} />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '123' } });
            
    expect(handleChange).toHaveBeenCalled();
            
  });
            

            
  test('does not call handleChange when the input is invalid', () => {
            
    const handleChange = jest.fn();
            
    const { getByLabelText } = render(<CostInput handleChange={handleChange} />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: 'abc' } });
            
    expect(handleChange).toHaveBeenCalled();
            
  });
            

            
  test('displays the correct value with dollar sign', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '123' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('$123');
            
  });
            

            
  test('displays empty value when input is invalid', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: 'abc' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('');
            
  });
            

            
  test('handles initial dollar sign correctly', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '$123' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('$123');
            
  });
            

            
  test('handles empty input correctly', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('');
            
  });
            

            
  test('handles input with leading zeros correctly', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '00123' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('$123');
            
  });
            

            
  test('handles input with multiple dollar signs correctly', () => {
            
    const { getByLabelText } = render(<CostInput />);
            
    fireEvent.change(getByLabelText('cost-input'), { target: { value: '$$123' } });
            
    expect(getByLabelText('cost-input')).toHaveValue('$123');
            
  });
            
});
        

picture.image

向MarsCode 输入单测/test,同样将生成的代码另存为react_tes t.test.js进行运行,运行之后发现单测覆盖率只有62.5%,不太理想。

picture.image

我们可以继续让MarsCode 生成单测补充用例,提高覆盖率

picture.image

应 用代码后,可以看到单测覆盖率 提升到83.33%,同时帮我们优化了部分代码,甚至对原来的handleChange函数进行了优化,提升了代码质量。

picture.image

通过本次实践,我们不仅掌握了单元测试的核心价值与实施方法,更验证了 MarsCode 在提升代码质量方面的工程价值。 相信在更复杂的业务场景下,优秀的工具能帮助开发者释放更多生产力。


欢迎点击【阅读原文】,立即体验全新升级MarsCode 编程助手!

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 XR 技术的探索与实践
火山引擎开发者社区技术大讲堂第二期邀请到了火山引擎 XR 技术负责人和火山引擎创作 CV 技术负责人,为大家分享字节跳动积累的前沿视觉技术及内外部的应用实践,揭秘现代炫酷的视觉效果背后的技术实现。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论