你正在 萌芽版 · ⚡ 代码俱乐部 · ← 回到学院 · 萌芽版主页 · 总入口

← 代码俱乐部 · 创作者层 03 / 8

第 07 节 · 给自己的代码写测试

测试不是"额外功课",是让你睡得着觉的护栏。

核心想法

你写一个 add(a, b) 函数。手测一次没问题。可是改了之后呢?三个月后再看呢?测试就是把"我刚才确认过"这件事记下来

最简单的测试

function add(a, b) { return a + b; }

// 测试
function test(name, fn) {
  try { fn(); console.log("✓ " + name); }
  catch (e) { console.log("✗ " + name + ": " + e.message); }
}

第 1 块 · 定义被测函数和测试框架

add(a, b) 就是一个简单的加法函数。function test(name, fn) = 定义一个"测试函数",它接收两个参数:测试的名字,和一个"测试逻辑"(fn)。try { ... } catch (e) { ... } = "尝试运行测试,如果出错就捕获错误信息并打印"。

💡 测试的核心思想:定义什么时候"过",什么时候"失败"。如果过了,打 ✓;失败了,打 ✗。

test("1+1 应该等于 2", () => {
  if (add(1, 1) !== 2) throw new Error("结果错了");
});
test("0+0 应该等于 0", () => {
  if (add(0, 0) !== 0) throw new Error("0+0 不是 0");
});
test("负数也行", () => {
  if (add(-1, -1) !== -2) throw new Error("负数错了");
});

第 2 块 · 写三个具体的测试用例

每个 test(...) 都检查一个条件。if (add(1, 1) !== 2) = "如果 1+1 不等于 2",那就 throw new Error("...") = 抛出一个错误。throw 会让 catch 块抓住,从而打印失败信息。如果条件通过(没有 throw),就会自动打 ✓。

💡 为什么要写多个用例:一个函数可能在某些情况下对,在其他情况下错。测试要覆盖边界情况:正数、0、负数。

👉 试改:改第一个 add(1,1) 成 add(2,3),改判断条件试试。

📋 看 / 复制完整代码
function add(a, b) { return a + b; }

// 测试
function test(name, fn) {
  try { fn(); console.log("✓ " + name); }
  catch (e) { console.log("✗ " + name + ": " + e.message); }
}

test("1+1 应该等于 2", () => {
  if (add(1, 1) !== 2) throw new Error("结果错了");
});
test("0+0 应该等于 0", () => {
  if (add(0, 0) !== 0) throw new Error("0+0 不是 0");
});
test("负数也行", () => {
  if (add(-1, -1) !== -2) throw new Error("负数错了");
});

把这段保存为 test.js,在 VS Code 终端跑 node test.js。三个 ✓ 表示对。

动手练习 1:写一个简单的测试

动手 单元测试的原理
任务:下面是一个简单的函数和它的测试。测试检查函数对不对。试试改改函数,看看测试会不会失败。然后修复函数,让测试通过。
参考答案

一个更完整的测试框架,包括多个函数和更多测试用例:

