尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Jest单元测试实战:Mock、快照与覆盖率配置详解

Jest单元测试实战:Mock、快照与覆盖率配置详解
📅 发布时间:2026/7/26 10:18:55

1. 项目概述:为什么前端单元测试是开发者的“安全网”?

做前端开发这些年,我见过太多因为一个小改动导致整个页面崩溃的案例。尤其是在大型项目里,一个看似无关紧要的组件更新,可能会像多米诺骨牌一样引发连锁反应。这时候,一套完善的单元测试就成了我们开发者的“安全网”。它不仅能让你在修改代码时心里有底,更是团队协作和代码重构的基石。

今天要聊的,就是前端单元测试领域最主流的工具之一:Jest。它之所以能成为React等生态的默认选择,不是没有道理的。速度快、零配置、功能强大,这些都是它的标签。但很多朋友在刚开始接触时,往往卡在几个关键概念上:怎么模拟(mock)外部依赖?快照测试(snapshot testing)到底该怎么用?还有那个覆盖率(coverage)报告,配置起来总是一头雾水。这篇文章,我就结合自己踩过的坑和实战经验,把这几个核心环节掰开揉碎了讲清楚,让你能快速上手,构建起自己项目的测试防线。

2. Jest 核心能力深度解析:不止是“运行测试”

很多人对Jest的理解还停留在“一个测试运行器”的层面,这其实大大低估了它的能力。Jest提供的是一个完整的测试解决方案,从测试框架、断言库、模拟工具到覆盖率收集,它全都包了。这种“全家桶”式的设计,避免了我们在不同工具间来回切换和配置的麻烦,这也是它“零配置”口号背后的底气。

2.1 Mock 机制:隔离测试环境的艺术

单元测试的核心原则是“隔离”。我们要测试的是当前单元(一个函数、一个组件)的逻辑,而不是它依赖的第三方服务、模块或浏览器API。Mock就是实现这种隔离的关键技术。Jest的mock系统非常灵活,大致可以分为三类:函数mock、模块mock和定时器mock。

函数mock是最常用的。比如,你有一个函数调用了axios去发起网络请求,在单元测试里,你肯定不希望真的去发送请求。这时候,你就可以用jest.fn()来创建一个模拟函数。

// 假设我们有一个用户服务模块 import axios from 'axios'; export async function fetchUser(userId) { const response = await axios.get(`/api/users/${userId}`); return response.data; } // 在测试文件中 import { fetchUser } from './userService'; import axios from 'axios'; jest.mock('axios'); // 关键的一步:告诉Jest要模拟整个axios模块 test('fetchUser returns user data', async () => { const mockUser = { id: 1, name: 'John Doe' }; // 模拟axios.get方法的实现和返回值 axios.get.mockResolvedValue({ data: mockUser }); const user = await fetchUser(1); expect(axios.get).toHaveBeenCalledWith('/api/users/1'); expect(user).toEqual(mockUser); });

这里有个非常重要的细节:jest.mock('axios')必须放在文件顶部,在import语句之后。这是因为Jest会在真正执行测试代码之前,先处理这些mock声明,用模拟模块替换掉真正的模块。

模块mock则更进一步,它允许你为一个模块提供完整的模拟实现。这在处理那些具有复杂内部逻辑或副作用的模块时特别有用。你可以使用jest.mock('./modulePath', () => { ... })来提供一个工厂函数,返回你自定义的模拟对象。

定时器mock是处理setTimeout、setInterval等异步操作的利器。使用jest.useFakeTimers()后,你可以用jest.advanceTimersByTime(ms)来“快进”时间,从而无需等待就能测试到定时回调里的逻辑。这对于测试倒计时组件、防抖/节流函数等场景至关重要。

实操心得:不要过度Mock。Mock的初衷是隔离不稳定或外部依赖。如果一个模块是你自己写的、逻辑简单且稳定,直接引入真实模块进行集成测试往往更可靠。过度Mock会让测试变得脆弱,且无法反映模块间的真实交互。

2.2 Snapshot Testing:UI回归测试的“守门员”

