Elixir 从零 · 第 24 课(展开目录)
01 · 先让代码说话02 · 先认六种值03 · 把值装起来04 · 从旧 list 生成新 list05 · 让形状对上06 · 用 case 做选择07 · 把文字安全变成整数08 · 拆开 &1 和管道09 · 把代码放进项目10 · 真假不只靠 true11 · 走进嵌套数据12 · 一字不一定一字节13 · 让函数按形状接活14 · 把一列数收成一个值15 · 给不同问题选不同路16 · 给 map 一张名片17 · 让项目自己验答案18 · 让成功和失败长得一样19 · 读写文件先管好路20 · 只算眼前需要的21 · 同一句话,不同做法22 · 先约好模块会做什么23 · 把数据形状写在门口24 · 测试不只看一条好路25 · 把项目磨成一个命令26 · 定下小项目边界27 · 把真实文字洗干净28 · 只开三扇门29 · 把约定钉在代码旁30 · 打包,也留下脚印31 · 照任务书交卷
ELIXIR · 进阶 · LESSON 2440 分钟

测试不只看一条好路

用 `describe`、`setup` 和边界用例组织可读的 ExUnit 测试。

01 · 先看完整例子

让边界写进测试名

例子假设项目已有 Discount.price/2;重点看测试边界怎样表达。

Elixir
defmodule DiscountTest do
  use ExUnit.Case, async: true

  describe "price/2" do
    test "满 10 件打九折" do
      assert Discount.price(100, 10) == 90.0
    end

    # 9 件仍按原价
    test "未满 10 件不打折" do
      assert Discount.price(100, 9) == 100
    end
  end
end
先对照结果
  • 实现符合规则时显示 2 tests, 0 failures。
02 · 回头拆代码

从第一行往下读

  1. 两个测试只差边界两侧的数量,容易读出业务规则。
  2. 测试名说“什么情况下,结果怎样”,不说函数内部怎样写。
  3. 失败后先读期望值和实际值,再定位实现。
03 · 认清新符号

符号不是暗号

describe

把同一功能的测试放在一组。

setup

每个测试前准备共享数据。

assert_raise

要求一段代码抛出指定异常。

04 · 代码里的概念

把名字和意思对上

01

边界用例

规则刚好发生变化的位置,如满 10 件、恰好 0 元。

02

测试上下文

setup 交给每个测试的一份准备数据。

05 · 再说清楚

这些写法为什么有用

好测试像路标:名字说明行为,输入够小,失败时能看出哪里偏了。

先测正常例子,再补边界和错误输入。describe 按功能分组,setup 只准备真正共用的数据。

测试不要复制实现。若正文和测试用同一套算法,两个地方可能一起错。

06 · 自己改一次

合上答案,动手

增加恰好 0 件的测试,期待原价。

练习起点
test "零件商品不打折" do
  assert Discount.price(80, ____) == ____
end

目标结果: 测试期待 Discount.price(80, 0) == 80。

卡住了,再看提示

数量为 0,价格仍写整数 80。

运行过以后,再看一种答案
一种答案
test "零件商品不打折" do
  # 零也是需要明确保存的边界
  assert Discount.price(80, 0) == 80
end
想一想:每个测试都需要 `setup` 吗?

不需要。只有多条测试确实共享准备步骤时才用;简单输入直接写在测试里更清楚。

带走

记住三句

  1. 1

    测试名应说明行为。

  2. 2

    正常、边界、失败三类输入都要考虑。

  3. 3

    共享准备要少而明确。

本课结束代码也跑过一次,就把这一课收好。