您现在的位置是:网站首页 > 不测试边界条件(数组为空?'undefined'?不存在的)文章详情

不测试边界条件(数组为空?'undefined'?不存在的)

边界条件测试的重要性

边界条件测试是编程中经常被忽视的一个环节。许多开发者倾向于只关注"正常情况"下的代码运行,而忽略了那些可能引发问题的边缘场景。这种习惯往往导致线上环境出现难以预料的bug,特别是在处理用户输入、API响应或数据结构时。

// 一个典型的例子:处理数组
function getFirstElement(arr) {
  return arr[0];
}

这个函数看起来简单,但如果传入空数组、null或undefined会发生什么?代码会直接崩溃。前端开发中,这类问题尤为常见,因为数据来源往往不可控。

常见被忽略的边界条件

空数组处理

数组操作是最容易出问题的场景之一。许多数组方法在空数组上表现不同:

const emptyArray = [];

// 这些方法在空数组上表现如何?
emptyArray.map(x => x * 2); // 返回[]
emptyArray.reduce((acc, cur) => acc + cur); // 抛出TypeError
emptyArray.find(x => x > 0); // 返回undefined

undefined和null值

JavaScript中undefined和null的处理需要特别注意:

function getUserName(user) {
  return user.name;
}

// 以下调用都会导致错误
getUserName(null);
getUserName(undefined);

数字边界

数字计算中的边界条件也经常被忽视:

// 大数计算
const bigNum = 9999999999999999; // 16位
console.log(bigNum + 1); // 10000000000000000

// 小数精度
console.log(0.1 + 0.2); // 0.30000000000000004

前端特定场景的边界条件

DOM操作

DOM操作中未检查元素是否存在是常见错误:

document.getElementById('non-existent').addEventListener('click', handler);
// 抛出TypeError: Cannot read property 'addEventListener' of null

API响应处理

处理API响应时,假设数据结构总是完整是危险的:

fetch('/api/user')
  .then(res => res.json())
  .then(data => {
    console.log(data.user.profile.name); // 如果data.user为null怎么办?
  });

表单验证

表单验证经常忽略边界情况:

function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// 这些边界情况考虑了吗?
validateEmail(''); // 空字符串
validateEmail(' '); // 只有空格
validateEmail(undefined); // undefined

防御性编程实践

参数校验

在函数开始处进行参数校验:

function safeGetFirstElement(arr) {
  if (!Array.isArray(arr) || arr.length === 0) {
    return null;
  }
  return arr[0];
}

可选链和空值合并

现代JavaScript提供了更简洁的处理方式:

// 旧方式
const name = user && user.profile && user.profile.name;

// 新方式
const name = user?.profile?.name ?? '默认名称';

类型检查

使用TypeScript可以在编译时捕获许多边界问题:

interface User {
  id: number;
  name: string;
  profile?: {
    email: string;
  };
}

function sendEmail(user: User) {
  const email = user.profile?.email;
  if (!email) {
    throw new Error('用户没有邮箱');
  }
  // 发送邮件逻辑
}

测试边界条件的策略

单元测试覆盖

为边界条件编写专门的测试用例:

describe('getFirstElement', () => {
  it('应该处理空数组', () => {
    expect(getFirstElement([])).toBeNull();
  });
  
  it('应该处理非数组输入', () => {
    expect(getFirstElement(null)).toBeNull();
    expect(getFirstElement(undefined)).toBeNull();
    expect(getFirstElement('string')).toBeNull();
  });
});

错误边界组件

React中的错误边界可以捕获子组件树中的JavaScript错误:

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError() {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <div>出错了</div>;
    }
    return this.props.children;
  }
}

监控和日志

在生产环境中监控边界条件导致的错误:

window.addEventListener('error', (event) => {
  fetch('/api/log-error', {
    method: 'POST',
    body: JSON.stringify({
      message: event.message,
      stack: event.error.stack,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno
    })
  });
});

实际案例分析

案例1:分页组件

一个典型的分页组件可能忽略的边界条件:

function Pagination({ total, current, onChange }) {
  // 如果total是0怎么办?
  // 如果current大于total怎么办?
  // 如果onChange不是函数怎么办?
  return (
    <div>
      {Array.from({ length: total }).map((_, i) => (
        <button key={i} onClick={() => onChange(i + 1)}>
          {i + 1}
        </button>
      ))}
    </div>
  );
}

案例2:本地存储操作

localStorage操作中的边界条件:

function getFromLocalStorage(key) {
  // 如果localStorage被禁用怎么办?
  // 如果key不存在怎么办?
  // 如果存储的值不是合法JSON怎么办?
  try {
    return JSON.parse(localStorage.getItem(key));
  } catch (e) {
    return null;
  }
}

案例3:URL参数解析

URL参数解析经常出现的问题:

function getQueryParam(name) {
  const params = new URLSearchParams(window.location.search);
  // 如果参数不存在怎么办?
  // 如果参数是空字符串怎么办?
  // 如果参数是"undefined"字符串怎么办?
  return params.get(name);
}

工具和库的辅助

Lodash的安全方法

Lodash提供了许多安全处理边界条件的方法:

_.get(object, 'a.b.c', defaultValue);
_.isEmpty(value);
_.isNil(value);

TypeScript的非空断言

TypeScript的非空断言要谨慎使用:

interface Props {
  user?: {
    name: string;
  };
}

function UserName({ user }: Props) {
  // 不安全的断言
  return <div>{user!.name}</div>;
  
  // 更安全的做法
  if (!user) return null;
  return <div>{user.name}</div>;
}

ESLint规则

配置ESLint规则来强制检查边界条件:

{
  "rules": {
    "no-unsafe-optional-chaining": "error",
    "no-unused-expressions": "error",
    "no-implicit-coercion": "error"
  }
}

性能与安全考量

过度防御的代价

虽然防御性编程很重要,但也要避免过度:

// 过度防御的例子
function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('参数必须是数字');
  }
  if (!Number.isFinite(a) || !Number.isFinite(b)) {
    throw new RangeError('参数必须是有限数字');
  }
  return a + b;
}

安全与用户体验的平衡

处理边界条件时要考虑用户体验:

function loadUserData(userId) {
  try {
    const data = fetchUser(userId);
    if (!data) {
      // 显示友好的空状态UI
      return showEmptyState();
    }
    renderUserData(data);
  } catch (error) {
    // 显示错误提示而非白屏
    showErrorToast(error.message);
  }
}

团队协作中的边界条件

代码审查重点

在代码审查中特别关注边界条件:

// 审查时应提问:
// - 如果API返回的数据结构不符合预期怎么办?
// - 如果用户快速连续点击按钮怎么办?
// - 如果组件卸载时异步操作还未完成怎么办?

文档记录

在文档中明确记录边界条件处理:

## 组件Props

- `data`: 必须是非空数组,如果为空数组将显示空状态提示
- `onSelect`: 可选的回调函数,当用户选择项时触发
- `defaultValue`: 如果没有匹配项将使用此值

共享工具函数

创建团队共享的边界条件处理工具:

// utils/safety.js
export function safeAccess(obj, path, defaultValue) {
  return path.split('.').reduce((acc, key) => {
    try {
      return acc != null ? acc[key] : defaultValue;
    } catch {
      return defaultValue;
    }
  }, obj);
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步