快照测试是Jest为React等UI框架量身定做的一个杀手锏。它的原理非常简单:第一次运行测试时,Jest会将你的组件渲染结果(一个序列化的字符串,通常是HTML结构或特定格式的对象)保存为一个“快照”文件(.snap文件)。后续每次运行测试,它都会将新的渲染结果与之前保存的快照进行比对。如果一致,测试通过;如果不一致,测试失败,并高亮显示差异。

这听起来很像图像对比测试,但它对比的是结构化的文本,因此速度极快,且差异一目了然。

// 一个简单的React组件测试 import React from 'react'; import renderer from 'react-test-renderer'; import { MyButton } from './MyButton'; test('MyButton renders correctly', () => { const tree = renderer.create(<MyButton label="Click me" />).toJSON(); expect(tree).toMatchSnapshot(); });

第一次运行后,会在__snapshots__目录下生成一个快照文件。如果后续你修改了MyButton的样式或结构,导致渲染输出变化,测试就会失败。这时,你需要判断这个变化是预期的(比如你主动修改了按钮颜色)还是非预期的(比如不小心改坏了样式)。

如果是预期变化,你可以通过运行jest --updateSnapshot或jest -u来更新快照。Jest会用新的渲染结果覆盖旧的快照文件。

快照测试的最佳实践与常见陷阱:

  1. 不要滥用:快照测试最适合用于展示型组件(Presentational Components),即主要根据props渲染UI,没有或很少有内部状态的组件。对于复杂的容器组件或逻辑组件,快照测试可能会因为内部状态或副作用而变得不稳定,且快照文件会异常庞大,难以维护。
  2. 关注核心输出:在生成快照前,可以考虑对组件输出进行“修剪”。例如,使用jest-styled-components这样的库来只捕获样式相关的快照,或者手动序列化时只选择关键的props和状态。
  3. 审查每一次快照更新:当测试失败提示快照不匹配时,一定要仔细查看差异(diff)。Jest会清晰地用“-”表示旧内容,“+”表示新内容。确保你理解并认可每一处变化,而不是盲目地更新快照。把更新快照当作一次代码审查的机会。
  4. 将大快照拆小:如果一个组件的快照行数成百上千,说明它可能做了太多事情,或者你的快照包含了太多不重要的细节(比如随机生成的ID)。考虑将组件拆分成更小的子组件,或者使用自定义序列化器来忽略某些属性。

2.3 Coverage 配置:衡量测试完备性的“仪表盘”

测试覆盖率是一个有争议但无法回避的指标。它量化了你的测试代码对业务代码的覆盖程度,通常包括四个维度:

  • 行覆盖率(Line Coverage):有多少比例的代码行被至少执行了一次。
  • 语句覆盖率(Statement Coverage):类似于行覆盖率,但粒度更细。
  • 分支覆盖率(Branch Coverage):控制结构(如if/else, switch case)中的每个分支是否都被测试到。
  • 函数覆盖率(Function Coverage):有多少比例的函数被调用过。

Jest内置了基于Istanbul的覆盖率收集工具,配置起来非常方便。你可以在package.json中或通过jest.config.js文件进行配置。

// jest.config.js module.exports = { // ... 其他配置 collectCoverage: true, // 是否收集覆盖率信息 collectCoverageFrom: [ // 指定需要收集覆盖率的文件范围 'src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts', '!src/index.tsx', // 通常排除入口文件 '!src/**/__tests__/**', // 排除测试文件本身 '!src/**/*.stories.{js,jsx,ts,tsx}', // 排除Storybook文件等 ], coverageThreshold: { // 覆盖率阈值,不满足时测试将失败 global: { branches: 80, functions: 80, lines: 80, statements: 80 }, './src/components/': { // 可以为特定目录设置更高的要求 branches: 90, functions: 90, lines: 90, statements: 90 } }, coverageReporters: ['text', 'lcov', 'html'] // 生成报告的格式 };

运行jest --coverage后,Jest会在终端输出一个简洁的表格,并在项目根目录生成一个coverage文件夹。打开里面的index.html,你可以看到一个交互式的HTML报告,可以精确地点击查看每个文件的哪一行代码没有被测试覆盖。

