Elixir WithClauseError问题:单元测试匹配{:ok, value}失败
问题分析与解决方案
你遇到的核心问题是**with语句的模式匹配错误**,同时函数的错误返回逻辑也不符合你的预期。让我一步步拆解并解决:
1. 根本错误:with子句的模式不匹配
你的验证函数(比如validate_times)返回的是原子:ok,但你在with里用了元组{:ok}去匹配——这两个是完全不同的类型!
:ok是一个单独的原子{:ok}是包含一个原子的元组
当validate_times返回:ok时,with的第一个匹配子句直接失败,进入else块。但你的else只处理{:error, error}的情况,无法匹配传入的:ok,所以抛出WithClauseError。
2. 错误返回逻辑不符合需求
原来的else块用IO.puts error,这个函数的返回值是:ok,导致参数错误时函数返回:ok而非你想要的{:error, reason}。
修正后的完整代码
主函数修正
def calculate_price(start_time, end_time, zone, payment_type) do with( :ok <- validate_times(start_time, end_time), :ok <- validate_zone(zone), :ok <- validate_payment_type(payment_type) ) do elapsed_minutes = div(Time.diff(end_time, start_time), 60) cond do zone == "A" && elapsed_minutes <= 15 -> {:ok, 0} zone == "B" && elapsed_minutes <= 90 -> {:ok, 0} zone == "A" && elapsed_minutes > 15 && payment_type == "hourly" -> {:ok, calc(elapsed_minutes - 15, 2, 60)} zone == "B" && elapsed_minutes > 90 && payment_type == "hourly" -> {:ok, calc(elapsed_minutes - 90, 1, 60)} zone == "A" && elapsed_minutes > 15 && payment_type == "real" -> {:ok, calc(elapsed_minutes - 15, 0.16, 5)} zone == "B" && elapsed_minutes > 90 && payment_type == "real" -> {:ok, calc(elapsed_minutes - 90, 0.08, 5)} end else {:error, error} -> {:error, error} # 兜底处理意外情况,避免再次抛出WithClauseError unexpected -> {:error, "Unexpected error: #{inspect(unexpected)}"} end end # 以下验证函数保持不变,它们的返回值是正确的 defp validate_times(start_time, end_time) when end_time > start_time, do: :ok defp validate_times(_start_time, _end_time), do: {:error, "The start/end time is wrong"} defp validate_zone(zone) when zone == "A" or zone == "B", do: :ok defp validate_zone(_zone), do: {:error, "The zone is wrong"} defp validate_payment_type(payment_type) when payment_type == "hourly" or payment_type == "real", do: :ok defp validate_payment_type(_payment_type), do: {:error, "The payment type is wrong"} defp calc(minutes_to_pay, price_per_minutes, minutes_per_price_increment) do cond do rem(minutes_to_pay, minutes_per_price_increment) > 0 -> (div(minutes_to_pay, minutes_per_price_increment) + 1) * price_per_minutes true -> div(minutes_to_pay, minutes_per_price_increment) * price_per_minutes end end
测试用例验证
你的原有测试用例现在可以正常通过:
test "calculate price; zone: B, paymentType: real" do # 4小时30分钟的时间差 startTime = ~T[12:00:00.000] endTime = ~T[16:30:00.000] zone = "B" paymentType = "real" assert {:ok, 2.88} == calculate_price(startTime, endTime, zone, paymentType) end
你还可以新增错误场景的测试,比如:
test "calculate price with invalid zone" do start_time = ~T[12:00:00.000] end_time = ~T[13:00:00.000] assert {:error, "The zone is wrong"} == calculate_price(start_time, end_time, "C", "real") end
关键修正点总结
- 将
with中的{:ok} <-改为:ok <-,匹配验证函数返回的原子类型 - 修改
else块,直接返回{:error, error}元组,符合你的需求 - 添加兜底的
else子句,避免因意外值再次抛出WithClauseError
内容的提问来源于stack exchange,提问作者bautrey
相关产品推荐
相关产品推荐

