测试失败会发生。如果不发生,测试就没有什么用。当测试失败时,我们需要找出原因。这可能是测试的问题,也可能是应用的问题。确定问题出在哪里以及如何解决的过程是相似的。
我们将在 pytest 标志和 pdb 的帮助下调试一些失败的代码
增加如下新功能:cards list -state done
# 安装新版本
$ cd ch13/cards_proj
$ pip install -e .
$ pytest tests
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini, testpaths: tests
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 53 items
tests\api\test_add.py ..... [ 9%]
tests\api\test_config.py . [ 11%]
tests\api\test_count.py ... [ 16%]
tests\api\test_delete.py ... [ 22%]
tests\api\test_finish.py .... [ 30%]
tests\api\test_list.py ......... [ 47%]
tests\api\test_list_done.py F [ 49%]
tests\api\test_start.py .... [ 56%]
tests\api\test_update.py .... [ 64%]
tests\api\test_version.py . [ 66%]
tests\cli\test_add.py .. [ 69%]
tests\cli\test_config.py .. [ 73%]
tests\cli\test_count.py . [ 75%]
tests\cli\test_delete.py . [ 77%]
tests\cli\test_done.py F [ 79%]
tests\cli\test_errors.py ..... [ 88%]
tests\cli\test_finish.py . [ 90%]
tests\cli\test_list.py .. [ 94%]
tests\cli\test_start.py . [ 96%]
tests\cli\test_update.py . [ 98%]
tests\cli\test_version.py . [100%]
================================== FAILURES ===================================
_______________________________ test_list_done ________________________________
cards_db = <cards.api.CardsDB object at 0x00000202D789C670>
@pytest.mark.num_cards(10)
def test_list_done(cards_db):
cards_db.finish(3)
cards_db.finish(5)
the_list = cards_db.list_done_cards()
> assert len(the_list) == 2
E TypeError: object of type 'NoneType' has no len()
tests\api\test_list_done.py:11: TypeError
__________________________________ test_done __________________________________
cards_db = <cards.api.CardsDB object at 0x00000202D789C670>
cards_cli = <function cards_cli_no_redirect.<locals>.run_cli at 0x00000202D7A34040>
def test_done(cards_db, cards_cli):
cards_db.add_card(cards.Card("some task", state="done"))
cards_db.add_card(cards.Card("another"))
cards_db.add_card(cards.Card("a third", state="done"))
output = cards_cli("done")
> assert output == expected
E AssertionError: assert '' == '\n ID sta... a third'
E -
E - ID state owner summary
E - ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
E - 1 done some task
E - 3 done a third
tests\cli\test_done.py:16: AssertionError
=========================== short test summary info ===========================
FAILED tests/api/test_list_done.py::test_list_done - TypeError: object of typ...
FAILED tests/cli/test_done.py::test_done - AssertionError: assert '' == '\n ...
======================== 2 failed, 51 passed in 1.95s =========================
-lf / --last-failed:/ -最后一次失败。只运行最后失败的测试
-ff / --先失败的。运行所有的测试,从最后失败的测试开始。
-x / --exitfirst: 在第一次失败后停止测试会话。
--maxfail=num: 在制定次数失败后停止测试
-nf / --new-first: 运行所有的测试,按文件修改时间排序
--sw / --stepwise: 在第一次失败时停止测试。下次在最后一次失败时启动测试
--sw-skip / --stepwise-skip。与-sw相同,但跳过第一次失败。
控制pytest输出的标志。
启动命令行调试器的标志。
--pdb: 在故障点启动交互式调试会话
--trace。在运行每个测试时立即启动pdb源代码调试器
--pdbcls: 使用pdb的替代品,例如IPython的调试器,使用-pdbcls=IPython.terminal.debugger:TerminalPdb
让我们开始我们的调试,确保当我们再次运行测试时失败。我们将使用 --lf 来重新运行失败的测试,而 --tb=no 来隐藏回溯,因为我们还没有准备好。
$ pytest --lf --tb=no
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items / 25 deselected / 2 selected
run-last-failure: rerun previous 2 failures (skipped 13 files)
api\test_list_done.py F [ 50%]
cli\test_done.py F [100%]
=========================== short test summary info ===========================
FAILED api\test_list_done.py::test_list_done - TypeError: object of type 'Non...
FAILED cli\test_done.py::test_done - AssertionError: assert '' == '\n ID s...
====================== 2 failed, 25 deselected in 0.23s =======================
让我们只运行第一个失败的测试,在失败后停止,然后看一下回溯。
$ pytest --lf -x
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items / 25 deselected / 2 selected
run-last-failure: rerun previous 2 failures (skipped 13 files)
api\test_list_done.py F
================================== FAILURES ===================================
_______________________________ test_list_done ________________________________
cards_db = <cards.api.CardsDB object at 0x000002C251B1A460>
@pytest.mark.num_cards(10)
def test_list_done(cards_db):
cards_db.finish(3)
cards_db.finish(5)
the_list = cards_db.list_done_cards()
> assert len(the_list) == 2
E TypeError: object of type 'NoneType' has no len()
api\test_list_done.py:11: TypeError
=========================== short test summary info ===========================
FAILED api\test_list_done.py::test_list_done - TypeError: object of type 'Non...
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!
====================== 1 failed, 25 deselected in 0.27s =======================
为了确保我们了解问题,我们可以用-l/--showlocals重新运行同一个测试。我们不需要完整的回溯,所以我们可以用--tb=short来缩短它。
$ pytest --lf -x -l --tb=short
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items / 25 deselected / 2 selected
run-last-failure: rerun previous 2 failures (skipped 13 files)
api\test_list_done.py F
================================== FAILURES ===================================
_______________________________ test_list_done ________________________________
api\test_list_done.py:11: in test_list_done
assert len(the_list) == 2
E TypeError: object of type 'NoneType' has no len()
cards_db = <cards.api.CardsDB object at 0x000002365B936670>
the_list = None
=========================== short test summary info ===========================
FAILED api\test_list_done.py::test_list_done - TypeError: object of type 'Non...
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!
====================== 1 failed, 25 deselected in 0.27s =======================
没错,the_list = None。-l/--showlocals通常是非常有用的,有时足以完全调试出一个测试失败。更重要的是,-l/--showlocals的存在已经训练了我在测试中使用大量的中间变量。当测试失败时,它们就会派上用场。
现在我们知道,在这种情况下,list_done_cards()返回的是 None。但我们不知道为什么。我们将在测试过程中使用 pdb 来调试 list_done_cards() 的内部。
pdb, "Python 调试器 "(Python debugger)的缩写,是 Python 标准库的一部分。
你可以通过几种不同的方式从pytest启动pdb。
在测试代码或应用代码中添加breakpoint() 调用。
使用--pdb标志。使用-pdb,pytest将在故障点处停止。
使用--trace标志。使用--trace,pytest将在每个测试的开始处停止。
对于我们的目的来说,将--lf和--trace结合起来,效果会非常好。这个组合将告诉pytest重新运行失败的测试,并在test_list_done()的开始处停止,在调用list_done_cards()之前。
$ pytest --lf --trace
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items / 25 deselected / 2 selected
run-last-failure: rerun previous 2 failures (skipped 13 files)
api\test_list_done.py
>>>>>>>>>>>>>>>>>>>> PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>
> d:\code\pytest_quick\ch13\cards_proj\tests\api\test_list_done.py(6)test_list_done()
-> cards_db.finish(3)
(Pdb)
以下是pdb识别的常用命令。完整的列表在pdb文档中。
元命令:
查看所在的位置。
查看数值。
执行命令。
继续调试我们的测试,我们将使用ll来列出当前的函数。
$ pytest --lf --trace
============================= test session starts =============================
platform win32 -- Python 3.9.13, pytest-7.1.2, pluggy-1.0.0
rootdir: D:\code\pytest_quick\ch13\cards_proj, configfile: pytest.ini
plugins: allure-pytest-2.12.0, Faker-4.18.0, tep-0.8.2, anyio-3.5.0, cov-4.0.0
collected 27 items / 25 deselected / 2 selected
run-last-failure: rerun previous 2 failures (skipped 13 files)
api\test_list_done.py
>>>>>>>>>>>>>>>>>>>> PDB runcall (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>
> d:\code\pytest_quick\ch13\cards_proj\tests\api\test_list_done.py(6)test_list_done()
-> cards_db.finish(3)
(Pdb) ll
4 @pytest.mark.num_cards(10)
5 def test_list_done(cards_db):
6 -> cards_db.finish(3)
7 cards_db.finish(5)
8
9 the_list = cards_db.list_done_cards()
10
11 assert len(the_list) == 2
12 for card in the_list:
13 assert card.id in (3, 5)
14 assert card.state == "done"
(Pdb) until 8
> d:\code\pytest_quick\ch13\cards_proj\tests\api\test_list_done.py(9)test_list_done()
-> the_list = cards_db.list_done_cards()
(Pdb) step
--Call--
> d:\code\pytest_quick\ch13\cards_proj\src\cards\api.py(91)list_done_cards()
-> def list_done_cards(self):
(Pdb) ll
91 -> def list_done_cards(self):
92 """Return the 'done' cards."""
93 done_cards = self.list_cards(state="done")
(Pdb) return
--Return--
> d:\code\pytest_quick\ch13\cards_proj\src\cards\api.py(93)list_done_cards()->None
-> done_cards = self.list_cards(state="done")
(Pdb) ll
91 def list_done_cards(self):
92 """Return the 'done' cards."""
93 -> done_cards = self.list_cards(state="done")
(Pdb) pp done_cards
[Card(summary='Line for PM identify decade.', owner='Russell', state='done', id=3),
Card(summary='Director baby season industry the describe.', owner='Cody', state='done', id=5)]
(Pdb) step
> d:\code\pytest_quick\ch13\cards_proj\tests\api\test_list_done.py(11)test_list_done()
-> assert len(the_list) == 2
(Pdb) ll
4 @pytest.mark.num_cards(10)
5 def test_list_done(cards_db):
6 cards_db.finish(3)
7 cards_db.finish(5)
8
9 the_list = cards_db.list_done_cards()
10
11 -> assert len(the_list) == 2
12 for card in the_list:
13 assert card.id in (3, 5)
14 assert card.state == "done"
(Pdb) pp the_list
None
(Pdb) exit
!!!!!!!!!!!!!!!!!! _pytest.outcomes.Exit: Quitting debugger !!!!!!!!!!!!!!!!!!!
===================== 25 deselected in 172.96s (0:02:52) ======================
现在很清楚了。我们在list_done_cards()中的ded_cards变量中得到了正确的列表。然而,这个值并没有返回。因为如果没有返回语句,Python 的默认返回值是 None,这就是 test_list_done() 中被分配给 the_list 的值。
如果我们停止调试器,在 list_done_cards() 中添加返回值 done_cards,然后重新运行失败的测试,我们可以看看这是否解决了问题。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
GivenIamadumbprogrammerandIamusingrspecandIamusingsporkandIwanttodebug...mmm...let'ssaaay,aspecforPhone.那么,我应该把“require'ruby-debug'”行放在哪里,以便在phone_spec.rb的特定点停止处理?(我所要求的只是一个大而粗的箭头,即使是一个有挑战性的程序员也能看到:-3)我已经尝试了很多位置,除非我没有正确测试它们,否则会发生一些奇怪的事情:在spec_helper.rb中的以下位置:require'rubygems'require'spork'