重要提示:覆盖率只是一个参考指标,而非目标。高覆盖率不等于高质量测试。要警惕“为了覆盖率而测试”的陷阱,比如编写一些只执行代码但毫无断言(assertion)的无效测试。我们的目标应该是通过测试来保障核心业务逻辑的正确性,覆盖率是帮助我们发现测试盲区的工具,而不是追求的终极数字。设定合理的、分层的阈值(如核心工具函数要求90%,普通组件80%)是更务实的做法。

3. 从零搭建一个可用的Jest测试环境

理解了核心概念,我们动手搭一个环境。假设我们有一个基于Vite + React + TypeScript的现代前端项目。

3.1 基础安装与配置

首先,安装Jest及其相关依赖。因为我们要测试React组件和TypeScript代码,所以需要额外的转译器。

npm install --save-dev jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom
  • jest: 测试框架本体。
  • @types/jest: 为Jest提供TypeScript类型定义。
  • ts-jest: 一个Jest的预处理器,用于处理TypeScript文件。
  • @testing-library/react&@testing-library/jest-dom: React Testing Library的核心,它提倡以用户视角(通过DOM)来测试组件,比直接测试组件实例更贴近真实使用场景。jest-dom提供了丰富的自定义Jest匹配器(matcher),如.toBeVisible(),.toHaveAttribute(),让断言更语义化。

接下来,创建Jest配置文件。你可以用npx jest --init生成一个交互式向导,也可以手动创建jest.config.js。

// jest.config.js /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { // 测试环境,对于前端项目,我们通常使用 jsdom 来模拟浏览器环境 testEnvironment: 'jsdom', // 使用 ts-jest 来处理 ts/tsx 文件 transform: { '^.+\\.(ts|tsx)$': 'ts-jest', // 如果你还有其它类型的文件需要处理(比如用babel处理js),可以在这里添加 // '^.+\\.(js|jsx)$': 'babel-jest', }, // 匹配哪些文件是测试文件 testMatch: [ '**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)' ], // 设置模块路径别名映射(如果你在tsconfig或vite.config里配置了alias) moduleNameMapper: { '^@/(.*)$': '<rootDir>/src/$1', // 将 @/ 映射到 src/ '\\.(css|less|scss|sass)$': 'identity-obj-proxy', // 模拟样式文件 '\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js', // 模拟图片等静态资源 }, // 每次测试前需要执行的文件,常用于设置全局的测试环境 setupFilesAfterEnv: ['<rootDir>/jest.setup.js'], };

然后,创建jest.setup.js文件,用于引入一些全局的测试配置。

// jest.setup.js // 引入 @testing-library/jest-dom 提供的扩展匹配器 import '@testing-library/jest-dom'; // 你可以在这里添加其它全局设置,比如全局的mock // 例如,如果不想在每个测试文件里都 mock fetch,可以在这里统一做 // global.fetch = jest.fn();

最后,别忘了创建静态资源的Mock文件。

// __mocks__/fileMock.js module.exports = 'test-file-stub';
// __mocks__/styleMock.js (如果需要) module.exports = {};

现在,在package.json的scripts里添加测试命令:

{ "scripts": { "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage" } }

运行npm test,你的Jest环境就应该能正常工作了。

3.2 编写你的第一个综合测试用例

让我们用一个稍微复杂点的例子,把mock、快照和覆盖率都串起来。假设我们有一个UserProfile组件,它接收一个用户ID,调用服务层获取用户数据并展示,同时有一个“刷新”按钮。

// src/components/UserProfile/UserProfile.tsx import React, { useState, useEffect } from 'react'; import { fetchUserById, User } from '@/services/userService'; import './UserProfile.css'; interface UserProfileProps { userId: number; } export const UserProfile: React.FC<UserProfileProps> = ({ userId }) => { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); const loadUser = async () => { setLoading(true); setError(null); try { const data = await fetchUserById(userId); setUser(data); } catch (err) { setError('Failed to load user'); console.error(err); } finally { setLoading(false); } }; useEffect(() => { loadUser(); }, [userId]); if (loading) return <div className="loading">Loading...</div>; if (error) return <div className="error">{error}</div>; if (!user) return null; return ( <div className="user-profile">// src/services/userService.ts import axios from 'axios'; export interface User { id: number; name: string; email: string; role: string; } export const fetchUserById = async (id: number): Promise<User> => { // 注意:这里使用了我们在jest.config.js中配置的路径别名 @ const response = await axios.get(`/api/users/${id}`); return response.data; };