<!DOCTYPE html>
<html>
<head><style>
  body { font-family: sans-serif; padding: 20px; background: #f5f5f5; }
  .container { max-width: 600px; margin: 0 auto; }
  .test { margin: 8px 0; padding: 12px; border-radius: 5px; border-left: 4px solid; }
  .pass { background: #c8e6c9; color: #2e7d32; border-left-color: #4caf50; }
  .fail { background: #ffcdd2; color: #c62828; border-left-color: #f44336; }
  .summary { margin: 20px 0; padding: 15px; background: white; border-radius: 5px; }
</style></head>
<body>
  <div class="container">
    <h1>我的函数测试报告</h1>
    <div class="summary">
      <p><strong>总测试:</strong> <span id="total">0</span></p>
      <p><strong>通过:</strong> <span id="passed" style="color: #4caf50;">0</span></p>
      <p><strong>失败:</strong> <span id="failed" style="color: #f44336;">0</span></p>
    </div>
    <div id="results"></div>
  </div>

  <script>
    // 定义函数
    function add(a, b) {
      return a + b;
    }
    
    function multiply(a, b) {
      return a * b;
    }
    
    function isEven(n) {
      return n % 2 === 0;
    }
    
    // 运行测试
    function runTests() {
      let tests = [
        { name: 'add(2, 3) === 5', run: () => add(2, 3) === 5 },
        { name: 'add(-1, 1) === 0', run: () => add(-1, 1) === 0 },
        { name: 'multiply(3, 4) === 12', run: () => multiply(3, 4) === 12 },
        { name: 'multiply(0, 100) === 0', run: () => multiply(0, 100) === 0 },
        { name: 'isEven(4) === true', run: () => isEven(4) === true },
        { name: 'isEven(3) === false', run: () => isEven(3) === false }
      ];
      
      let passed = 0, failed = 0;
      let html = '';
      
      for (let t of tests) {
        let pass = t.run();
        if (pass) passed++; else failed++;
        
        let cls = pass ? 'pass' : 'fail';
        let icon = pass ? '✓ 通过' : '✗ 失败';
        html += '<div class="test ' + cls + '">' + icon + ': ' + t.name + '</div>';
      }
      
      document.getElementById('total').innerText = tests.length;
      document.getElementById('passed').innerText = passed;
      document.getElementById('failed').innerText = failed;
      document.getElementById('results').innerHTML = html;
    }
    
    runTests();
  </script>
</body>
</html>

进阶:这就是"单元测试"的真实流程 —— 写一个函数,给它各种输入,检查输出对不对。如果有一个测试失败了,你就知道函数有 bug。

动手练习 2:测试驱动开发(TDD)

动手 先写测试,再写函数
任务:下面给出了几个测试,但函数还没写。你的任务是:根据这些测试,把函数写出来。比如,测试说 reverse('hello') 应该等于 'olleh',那么你就要写一个能做这个的函数。
参考答案

用 JavaScript 实现字符串反转的几种方法:

<!DOCTYPE html>
<html>
<head><style>
  body { font-family: sans-serif; padding: 20px; background: #f5f5f5; }
  .test { margin: 8px 0; padding: 12px; border-radius: 5px; border-left: 4px solid; }
  .pass { background: #c8e6c9; color: #2e7d32; border-left-color: #4caf50; }
  .fail { background: #ffcdd2; color: #c62828; border-left-color: #f44336; }
</style></head>
<body>
  <h1>字符串反转 —— 多种实现方法</h1>
  <div id="results"></div>

  <script>
    // 方法 1: 用 split + reverse + join
    function reverse_v1(str) {
      return str.split('').reverse().join('');
    }
    
    // 方法 2: 用 for 循环
    function reverse_v2(str) {
      let result = '';
      for (let i = str.length - 1; i >= 0; i--) {
        result += str[i];
      }
      return result;
    }
    
    // 定义测试
    let tests = [
      { name: "reverse('hello') === 'olleh'", run: () => reverse_v1('hello') === 'olleh' },
      { name: "reverse('abc') === 'cba'", run: () => reverse_v1('abc') === 'cba' },
      { name: "reverse('') === ''", run: () => reverse_v1('') === '' },
      { name: "reverse('a') === 'a'", run: () => reverse_v1('a') === 'a' }
    ];
    
    let html = '<p><strong>方法 1(split + reverse + join):</strong></p>';
    for (let t of tests) {
      let pass = t.run();
      let cls = pass ? 'pass' : 'fail';
      let icon = pass ? '✓' : '✗';
      html += '<div class="test ' + cls + '">' + icon + ' ' + t.name + '</div>';
    }
    
    document.getElementById('results').innerHTML = html;
  </script>
</body>
</html>

进阶:TDD(测试驱动开发)是这样工作的:先写测试(定义你想要什么),然后写函数(实现你想要的)。这样你永远知道自己的代码对不对。

小测验:为什么要写测试

小测 你理解测试的价值了吗?
下面哪个最能说明为什么要写测试?
  • 因为老师要求。
  • 测试让你知道你的代码对不对。如果改了代码,只要测试都通过,你就知道没有破坏别的东西。
  • 只有大公司的代码才需要测试。
解释:代码会随着时间改变。新加功能、修复 bug、优化速度 —— 每次改动都有可能破坏旧的功能。测试就像"护栏" —— 它检查"我改的东西对不对",也检查"我有没有不小心搞坏别的东西"。

这一节学到什么

测试看起来麻烦。但有了测试,你才敢改代码。怕改坏,是不会写好东西的。

← 上一节下一节 →