如何为返回非确定性结果的Rails控制器编写测试?
Ruby on Rails 非确定性控制器的测试方案设计与实现
问题背景
- 数据库包含
Article表,字段为weight(整数)和name(字符串) - 库中存在10条
weight和name均不重复的Article数据 - 控制器处理带
name参数的GET请求时,先筛选同名文章,再通过加权随机算法返回一条结果(权重越高被选中概率越大,例如权重1、3、5、7时,随机数范围为0-15,对应区间匹配对应文章)
现有测试思路
- 无
name参数的GET请求返回400状态码及"Bad parameters"信息 - 带
name参数的GET请求返回200状态码 - 带不存在的
name参数的GET请求返回200状态码及null - 验证同名文章筛选逻辑的正确性
- 验证加权随机选择逻辑的概率符合预期(通过大样本运行统计结果,如权重1、2、3、4时,各文章选中次数需落在对应比例区间内)
核心测试点实现方案
拆分出的getSelectedArticles和randomWeight是核心业务逻辑,必须单独测试——单独测试能精准定位问题,避免集成测试因随机性导致的不稳定,也能覆盖概率逻辑的正确性。
1. 测试getSelectedArticles(筛选逻辑)
这是确定性逻辑,用单元测试验证筛选结果的准确性:
test "getSelectedArticles returns correct articles for given name" do # 准备测试数据 alice_article1 = Article.create!(name: "Alice", weight: 2) alice_article2 = Article.create!(name: "Alice", weight: 5) bob_article = Article.create!(name: "Bob", weight: 3) # 实例化控制器并调用方法 controller = ArticleController.new selected = controller.getSelectedArticles(name: "Alice") # 断言筛选结果符合预期 assert_equal 2, selected.count assert_includes selected, alice_article1 assert_includes selected, alice_article2 assert_not_includes selected, bob_article end
如果后续将该方法改为私有,可通过controller.send(:getSelectedArticles, name: "Alice")调用。
2. 测试randomWeight(加权随机逻辑)
这是非确定性逻辑,通过大样本统计+误差容忍的方式验证概率是否符合预期:
test "randomWeight selects articles with probability matching their weights" do # 模拟文章集合:权重分别为1、2、3、4,总权重10 articles = [ OpenStruct.new(weight: 1), OpenStruct.new(weight: 2), OpenStruct.new(weight: 3), OpenStruct.new(weight: 4) ] total_runs = 10000 counts = Array.new(articles.size, 0) controller = ArticleController.new total_runs.times do index = controller.randomWeight(articles: articles) counts[index] += 1 end # 预期概率:10%、20%、30%、40%,允许±5%的误差 expected_ratios = [0.1, 0.2, 0.3, 0.4] tolerance = 0.05 counts.each_with_index do |count, i| actual_ratio = count.to_f / total_runs expected_ratio = expected_ratios[i] assert_in_delta expected_ratio, actual_ratio, tolerance, "Index #{i} had ratio #{actual_ratio.round(4)}, expected #{expected_ratio} ± #{tolerance}" end end
3. 补充集成测试(整体流程验证)
确保控制器整体流程正常,返回结果在合理范围内:
test "returns one of the matching articles for existing name" do alice_article1 = Article.create!(name: "Alice", weight: 2) alice_article2 = Article.create!(name: "Alice", weight: 5) get "/article?name=Alice" assert_response :ok response_json = JSON.parse(@response.body) # 断言返回结果是目标name下的某一篇文章 assert_includes [alice_article1.id, alice_article2.id], response_json["id"] end
内容的提问来源于stack exchange,提问作者itsmarziparzi
相关产品推荐
相关产品推荐