现在,我们来为这个组件编写测试。

// src/components/UserProfile/UserProfile.test.tsx import React from 'react'; import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { UserProfile } from './UserProfile'; import { fetchUserById } from '@/services/userService'; import axios from 'axios'; // 1. Mock 整个 axios 模块 jest.mock('axios'); // 2. Mock 我们的服务层函数(因为我们已经mock了axios,这里也可以选择不mock,但为了演示清晰,我们mock它) jest.mock('@/services/userService'); // 在测试开始前,清除所有mock的调用记录,避免测试间相互影响 beforeEach(() => { jest.clearAllMocks(); }); describe('UserProfile Component', () => { const mockUser = { id: 1, name: 'Alice Smith', email: 'alice@example.com', role: 'Admin', }; test('3.1 初始加载时显示 Loading 状态,然后渲染用户数据', async () => { // 模拟 fetchUserById 返回成功数据 (fetchUserById as jest.Mock).mockResolvedValueOnce(mockUser); render(<UserProfile userId={1} />); // 断言初始状态是 Loading expect(screen.getByText(/loading/i)).toBeInTheDocument(); // 等待异步操作完成,并断言用户信息被正确渲染 await waitFor(() => { expect(screen.getByText(mockUser.name)).toBeInTheDocument(); expect(screen.getByText(`Email: ${mockUser.email}`)).toBeInTheDocument(); expect(screen.getByText(`Role: ${mockUser.role}`)).toBeInTheDocument(); }); // 断言 fetchUserById 被以正确的参数调用 expect(fetchUserById).toHaveBeenCalledTimes(1); expect(fetchUserById).toHaveBeenCalledWith(1); // 断言 Loading 状态消失 expect(screen.queryByText(/loading/i)).not.toBeInTheDocument(); }); test('3.2 点击 Refresh 按钮重新加载数据', async () => { (fetchUserById as jest.Mock) .mockResolvedValueOnce(mockUser) // 第一次加载 .mockResolvedValueOnce({ ...mockUser, name: 'Alice Updated' }); // 点击刷新后 render(<UserProfile userId={1} />); // 等待第一次加载完成 await waitFor(() => screen.getByText(mockUser.name)); // 找到并点击刷新按钮 const refreshButton = screen.getByRole('button', { name: /refresh/i }); fireEvent.click(refreshButton); // 点击后按钮应处于禁用状态(因为loading) expect(refreshButton).toBeDisabled(); // 等待第二次加载完成,并断言数据更新 await waitFor(() => { expect(screen.getByText('Alice Updated')).toBeInTheDocument(); }); // 断言 fetchUserById 被调用了两次 expect(fetchUserById).toHaveBeenCalledTimes(2); // 断言按钮恢复可用状态 expect(refreshButton).not.toBeDisabled(); }); test('3.3 网络请求失败时显示错误信息', async () => { // 模拟请求失败 (fetchUserById as jest.Mock).mockRejectedValueOnce(new Error('Network Error')); render(<UserProfile userId={999} />); // 等待错误信息出现 await waitFor(() => { expect(screen.getByText(/failed to load user/i)).toBeInTheDocument(); }); // 断言 Loading 状态已消失 expect(screen.queryByText(/loading/i)).not.toBeInTheDocument(); }); test('3.4 组件渲染快照匹配', () => { // 注意:快照测试需要数据是确定的。我们mock一个立即解决的Promise。 (fetchUserById as jest.Mock).mockResolvedValue(mockUser); const { asFragment } = render(<UserProfile userId={1} />); // 初次渲染时(Loading状态)的快照 expect(asFragment()).toMatchSnapshot('initial loading state'); // 通常,对于异步组件,我们更倾向于测试其最终状态。 // 但快照测试异步组件比较棘手,一个更好的模式是测试一个传入静态props的纯展示版本。 // 因此,快照测试更适合不依赖外部数据的纯展示组件。 // 对于此组件,我们可以考虑将其拆分为 Presentational 和 Container 组件,然后对 Presentational 部分做快照。 }); });

