You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对编译器可强制执行的Post标题必填需求做单元测试?

这是个非常务实的问题——虽然TypeScript的严格空检查在编译阶段帮我们拦住了不少错误,但单元测试依然是守住业务规则的关键防线,咱们一步步说清楚:

一、怎么写单元测试验证这个需求?

因为TS严格模式下直接传null/undefined/空字符串会触发编译报错,所以我们需要绕过类型检查,模拟运行时可能出现的非法输入,验证构造函数的报错逻辑。以Jest为例,测试代码可以这么写:

import { Post } from './Post';

describe('Post Domain Object', () => {
  // 测试空字符串场景
  test('throws error when title is an empty string', () => {
    // 用类型断言绕过TS编译检查,模拟运行时传入空值
    expect(() => new Post('' as string)).toThrow('Post title should not be empty');
  });

  // 测试null场景
  test('throws error when title is null', () => {
    // 用@ts-expect-error告诉TS:我们预期这里会有编译错误,不用提示
    // @ts-expect-error
    expect(() => new Post(null)).toThrow('Post title should not be empty');
  });

  // 测试undefined场景
  test('throws error when title is undefined', () => {
    // @ts-expect-error
    expect(() => new Post(undefined)).toThrow('Post title should not be empty');
  });

  // 正向测试:合法标题能创建Post
  test('creates Post successfully with valid title', () => {
    const post = new Post('My First DDD Post');
    expect(post).toBeInstanceOf(Post);
    // 如果给title加了getter方法,还可以验证值:expect(post.title).toBe('My First DDD Post');
  });
});

这么做的原因是:虽然编译时TS会拦截非法输入,但运行时依然有可能出现这种情况(比如前端传过来的JSON缺少title字段被解析成undefined,或者有人用纯JS调用这个类),测试运行时的校验逻辑才能确保业务规则真正生效。

二、能不能忽略这个测试?绝对不行!

现在TS严格模式帮我们挡了一部分问题,但未来可能出现这些场景:

  1. 有人修改tsconfig.json把strict改成false,编译时的空检查就失效了;
  2. 有人重构Post类,不小心把构造函数参数改成可选(比如constructor(title?: string));
  3. 有人调整校验逻辑,比如把!this.title改成this.title === null,漏掉了空字符串的情况。

单元测试是业务规则的可执行文档,它能在代码被修改后立刻提醒开发者:你破坏了“Post标题必填”的核心规则,避免无意识的回归错误。

三、如果未来有人把title设为可选怎么办?

这正是单元测试的价值所在——当有人修改代码违反业务规则时,对应的测试用例会直接失败,让开发者立刻意识到问题。比如如果有人把构造函数改成constructor(title?: string),测试new Post(undefined)的用例就会抛出错误,提醒开发者:这个改动不符合需求,必须回滚或者重新评估。

额外优化:类型系统+运行时校验双重保障

如果想让类型规则和业务规则绑定得更紧密,可以自定义一个非空字符串类型,从类型层面强制约束输入:

// 定义一个标记类型,确保只有经过校验的字符串才能传入
type NonEmptyString = string & { __brand: 'NonEmptyString' };

// 辅助函数:验证字符串非空,返回标记类型
function ensureNonEmptyString(value: string): NonEmptyString {
  if (!value.trim()) {
    throw new Error('String should not be empty or whitespace');
  }
  return value as NonEmptyString;
}

export class Post {
  private title: NonEmptyString;
  constructor(title: NonEmptyString) {
    this.title = title;
  }
}

// 使用时必须先通过辅助函数校验
const validPost = new Post(ensureNonEmptyString('Valid Title'));
// new Post('') 会直接编译报错,因为''不是NonEmptyString类型

但即便做了这样的类型优化,单元测试依然不能少——辅助函数的校验逻辑、Post类的构造函数逻辑,都需要测试来确保正确性。

内容的提问来源于stack exchange,提问作者Mohamed Ramrami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:13:08