Elixir测试中如何让结构体断言在缺失键时触发失败?
问题背景
- 现有可正常通过的Elixir测试断言代码如下:
assert Contract.fetch(contract: valid_address) == %Contract{ contract: "0x3D29Aa78fB558F84112bbC48a84F371147A920C9", name: "bla", price: nil, price_bnb: nil, call_id: 7, symbol: nil, transactions: [] }
- 现存的问题:
- 断言中书写的
%Contract{}结构体字面量,没有列出Contract结构体定义的全部键 - 受Elixir结构体语法特性影响,未显式声明的字段会自动填充结构体定义的默认值,只要实际返回值的对应字段和默认值匹配,哪怕漏写字段,测试也能通过
- 实际需求:只要断言没有覆盖Contract结构体的全部键,测试就主动触发失败,避免漏校验字段的问题
- 断言中书写的
调整实现方案
核心校验逻辑
要实现目标,需要在原有等值比对之前,增加一层「字段覆盖完整性校验」,确保测试中手写的期望值结构体,没有靠默认值凑数、遗漏任何结构体定义的字段。
具体逻辑拆分为三步:
- 获取Contract结构体定义的全量键列表
- 通过
Contract.__struct__()可以拿到结构体定义的默认全量字段集合,排除内置的:__struct__元字段,就是需要全部覆盖的目标键集合
- 通过
- 校验手写期望值的键集合和全量键集合完全一致
- 如果存在差值(即部分键没有被显式写在期望值结构体里),直接触发断言失败,提示具体遗漏的字段
- 完成覆盖校验后,再执行原有的结构体等值比对
可直接落地的测试代码示例
# 1. 取Contract结构体定义的所有业务字段(排除内置元字段) all_contract_fields = Contract.__struct__() |> Map.keys() |> List.delete(:__struct__) # 2. 编写测试用的期望值(即原断言中的结构体内容) expected = %Contract{ contract: "0x3D29Aa78fB558F84112bbC48a84F371147A920C9", name: "bla", price: nil, price_bnb: nil, call_id: 7, symbol: nil, transactions: [] } # 3. 字段覆盖校验:检查是否漏写字段 written_fields = expected |> Map.keys() |> List.delete(:__struct__) assert written_fields == all_contract_fields, "断言未覆盖Contract的全部字段,缺失字段:#{inspect(all_contract_fields -- written_fields)}" # 4. 执行原有的等值比对 assert Contract.fetch(contract: valid_address) == expected
复用性优化
如果多个测试用例都需要做全结构体覆盖校验,可以把上述逻辑抽成通用的测试助手函数,放到测试辅助模块里复用:
defmodule ContractCase do use ExUnit.CaseTemplate def assert_full_struct_equal(actual, %struct_module{} = expected) do all_fields = struct_module.__struct__() |> Map.keys() |> List.delete(:__struct__) written_fields = expected |> Map.keys() |> List.delete(:__struct__) missing = all_fields -- written_fields if missing != [] do ExUnit.Assertions.flunk("断言未覆盖#{struct_module}的全部字段,缺失字段:#{inspect(missing)}") end ExUnit.Assertions.assert actual == expected end end
测试用例里可以直接调用这个助手函数,不需要重复写校验逻辑:
expected = %Contract{ contract: "0x3D29Aa78fB558F84112bbC48a84F371147A920C9", name: "bla", price: nil, price_bnb: nil, call_id: 7, symbol: nil, transactions: [] } assert_full_struct_equal(Contract.fetch(contract: valid_address), expected)
实现效果说明:这种校验逻辑会强制要求在断言里显式写出所有结构体字段,哪怕某字段的实际值和结构体默认值完全一致,只要没在断言中声明对应字段,就会触发测试失败。
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