这个测试文件涵盖了:

  • Mock异步函数:模拟了fetchUserById的成功和失败情况。
  • 模拟用户交互:使用fireEvent.click模拟点击按钮。
  • 异步测试:使用waitFor等待组件状态更新和DOM变化。
  • 丰富的断言:使用了@testing-library/jest-dom提供的toBeInTheDocument,toBeDisabled等语义化匹配器。

运行npm test或npm run test:coverage,你就能看到这个组件的测试运行结果和覆盖率报告了。

4. 高级技巧与实战避坑指南

掌握了基础,我们来看看一些能让你测试水平更上一层楼的进阶技巧和那些容易踩的坑。

4.1 Mock 的精细控制:jest.spyOn与mockImplementation

有时候你不想完全替换一个模块,只是想监听(spy)某个方法的调用,或者临时改变它的实现。这时jest.spyOn就派上用场了。

// 假设我们有一个工具函数,调用了原生的 Date.now import { someFunction } from './utils'; test('someFunction uses current time', () => { // 创建一个监听器,监听全局 Date 对象的 now 方法 const spy = jest.spyOn(Date, 'now'); // 你可以让它返回一个固定值 spy.mockReturnValue(1625097600000); // 2021-07-01 someFunction(); expect(spy).toHaveBeenCalled(); // 测试结束后恢复原状,避免影响其他测试 spy.mockRestore(); });

mockImplementation则允许你动态定义模拟函数的具体实现,这在需要根据不同测试用例返回不同值的时候非常有用。

