让我把我的问题放到一个我能想到的最简单的例子中,这个例子有点类似。
1.我有一个数据库表“Article”,它有一个名为“weight”的整数属性和一个名为“name”的字符串属性
1.我在数据库中有10篇不同权重和名称的文章
1.我收到一个包含“name”作为参数的GET请求
1.在这10篇文章中,我找到了同名的文章。我找到了4篇文章。
1.在这4篇文章中,我选择一个随机选择的,但是随机性是加权的,权重越大,被选中的机会越大。因此,如果四个权重是1、3、5、7,我生成一个0和权重之和之间的随机数(即1+3+5+7=16),然后用这个数字来决定选择哪篇文章,所以如果我得到的随机数是0,那么我选择第一篇文章,如果我得到的随机数是1~3,我选择第二条,如果我得到的随机数是4~8,我选择第三条,如果我得到的随机数是9~15,我选择第四条。
1.我发送这篇文章作为GET请求的响应。
正如你所看到的,对于一个GET请求的响应,如果有一个特定的“name”作为参数,那么响应并不总是相同的。然而,我找到的所有关于测试Controller的例子都依赖于请求的响应是确定的。
所以我想得到一些帮助,关于我将如何去设计我的测试代码。
我目前对测试的想法如下:
1.就GET请求的响应而言,如果GET查询不包括name参数,那么我预期HTTP状态代码为400,并显示消息“Bad parameters”
1.如果GET包含name参数,那么我期望HTTP Status代码为200(但是不要对内容做任何额外的检查)
1.如果GET包含一个与任何Articles都不匹配的名称,那么我期望HTTP Status代码为200,响应消息为“null”。
1.我用一个名字做了一个GET请求,在我做随机选择之前,我得到了与“名字”匹配的文章的正确列表。
1.我创建了4个具有相同“名称”的Article对象,每个对象的权重为1、2、3、4我在进行加权随机选择的逻辑上运行10,000次。我检查我获得第一篇文章的次数是否在700到1300之间(约1/10,000),如果我得到第二篇文章的次数在1700到2300之间(约万分之二),如果我得到第三篇文章的次数在2700到3300之间(约万分之三),如果我拿到第四篇文章的次数在3700到4300之间(约万分之四)。
一个问题是,尽管试图阅读如何在RubyonRails上编写测试,我仍然不确定如何做4和5。4和5要求我在逻辑“内部”测试事物(而不是使用执行整个逻辑的结果)。看起来我需要把逻辑分解成更小的函数,可以调用来测试4和5。对吗?我还应该测试哪些东西?
下面是我创建的一个简单代码,以便我们可以按照这个示例进行沿着。完整代码如下:https://github.com/maruhanpark/example
你可以用docker compose运行它。
我上面解释的测试代码如下所示:
require 'test_helper'
class ArticleControllerTest < ActionDispatch::IntegrationTest
test "having no name parameter returns Bad Parameters" do
get "/article"
assert_response(400)
assert_equal "{\"message\":\"Bad parameters\"}", @response.body
end
test "having name parameter returns 200" do
get "/article?name=Bob"
assert_response(200)
end
test "having empty name parameter returns 200 and null" do
get "/article?name="
assert_response(200)
assert_equal "null", @response.body
end
test "getSelectedArticles returns the correct list of articles" do
end
test "randomWeight selects properly based on weight" do
end
end
正如你所看到的,我写了我解释的前三个案例,但是遗漏了案例4和5。
对于控制器,我把我想测试的逻辑分解为单独的方法,我创建的两个方法分别用于测试用例4和5,我不确定是否有必要创建单独的方法来测试那个逻辑。
class ArticleController < ApplicationController
def getSelectedArticles(name:)
#logger.debug "===== #{Article.where(name: name)}"
return Article.where(name: name)
end
#Returns the index of the finally selected article
def randomWeight(articles:)
sum = 0
articles.each { |a| sum += a.weight }
randNum = rand(sum)
returnIndex = 0
for i in 0 ... articles.size
randNum -= articles[i].weight
if (randNum < 0)
returnIndex = i
break
end
end
return returnIndex
end
def article
if params[:name]
selectedArticles = getSelectedArticles(name: params[:name])
finalArticle = selectedArticles[randomWeight(articles: selectedArticles)]
render json: finalArticle, status: :ok
else
render json: {message: "Bad parameters"}, status: :bad_request
end
end
end
我在尝试实现测试4时遇到的一个问题是,我需要在调用GET之后,以某种方式让代码在getSelectedArticles方法上有一个断点。我不确定这是否可能,或者我是否必须只作为一种单元测试来测试它,在这种测试中,我专门调用该方法,并看到该方法返回预期的东西。
1条答案
按热度按时间zpgglvta1#
我阅读了您的请求,并注意到我们可以对您的代码进行以下改进:
1.请使用REST路由,即在
routes.rb
中请写入resources :articles, only: [:show]
,将控制器命名为ArticlesController
(注意复数),您的路由将为/articles/:id
1.方法和变量名请使用两个空格缩进和snake_case
1.鼓励使用
private
来分隔方法1.在方法的最后语句中不需要“return
1.在ruby中选择随机的文章可能不是很好,如果你有很多文章,我建议在数据库上这样做:SQL技能方面的工作量会稍大一些,但速度会更快,扩展性也会更好
1.我建议您将控制器的内容与服务分离,这样更易于测试和开发
无论如何,这里是我的第一次通过的改进:
测试
由于我对minitest了解不多,我将为RSpec写一个答案,希望您能够自己转换它(警告,未经测试!):
请注意,以上都不是很好的状态,它应该被视为一个很好的起点。
祝你好运伙计!