文章目录
unittest 是web界面的单元测试框架。而Junit是白盒测试的单元测试框架。
unittest 单元测试提供了创建测试用例,测试套件以及批量执行的方案, unittest 在安装pyhton 以后就直接自带了,直接import unittest 就可以使用。
作为单元测试的框架, unittest 也是可以对程序最小模块的一种敏捷化的测试。在自动化测试中,我们虽然不需要做白盒测试,但是必须需要知道所使用语言的单元测试框架。利用单元测试框架,创建一个类,该类继承unittest的TestCase,这样可以把每个case看成是一个最小的单元, 由测试容器组织起来,到时候直接执行,同时引入测试报告。
unittest各组件的关系:

test fixture:初始化和清理测试环境,比如创建临时的数据库,文件和目录等,其中 setUp() 和 tearDown()是最常用的方法
test case:单元测试用例,TestCase 是编写单元测试用例最常用的类
test suite:单元测试用例的集合,TestSuite 是最常用的类
test runner:执行单元测试
test report:生成测试报告
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
#继承
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
def test_baidu1(self):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys("孔刘")
driver.find_element(By.ID, "su").click()
time.sleep(3)
def test_baidu2(self):
driver = self.driver
driver.find_element(By.LINK_TEXT, "新闻").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
关注的点:
1.创建一个类,要它继承于unittest中的TestCase类,才能完成下面的步骤。
2.setUp方法,在执行每一个以test_开头的方法前都会进行;而tearDown方法在执行每一个以test_开头的方法后都会进行。setUp方法是进行初始化工作,tearDown是进行清理工作
3.需要测试的流程关键部分放到以test_开头的方法中,那么编译器就会自动去执行。
4.不是以test_开头的方法,是需要调用才能够执行,否则不执行。
5.对于测试流程中有alert、prompt等出现提示框的,要捕捉异常。
6.写出main方法,给程序一个执行的入口,verbosity关系到打印的结果详细程度。
7.如果在不同的TestCase下右击,即是选择执行的是哪个TestCase,同样地,可以在下图的地方选择执行的代码。

完整的单元测试很少只执行一个测试用例,开发人员通常都需要编写多个测试用例才能对某一软件功能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在unittest中是用TestSuite 类来表示的。
test001的Python文件:
test002的Python跟下面的相同,只不过将类名改为了testCase2.
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
#继承
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
def test_baidu1(self):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys("孔刘")
driver.find_element(By.ID, "su").click()
time.sleep(3)
def test_baidu2(self):
driver = self.driver
driver.find_element(By.LINK_TEXT, "新闻").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
createSuite的Python文件:
import unittest
from src202205 import test001
from src202205 import test002
def createSuite():
suite = unittest.TestSuite()
suite.addTest(test001.testCase1("test_baidu1"))
suite.addTest(test001.testCase1("test_baidu2"))
suite.addTest(test002.testCase2("test_baidu1"))
suite.addTest(test002.testCase2("test_baidu2"))
return suite
if __name__ == "__main__":
suite = createSuite()
runner = unittest.TextTestRunner(verbosity=2) # 打印的很详细
runner.run(suite)
执行顺序下面会讲到。
这个方法是将一个类中的所有以test_开头的方法全部放入到suite套件中,能够解决将类中如果有很多的以test_开头的方法不用一一添加到套件的问题。注意makeSuite方法的使用。
import unittest
from src202205 import test001
from src202205 import test002
def createSuite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(test001.testCase1))
suite.addTest(unittest.makeSuite(test002.testCase2))
return suite
if __name__ == "__main__":
suite = createSuite()
runner = unittest.TextTestRunner(verbosity=2) # 打印的很详细
runner.run(suite)
对于TestLoader方法来说,是先将类装入小的测试套件中,再将全部小的测试套件装入最终的一个最大的测试套件中,在将小的测试套件装入大的测试套件中,可以采用列表的形式装入。
import unittest
from src202205 import test001
from src202205 import test002
def createSuite():
suite = unittest.TestSuite()
suite1 = unittest.TestLoader().loadTestsFromTestCase(test001.testCase1)
suite2 = unittest.TestLoader().loadTestsFromTestCase(test002.testCase2)
suite = unittest.TestSuite([suite1, suite2])
return suite
if __name__ == "__main__":
suite = createSuite()
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
discover方法可以在指定文件下,所有特定格式命名的测试脚本中类的所有以test_开头的方法存放起来。
下面的代码中,test00开头的文件都是放在src202205的文件夹中。
import unittest
def createSuite():
discover = unittest.defaultTestLoader.discover('../src202205', pattern='test00*.py', top_level_dir=None)
#要先返回上一级,然后找到以test00开头,.py结尾的文件
return discover
if __name__ == "__main__":
suite = createSuite()
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
unittest 框架默认加载测试用例的顺序是根据ASCII 码的顺序,数字与字母的顺序为: 0-9,A-Z,a-z。越小的先运行。并且如果有baidu和baidU,则baidU的会先运行。
所以, TestAdd 类会优先于TestBdd 类被发现, test_aaa() 方法会优先于test_ccc() 被执行。对于测试目录与测试文件来说, unittest 框架同样是按照这个规则来加载测试用例。
只需要在不想执行的用例前加上@unittest.skip(内容)即可。写入的内容中,在测试完其它用例的时候会打印出来。
自动化的测试中, 对于每个单独的case来说,一个case的执行结果中, 必然会有期望结果与实际结果, 来判断该case是通过还是失败, 在unittest 的库中提供了大量的实用方法来检查预期值与实际值, 来验证case的结果, 一般来说, 检查条件大体分为等价性, 逻辑比较以及其他, 如果给定的断言通过, 测试会继续执行到下一行的代码, 如果断言失败, 对应的case测试会立即停止或者生成错误信息( 一般打印错误信息即可) ,但是不要影响其他的case执行。
| 断言方法 | 断言描述 |
|---|---|
| assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail |
| assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail |
| assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail |
| assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail |
| assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
| assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
| assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
| assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fail |
| assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
| assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
| assertIsInstance(obj, cls, msg=None) | 验证obj是cls的实例,不是则fail |
| assertNotIsInstance(obj, cls, msg=None) | 验证obj不是cls的实例,是则fail |
我们还可以通过IDE来增加断言:当结束录制后,保存的脚本就有了断言的代码。