const mockApi = { fetchData: jest.fn(), // 先创建一个mock函数 }; test('case A', () => { mockApi.fetchData.mockImplementation(() => Promise.resolve('data for A')); // ... 测试逻辑 }); test('case B', () => { mockApi.fetchData.mockImplementation(() => Promise.reject(new Error('error for B'))); // ... 测试逻辑 });

4.2 测试覆盖率报告的优化与解读

生成的HTML覆盖率报告非常直观。红色行表示未覆盖,黄色行表示分支未完全覆盖(如if语句只走了true分支)。点击文件名,你可以逐行查看覆盖情况。

如何提高覆盖率?

  1. 覆盖边缘情况(Edge Cases):检查你的测试是否覆盖了函数的所有可能输入,特别是边界值、空值、非法值。例如,一个处理数组的函数,你测试了正常数组,那空数组[]、null、undefined输入呢?
  2. 覆盖所有分支:对于每个if/else、switch、三元运算符,确保你的测试用例能走到每一个分支。覆盖率报告中的黄色行就是提示你这里有分支没走到。
  3. 不要忽视错误处理:try...catch块、Promise的.catch()分支,这些处理错误路径的代码同样重要,需要编写触发错误的测试用例来覆盖。

覆盖率陷阱:

  • 虚假的100%覆盖:如果你只是调用了函数,但没有对其输出或行为进行任何断言(expect),那么这行代码虽然被执行了(算入覆盖率),但测试是无效的。确保每个测试都有明确的断言。
  • 难以覆盖的代码:有些代码在测试环境下就是很难触发,比如针对特定浏览器bug的polyfill、只在process.env.NODE_ENV === 'production'下运行的代码。对于这类代码,可以考虑使用/* istanbul ignore next */注释来让覆盖率工具忽略它们,并在代码旁添加清晰的注释说明原因。

4.3 与其它工具链的集成

与ESLint集成:使用eslint-plugin-jest可以让你在编写测试时也获得语法和最佳实践的提示,比如避免在describe块外写测试,或者提醒你清理mock。

与Husky集成:在package.json中配置pre-commit钩子,在每次提交前自动运行测试,确保有问题的代码不会被提交。

{ "husky": { "hooks": { "pre-commit": "npm run test -- --passWithNoTests" } } }

与CI/CD集成:在GitHub Actions、GitLab CI等持续集成环境中,将npm run test:coverage作为必过的关卡。你还可以使用coveralls或codecov等服务,将覆盖率报告上传并集成到Pull Request的评论中,可视化地展示改动对覆盖率的影响。

5. 常见问题排查与调试技巧

即使经验丰富,写测试时也会遇到各种诡异的问题。这里记录几个我经常碰到的情况和解决方法。

问题一:“Cannot find module ‘module-name’ from ‘test-file.js’”

  • 原因:Jest无法解析模块路径。通常是因为模块别名(alias)或Node核心模块(如path,fs)在Jest环境中没有正确配置。
  • 解决:
    1. 检查jest.config.js中的moduleNameMapper,确保正确映射了项目中的路径别名。
    2. 对于Node核心模块,Jest的testEnvironment: 'node'默认包含它们。但在jsdom环境下,你可能需要手动mock,或者在moduleNameMapper中指向一个polyfill(如'^path$': require.resolve('path-browserify'))。

问题二:“ReferenceError: document is not defined”或“window is not defined”

  • 原因:你的测试代码(或你导入的模块)在运行环境(Node.js)中试图访问浏览器特有的全局对象document或window。
  • 解决:
    1. 确保jest.config.js中设置了testEnvironment: 'jsdom'。jsdom会模拟一个浏览器环境。
    2. 如果问题出在某个特定的第三方库上,你可能需要在jest.setup.js中手动设置全局变量,或者使用jest.mock来模拟这个库。

问题三:测试通过,但控制台输出“Not implemented: window.alert”等警告

  • 原因:jsdom并没有实现所有的Web API,alert、confirm等就是其中之一。你的代码或依赖的库调用了这些未实现的API。
  • 解决:在测试文件或jest.setup.js中mock掉它们。
    // jest.setup.js window.alert = jest.fn(); window.confirm = jest.fn(() => true); // 默认返回true

问题四:异步测试超时(Timeout)

  • 原因:默认情况下,Jest的异步测试超时时间是5秒。如果你的异步操作(如网络请求、定时器)没有在预期内完成或正确模拟,测试就会超时失败。
  • 解决:
    1. 检查Mock:确保所有异步操作(如API调用、Promise)都被正确mock,并立即返回(mockResolvedValue)或拒绝(mockRejectedValue)。
    2. 使用async/await和waitFor:对于组件测试,确保使用了async测试函数和@testing-library/react的waitFor来等待DOM更新。
    3. 调整超时时间:在特定的测试用例上使用jest.setTimeout(10000)来延长超时时间(谨慎使用,优先检查代码逻辑)。

问题五:快照测试因无关紧要的差异(如随机ID、日期)而失败

  • 原因:组件输出中包含每次运行都会变化的值。
  • 解决:在生成快照前,将这些不稳定值固定下来。
    1. Mock 随机源:jest.spyOn(Math, 'random').mockReturnValue(0.5)。
    2. Mock 日期:jest.useFakeTimers().setSystemTime(new Date('2023-01-01'))。
    3. 使用序列化器:自定义一个Jest序列化器,在序列化组件树时替换掉这些不稳定属性。对于像styled-components生成的随机类名,使用jest-styled-components库可以很好地解决。

调试测试本身也是一个技能。除了看错误信息,你还可以:

  • 使用console.log在测试和被测代码中打印信息。
  • 在VSCode中,使用Jest扩展插件进行断点调试。
  • 使用jest --verbose获取更详细的输出。
  • 对于特别棘手的测试,可以临时使用test.only或describe.only来只运行单个测试,快速定位问题。

写测试就像给代码买保险,前期投入时间,后期节省大量调试和修复的精力。从最重要的工具函数和核心业务组件开始,逐步扩大测试范围,形成习惯后,你会发现自己的代码质量和开发信心都会有质的提升。

相关新闻

  • Blender与UE5虚拟制片管线搭建:从资产规范到实时渲染全流程解析
  • OMAP5910 UART深度解析:从自动波特率到IrDA的嵌入式串口实战
  • 多模态遥感数据统一表征学习技术TerraFM解析

最新新闻

  • 3分钟快速上手:终极图表数字化工具WebPlotDigitizer完整指南
  • 2026 上海翡翠回收行情简析!闲置翡翠手镯、挂件出手时机与渠道选择 - 全国二奢机构参考
  • 2026河源龙川黄金回收正规门店推荐 金饰抵押变现避坑与靠谱渠道盘点 - 行走在冷风中。
  • OmenSuperHub终极指南:3步彻底掌控你的暗影精灵笔记本性能
  • Bielik.ai开源大语言模型:专为波兰语优化的部署与应用指南
  • WSL2环境部署与性能优化全攻略

日新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号