脚本执行完毕之后,还需要看到HTML报告,下面我们就通过HTMLTestRunner.py 来生成测试报告。这个HTMLTestRunner.py 需要我们手动去添加到C:\Program Files\Python\Lib这个目录中(Python的安装目录),才能看到测试报告。
在src202205文件夹中,创建一个HTMLReport文件(名字不作要求):
import unittest
import sys, os
import HTMLTestRunner
import time
def createSuite():
discover = unittest.defaultTestLoader.discover('../src202205', pattern='test00*.py', top_level_dir=None)
print(discover)
return discover
if __name__ == "__main__":
# 获取当前文件的路径
curpath = sys.path[0]
print(curpath)
if not os.path.exists(curpath+"/resultReport"):
os.mkdir(curpath+"/resultReport")
now = time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))
filename = curpath+"/resultReport/"+now+"-"+"resultReport.html"
with open(filename, 'wb') as fp:
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"测试报告",
description=u"测试用例执行的结果", verbosity=2)
suite = createSuite()
runner.run(suite)
细节:
1.curpath = sys.path[0]打印出来的路径是D:\pythonProject\0530\src202205,即这个HTMLReport文件的路径。目的是在该路径底下先创建一个resultReport的文件夹,来保存测试报告。
2.now = time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))是格式化时间,time.time()获取的是时间戳, time.localtime(time.time())将时间戳转化成现在的时间,time.strftime("%Y-%m-%d-%H %M %S", time.localtime(time.time()))是将现在的时间格式化为年-月-日- 时 分 秒,能够使得测试报告的名字更加清晰。
3.with open(filename, 'wb') as fp:打开一个文件,以wb的形式去写入,fp为输入流。
4.runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"测试报告",description=u"测试用例执行的结果", verbosity=2),stram是写入文件流,title是给该html文件起标题,description对测试的结果的描述,verbosity=2是对测试报告结果信息复杂度。
如上面代码打印的测试报告如下:可以看到对测试结果的总结是非常有帮助的。

用例不可能每一次运行都成功,肯定运行时候有不成功的时候。如果可以捕捉到错误,并且把错误截图保存,这将是一个非常棒的功能,也会给我们错误定位带来方便。
下面的代码中,利用断言利用driver.title来判断打开的页面是否为百度的搜索页面,如果不是,就截图当前测试打开的页面是一个什么页面,将截图到的图片放到一个指定的文件夹中保存,并给该图片命名。
注:一定要注意路径的问题。
import os.path
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
#继承
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
def test_baidu2(self):
driver = self.driver
try:
self.assertEqual(driver.title,"百度,你就知道",msg="判断失败,没有打开百度页面")
except:
self.save_error_image(driver, "baidu.png")
time.sleep(3)
def save_error_image(self, driver, name):
if not os.path.exists("./errorImage"):
os.makedirs("./errorImage")
now = time.strftime("%Y%m%d-%H-%M-%S", time.localtime(time.time()))
self.driver.get_screenshot_as_file('./errorImage/'+now+"-"+name)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
如果断言中判断失败,就在目录中产生了一个errorImage的目录,目录中就放入了在断言中失败时,测试打开的页面。
目录关系:

之前的case都是数据和代码在一起编写。考虑如下场景:
需要多次执行一个案例,比如baidu搜索,分别输入中文、英文、数字等进行搜索这时候需要编写3个案例吗?有没有版本一次运行?
python 的unittest 没有自带数据驱动功能。所以如果使用unittest,同时又想使用数据驱动,那么就可以使用DDT来完成。
可以在cmd窗口中输入pip show ddt 中查看是否安装了ddt,如果没有安装会出现一个warning,安装ddt:pip install ddt ,就进行安装。
安装好ddt后,在PyCharm中就可以直接import ddt 了,它能够实现类似于代码的复用,耦合性更强。
使用ddt前,导入ddt中的ddt、data、file_name、unpack
在使用ddt的时候,必须要在类的前面加上@ddt,才能说明这个类是使用ddt了的。
如:可以看到,test_baidu1的方法中增加了一个value参数,即该方法上面的@data() 中的数据,一个一个地将data中的数据传入给参数value,就能够避免大量重复的代码了。
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
from ddt import ddt, file_data, unpack, data
#继承
@ddt
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
@data("Lisa", "马思维")
def test_baidu1(self, value):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys(value)
driver.find_element(By.ID, "su").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
有时候,我们可能会测试大量的数据,因此就不可能直接在@data()中输入大量的内容,会显得代码很乱,因此可以将上面的内容写入到一个json文件里面,以json的形式来保存数据。
(此时json文件和此py文件是同一级文件夹下的),因此可以直接写@file_data("test_baidu.json")
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
from ddt import ddt, file_data, unpack, data
#继承
@ddt
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
# @data("Lisa", "马思维")
@file_data("test_baidu.json")
def test_baidu1(self, value):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys(value)
driver.find_element(By.ID, "su").click()
time.sleep(3)
@unittest.skip("skipping")
def test_baidu2(self):
driver = self.driver
driver.find_element(By.LINK_TEXT, "新闻").click()
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
test_baidu.json中的内容:

有时候,我们要使用的数据是对应的,如:此时要断言网页的title和我们预计的一不一样,不一样就报错。
利用unpack也可以使用解耦合。当一个列表中有多个数据时,就使用unpack来处理了。
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
from ddt import ddt, file_data, unpack, data
#继承
@ddt
class testCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
# @data("Lisa", "马思维")
@data(["Jolin","Jolin_百度搜索"],["马思维","马思维_百度搜索"])
@unpack
def test_baidu1(self, value1, value2):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys(value1)
driver.find_element(By.ID, "su").click()
time.sleep(3)
self.assertEqual(driver.title, value2, msg="fail!")
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
同样地,测试的数据不可能是一两条,因此就可以把@data(["Jolin","Jolin_百度搜索"],["马思维","马思维_百度搜索"])放入到一个.txt文件中保存,再在需要这个数据时就读取该文件。注:在.txt文件中,在首行要加上Data,第二行才开始写数据。
文件目录关系:

import csv
import sys
import time
from selenium import webdriver
import unittest
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoAlertPresentException
from ddt import ddt, file_data, unpack, data
def getCsv(file_name):
rows=[]
path=sys.path[0]
print(path)
with open(path + '/src202205/' + '/data/'+file_name, 'rt',encoding='utf-8') as f:
readers = csv.reader(f, delimiter=',', quotechar='|')
next(readers, None)
for row in readers:
temprows=[]
for i in row:
temprows.append(i)
rows.append(temprows)
return rows
@ddt
class testCase(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(3)
def tearDown(self):
self.driver.quit()
# @data(["Jolin","Jolin_百度搜索"],["马思维","马思维_百度搜索"])
@data(*getCsv('test_baidu_data.txt'))
@unpack
def test_baidu1(self, value, excepted_value):
driver = self.driver
driver.find_element(By.ID, "kw").send_keys(value)
driver.find_element(By.ID, "su").click()
driver.maximize_window()
time.sleep(3)
self.assertEqual(driver.title, excepted_value, msg='fail!!')
time.sleep(3)
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
if __name__ == "__main__":
unittest.main(verbosity=0)
对于readers = csv.reader(f, delimiter=',', quotechar='|')中的意思是:f为读取的流,delimiter是以什么为分隔符,quotechar中的| 为换行符。
而next(readers, None)就是开始读取该.txt文件的意思。
此处需要注意.txt的路径问题,读取的该文件时要确定好对应的路径。如果报错的时候就看控制台中提供的信息,去更准确地定位问题。
Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图
我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。我一直在Rails上做两个项目,它们运行良好,但在这个过程中重新发明了轮子,自来水(和热水)和止痛药,正如我随后了解到的那样,这些已经存在于框架中。那么基本上,正确了解框架中所有智能部分的最佳方法是什么,这将节省时间而不是自己构建已经实现的功能?从第1页开始阅读文档?是否有公开所有内容的特定示例应用程序?一个特定的开源项目?所有的rails交通?还是完全
关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。关闭4年前。Improvethisquestion我希望能够将模板化的YARD文档样式注释插入到我现有的Rails遗留应用程序中。目前它的评论很少。我想要具有指定参数的类header和方法header(通过从我假定的方法签名中提取)和返回值的占位符。在PHP代码中,我有一些工具可以检查代码并在适当的位置创建插入到代码中的文档header注释。在带有Ducktyping等的Ruby中,我确信诸如@params等类型之类
我尝试用Ruby设计一个基于Web的应用程序。我开发了一个简单的核心应用程序,在没有框架和数据库的情况下在六边形架构中实现DCI范例。核心六边形中有小六边形和网络,数据库,日志等适配器。每个六边形都在没有数据库和框架的情况下自行运行。在这种方法中,我如何提供与数据库模型和实体类的关系作为独立于数据库的关系。我想在将来将框架从Rails更改为Sinatra或数据库。事实上,我如何在这个核心Hexagon中实现完全隔离的rails和mongodb的数据库适配器或框架适配器。有什么想法吗? 最佳答案 ROM呢?(Ruby对象映射器)。还有
据我了解,Python的扭曲框架为网络通信提供了更高级别的抽象(?)。我正在寻找在Rails应用程序中使用与twisted等效的Ruby。 最佳答案 看看EventMachine.它不像Twisted那样广泛,但它是围绕事件驱动网络编程的相同概念构建的。 关于python-Ruby是否有相当于Python的扭曲框架作为网络抽象层?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/9
我想使用比Rails(Sinatra/Ramaze/Camping)更轻的框架,但我担心这样做我将无法使用许多以插件形式为Rails定制的共享库.这是一个主要问题,还是这些插件中的大多数都可以跨不同的Ruby框架使用?使用Ruby框架而不是Rails是否还有其他潜在的缺点? 最佳答案 您仍然可以使用gems在你提到的所有框架中,很多东西都是可重用的。想要交换一个新的ORM,没问题。想要一个花哨的shmacy语法高亮,没问题。Rails一直在大力插入摆脱旧的插件模型,转而使用gems。如果其他框架之一符合您的需求,最好使用它。请记住,
我将以下代码放入RSpec测试中:it{shouldvalidate_format_of(:email).not_with('test@test')}并设置实际的类:validates:email,:presence=>true,:format=>/\b[A-Z0-9._%-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b/i当我运行测试时,我得到:失败:1)用户失败/错误:它{应该validate_format_of(:email).not_with('test@test')}当电子邮件设置为“test@test”时,预期错误包括“can'tbeblank”,得到错误
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭9年前。ImprovethisquestionRails使用了哪些单元测试框架?我正在阅读一本书(PragmaticProgrammersAgileDev.withRails),其中展示了如何在Rails中进行单元测试。这本书向我展示了默认的Rails测试套件(Test::Unit的子类)。这是Rails社区中使用的主要测试框架吗?我在执行常规ruby时使用RSpec,我也希望能够在Rails中使用它(如果不是太麻烦的话)?
我看过很多过时的播客,其中提到摩卡是我想安装的一个宝石,因为它确实比rspec更好模仿。我有一种感觉,rspec开发人员已经意识到这一点,并从那时起改进了他们的模拟。但是,在默认的spec_helper.rb文件中,我看到三个模拟框架的一些注释掉的代码存根mochaflexmockrr向任何能给我一个像样答案的人投赞成票,就这些框架中至少一个的利弊与rspec自己的模仿框架进行比较。如果你能给我一个关于这三个问题的详细说明,我会接受你的回答。 最佳答案 真的,这只是口味的问题。看一看语法,看看什么最适合你。当然,使用rspec的内置