-
Notifications
You must be signed in to change notification settings - Fork 2
/
testing.html
1307 lines (1176 loc) · 69 KB
/
testing.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Rails 程序测试指南 — Ruby on Rails 指南</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
更多内容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">综览</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下载</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">源码</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">视频</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首页">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首页</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目录</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="L">
<dt>入门</dt>
<dd><a href="getting_started.html">Rails 入门</a></dd>
<dt>模型</dt>
<dd><a href="active_record_basics.html">Active Record 基础</a></dd>
<dd><a href="active_record_migrations.html">Active Record 数据库迁移</a></dd>
<dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
<dd><a href="association_basics.html">Active Record 关联</a></dd>
<dd><a href="active_record_querying.html">Active Record 查询</a></dd>
<dt>视图</dt>
<dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
<dd><a href="form_helpers.html">Action View 表单帮助方法</a></dd>
<dt>控制器</dt>
<dd><a href="action_controller_overview.html">Action Controller 简介</a></dd>
<dd><a href="routing.html">Rails 路由全解</a></dd>
</dl>
<dl class="R">
<dt>深入</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
<dd><a href="i18n.html">Rails 国际化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
<dd><a href="active_job_basics.html">Active Job 基础</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">调试 Rails 程序</a></dd>
<dd><a href="configuring.html">设置 Rails 程序</a></dd>
<dd><a href="command_line.html">Rails 命令行</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>扩展 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客制与新建 Rails 产生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 应用程式模版</a></dd>
<dt>贡献 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件准则</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</a></dd>
<dt>维护方针</dt>
<dd><a href="maintenance_policy.html">维护方针</a></dd>
<dt>发布记</dt>
<dd><a href="upgrading_ruby_on_rails.html">升级 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
</dl>
</div>
</li>
<!-- <li><a class="nav-item" href="//github.com/docrails-tw/wiki">参与翻译</a></li> -->
<li><a class="nav-item" href="https://github.com/ruby-china/guides/blob/master/CONTRIBUTING.md">贡献</a></li>
<li><a class="nav-item" href="credits.html">致谢</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目录</option>
<optgroup label="入门">
<option value="getting_started.html">Rails 入门</option>
</optgroup>
<optgroup label="模型">
<option value="active_record_basics.html">Active Record 基础</option>
<option value="active_record_migrations.html">Active Record 数据库迁移</option>
<option value="active_record_validations.html">Active Record 数据验证</option>
<option value="active_record_callbacks.html">Active Record 回调</option>
<option value="association_basics.html">Active Record 关联</option>
<option value="active_record_querying.html">Active Record 查询</option>
</optgroup>
<optgroup label="视图">
<option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
<option value="form_helpers.html">Action View 表单帮助方法</option>
</optgroup>
<optgroup label="控制器">
<option value="action_controller_overview.html">Action Controller 简介</option>
<option value="routing.html">Rails 路由全解</option>
</optgroup>
<optgroup label="深入">
<option value="active_support_core_extensions.html">Active Support 核心扩展</option>
<option value="i18n.html">Rails 国际化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基础</option>
<option value="active_job_basics.html">Active Job 基础</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">调试 Rails 程序</option>
<option value="configuring.html">设置 Rails 程序</option>
<option value="command_line.html">Rails 命令行</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="扩展 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客制与新建 Rails 产生器</option>
<option value="rails_application_templates.html">Rails 应用程式模版</option>
</optgroup>
<optgroup label="贡献 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">贡献 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件准则</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南准则</option>
</optgroup>
<optgroup label="维护方针">
<option value="maintenance_policy.html">维护方针</option>
</optgroup>
<optgroup label="发布记">
<option value="upgrading_ruby_on_rails.html">升级 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide" />
<div id="feature">
<div class="wrapper">
<h2>Rails 程序测试指南</h2><p>本文介绍 Rails 内建对测试的支持。</p><p>读完本文,你将学到:</p>
<ul>
<li>Rails 测试术语;</li>
<li>如何为程序编写单元测试,功能测试和集成测试;</li>
<li>常用的测试方法和插件;</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E4%B8%BA-rails-%E7%A8%8B%E5%BA%8F%E7%BC%96%E5%86%99%E6%B5%8B%E8%AF%95%EF%BC%9F">为什么要为 Rails 程序编写测试?</a></li>
<li>
<a href="#%E6%B5%8B%E8%AF%95%E7%AE%80%E4%BB%8B">测试简介</a>
<ul>
<li><a href="#%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83">测试环境</a></li>
<li><a href="#rails-sets-up-for-testing-from-the-word-go">Rails Sets up for Testing from the Word Go</a></li>
<li><a href="#%E5%9B%BA%E4%BB%B6%E8%AF%A6%E8%A7%A3">固件详解</a></li>
</ul>
</li>
<li>
<a href="#%E4%B8%BA%E6%A8%A1%E5%9E%8B%E7%BC%96%E5%86%99%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95">为模型编写单元测试</a>
<ul>
<li><a href="#%E7%BB%B4%E6%8A%A4%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E6%A8%A1%E5%BC%8F">维护测试数据库的模式</a></li>
<li><a href="#%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95">运行测试</a></li>
<li><a href="#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95%E8%A6%81%E6%B5%8B%E8%AF%95%E4%BB%80%E4%B9%88">单元测试要测试什么</a></li>
<li><a href="#%E5%8F%AF%E7%94%A8%E7%9A%84%E6%96%AD%E8%A8%80">可用的断言</a></li>
<li><a href="#rails-%E6%8F%90%E4%BE%9B%E7%9A%84%E6%96%AD%E8%A8%80">Rails 提供的断言</a></li>
</ul>
</li>
<li>
<a href="#%E4%B8%BA%E6%8E%A7%E5%88%B6%E5%99%A8%E7%BC%96%E5%86%99%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95">为控制器编写功能测试</a>
<ul>
<li><a href="#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E8%A6%81%E6%B5%8B%E8%AF%95%E4%BB%80%E4%B9%88">功能测试要测试什么</a></li>
<li><a href="#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%8F%AF%E7%94%A8%E7%9A%84%E8%AF%B7%E6%B1%82%E7%B1%BB%E5%9E%8B">功能测试中可用的请求类型</a></li>
<li><a href="#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%9B%9B%E4%B8%AA-hash">可用的四个 Hash</a></li>
<li><a href="#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%AE%9E%E4%BE%8B%E5%8F%98%E9%87%8F">可用的实例变量</a></li>
<li><a href="#%E8%AE%BE%E7%BD%AE%E6%8A%A5%E5%A4%B4%E5%92%8C-cgi-%E5%8F%98%E9%87%8F">设置报头和 CGI 变量</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95%E6%A8%A1%E6%9D%BF%E5%92%8C%E5%B8%83%E5%B1%80">测试模板和布局</a></li>
<li><a href="#%E5%AE%8C%E6%95%B4%E7%9A%84%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95%E7%A4%BA%E4%BE%8B">完整的功能测试示例</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95%E8%A7%86%E5%9B%BE">测试视图</a></li>
</ul>
</li>
<li>
<a href="#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95">集成测试</a>
<ul>
<li><a href="#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95%E4%B8%AD%E5%8F%AF%E7%94%A8%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95">集成测试中可用的帮助方法</a></li>
<li><a href="#%E9%9B%86%E6%88%90%E6%B5%8B%E8%AF%95%E7%A4%BA%E4%BE%8B">集成测试示例</a></li>
</ul>
</li>
<li><a href="#%E8%BF%90%E8%A1%8C%E6%B5%8B%E8%AF%95%E4%BD%BF%E7%94%A8%E7%9A%84-rake-%E4%BB%BB%E5%8A%A1">运行测试使用的 Rake 任务</a></li>
<li><a href="#minitest-%E7%AE%80%E4%BB%8B">MiniTest 简介</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95%E5%89%8D%E5%87%86%E5%A4%87%E5%92%8C%E6%B5%8B%E8%AF%95%E5%90%8E%E6%B8%85%E7%90%86">测试前准备和测试后清理</a></li>
<li><a href="#%E6%B5%8B%E8%AF%95%E8%B7%AF%E7%94%B1">测试路由</a></li>
<li>
<a href="#%E6%B5%8B%E8%AF%95%E9%82%AE%E4%BB%B6%E7%A8%8B%E5%BA%8F">测试邮件程序</a>
<ul>
<li><a href="#%E7%A1%AE%E4%BF%9D%E9%82%AE%E4%BB%B6%E7%A8%8B%E5%BA%8F%E5%9C%A8%E7%AE%A1%E6%8E%A7%E5%86%85">确保邮件程序在管控内</a></li>
<li><a href="#%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95">单元测试</a></li>
<li><a href="#%E5%8A%9F%E8%83%BD%E6%B5%8B%E8%AF%95">功能测试</a></li>
</ul>
</li>
<li><a href="#%E6%B5%8B%E8%AF%95%E5%B8%AE%E5%8A%A9%E6%96%B9%E6%B3%95">测试帮助方法</a></li>
<li><a href="#%E5%85%B6%E4%BB%96%E6%B5%8B%E8%AF%95%E6%96%B9%E6%A1%88">其他测试方案</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="为什么要为-rails-程序编写测试?">1 为什么要为 Rails 程序编写测试?</h3><p>在 Rails 中编写测试非常简单,生成模型和控制器时,已经生成了测试代码骨架。</p><p>即便是大范围重构后,只需运行测试就能确保实现了所需功能。</p><p>Rails 中的测试还可以模拟浏览器请求,无需打开浏览器就能测试程序的响应。</p><h3 id="测试简介">2 测试简介</h3><p>测试是 Rails 程序的重要组成部分,不是处于尝鲜和好奇才编写测试。基本上每个 Rails 程序都要频繁和数据库交互,所以测试时也要和数据库交互。为了能够编写高效率的测试,必须要了解如何设置数据库以及导入示例数据。</p><h4 id="测试环境">2.1 测试环境</h4><p>默认情况下,Rails 程序有三个环境:开发环境,测试环境和生产环境。每个环境所需的数据库在 <code>config/database.yml</code> 文件中设置。</p><p>测试使用的数据库独立于其他环境,不会影响开发环境和生产环境的数据库。</p><h4 id="rails-sets-up-for-testing-from-the-word-go">2.2 Rails Sets up for Testing from the Word Go</h4><p>执行 <code>rails new</code> 命令生成新程序时,Rails 会创建一个名为 <code>test</code> 的文件夹。这个文件夹中的内容如下:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ ls -F test
controllers/ helpers/ mailers/ test_helper.rb
fixtures/ integration/ models/
</pre>
</div>
<p><code>modles</code> 文件夹存放模型测试,<code>controllers</code> 文件夹存放控制器测试,<code>integration</code> 文件夹存放多个控制器之间交互的测试。</p><p><code>fixtures</code> 文件夹中存放固件。固件是一种组织测试数据的方式。</p><p><code>test_helper.rb</code> 文件中保存测试的默认设置。</p><h4 id="固件详解">2.3 固件详解</h4><p>好的测试应该应该具有提供测试数据的方式。在 Rails 中,测试数据由固件提供。</p><h5 id="固件是什么?">2.3.1 固件是什么?</h5><p>固件代指示例数据,在运行测试之前,把预先定义好的数据导入测试数据库。固件相互独立,一个文件对应一个模型,使用 YAML 格式编写。</p><p>固件保存在文件夹 <code>test/fixtures</code> 中,执行 <code>rails generate model</code> 命令生成新模型时,会在这个文件夹中自动创建一个固件文件。</p><h5 id="yaml">2.3.2 YAML</h5><p>使用 YAML 格式编写的固件可读性极高,文件的扩展名是 <code>.yml</code>,例如 <code>users.yml</code>。</p><p>下面举个例子:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# lo & behold! I am a YAML comment!
david:
name: David Heinemeier Hansson
birthday: 1979-10-15
profession: Systems development
steve:
name: Steve Ross Kellock
birthday: 1974-09-27
profession: guy with keyboard
</pre>
</div>
<p>每个附件都有名字,后面跟着一个缩进后的键值对列表。记录之间往往使用空行分开。在固件中可以使用注释,在行首加上 <code>#</code> 符号即可。如果键名使用了 YAML 中的关键字,必须使用引号,例如 <code>'yes'</code> 和 <code>'no'</code>,这样 YAML 解析程序才能正确解析。</p><p>如果涉及到关联,定义一个指向其他固件的引用即可。例如,下面的固件针对 <code>belongs_to/has_many</code> 关联:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
# In fixtures/categories.yml
about:
name: About
# In fixtures/articles.yml
one:
title: Welcome to Rails!
body: Hello world!
category: about
</pre>
</div>
<h5 id="使用-erb-增强固件">2.3.3 使用 ERB 增强固件</h5><p>ERB 允许在模板中嵌入 Ruby 代码。Rails 加载 YAML 格式的固件时,会先使用 ERB 进行预处理,因此可使用 Ruby 代码协助生成示例数据。例如,下面的代码会生成一千个用户:</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
<% 1000.times do |n| %>
user_<%= n %>:
username: <%= "user#{n}" %>
email: <%= "user#{n}@example.com" %>
<% end %>
</pre>
</div>
<h5 id="固件实战">2.3.4 固件实战</h5><p>默认情况下,运行模型测试和控制器测试时会自动加载 <code>test/fixtures</code> 文件夹中的所有固件。加载的过程分为三步:</p>
<ul>
<li>从数据表中删除所有和固件对应的数据;</li>
<li>把固件载入数据表;</li>
<li>把固件中的数据赋值给变量,以便直接访问;</li>
</ul>
<h5 id="固件是-active-record-对象">2.3.5 固件是 Active Record 对象</h5><p>固件是 Active Record 实例,如前一节的第 3 点所述,在测试用例中可以直接访问这个对象,因为固件中的数据会赋值给一个本地变量。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# this will return the User object for the fixture named david
users(:david)
# this will return the property for david called id
users(:david).id
# one can also access methods available on the User class
email(david.girlfriend.email, david.location_tonight)
</pre>
</div>
<h3 id="为模型编写单元测试">3 为模型编写单元测试</h3><p>在 Rails 中,单元测试用来测试模型。</p><p>本文会使用 Rails 脚手架生成模型、迁移、控制器、视图和遵守 Rails 最佳实践的完整测试组件。我们会使用自动生成的代码,也会按需添加其他代码。</p><div class="note"><p>关于 Rails 脚手架的详细介绍,请阅读“<a href="getting_started.html">Rails 入门</a>”一文。</p></div><p>执行 <code>rails generate scaffold</code> 命令生成资源时,也会在 <code>test/models</code> 文件夹中生成单元测试文件:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate scaffold post title:string body:text
...
create app/models/post.rb
create test/models/post_test.rb
create test/fixtures/posts.yml
...
</pre>
</div>
<p><code>test/models/post_test.rb</code> 文件中默认的测试代码如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'test_helper'
class PostTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end
</pre>
</div>
<p>下面逐行分析这段代码,熟悉 Rails 测试的代码和相关术语。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'test_helper'
</pre>
</div>
<p>现在你已经知道,<code>test_helper.rb</code> 文件是测试的默认设置,会载入所有测试,因此在所有测试中都可使用其中定义的方法。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PostTest < ActiveSupport::TestCase
</pre>
</div>
<p><code>PostTest</code> 继承自 <code>ActiveSupport::TestCase</code>,定义了一个测试用例,因此可以使用 <code>ActiveSupport::TestCase</code> 中的所有方法。后文会介绍其中一些方法。</p><p><code>MiniTest::Unit::TestCase</code>(<code>ActiveSupport::TestCase</code> 的父类)子类中每个以 <code>test</code> 开头(区分大小写)的方法都是一个测试,所以,<code>test_password</code>、<code>test_valid_password</code> 和 <code>testValidPassword</code> 都是合法的测试名,运行测试用例时会自动运行这些测试。</p><p>Rails 还提供了 <code>test</code> 方法,接受一个测试名作为参数,然后跟着一个代码块。<code>test</code> 方法会生成一个 <code>MiniTest::Unit</code> 测试,方法名以 <code>test_</code> 开头。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "the truth" do
assert true
end
</pre>
</div>
<p>和下面的代码是等效的</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def test_the_truth
assert true
end
</pre>
</div>
<p>不过前者的测试名可读性更高。当然,使用方法定义的方式也没什么问题。</p><div class="note"><p>生成的方法名会把空格替换成下划线。最终得到的结果可以不是合法的 Ruby 标示符,名字中可以包含标点符号等。因为在 Ruby 中,任何字符串都可以作为方法名,奇怪的方法名需要调用 <code>define_method</code> 或 <code>send</code> 方法,所以没有限制。</p></div><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
assert true
</pre>
</div>
<p>这行代码叫做“断言”(assertion)。断言只有一行代码,把指定对象或表达式和期望的结果进行对比。例如,断言可以检查:</p>
<ul>
<li>两个值是够相等;</li>
<li>对象是否为 <code>nil</code>;</li>
<li>这行代码是否抛出异常;</li>
<li>用户的密码长度是否超过 5 个字符;</li>
</ul>
<p>每个测试中都有一个到多个断言。只有所有断言都返回真值,测试才能通过。</p><h4 id="维护测试数据库的模式">3.1 维护测试数据库的模式</h4><p>为了能运行测试,测试数据库要有程序当前的数据库结构。测试帮助方法会检查测试数据库中是否有尚未运行的迁移。如果有,会尝试把 <code>db/schema.rb</code> 或 <code>db/structure.sql</code> 载入数据库。之后如果迁移仍处于待运行状态,会抛出异常。</p><h4 id="运行测试">3.2 运行测试</h4><p>运行测试执行 <code>rake test</code> 命令即可,在这个命令中还要指定要运行的测试文件。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake test test/models/post_test.rb
.
Finished tests in 0.009262s, 107.9680 tests/s, 107.9680 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
</pre>
</div>
<p>上述命令会运行指定文件中的所有测试方法。注意,<code>test_helper.rb</code> 在 <code>test</code> 文件夹中,因此这个文件夹要使用 <code>-I</code> 旗标添加到加载路径中。</p><p>还可以指定测试方法名,只运行相应的测试。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake test test/models/post_test.rb test_the_truth
.
Finished tests in 0.009064s, 110.3266 tests/s, 110.3266 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
</pre>
</div>
<p>上述代码中的点号(<code>.</code>)表示一个通过的测试。如果测试失败,会看到一个 <code>F</code>。如果测试抛出异常,会看到一个 <code>E</code>。输出的最后一行是测试总结。</p><p>要想查看失败测试的输出,可以在 <code>post_test.rb</code> 中添加一个失败测试。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "should not save post without title" do
post = Post.new
assert_not post.save
end
</pre>
</div>
<p>我们来运行新添加的测试:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake test test/models/post_test.rb test_should_not_save_post_without_title
F
Finished tests in 0.044632s, 22.4054 tests/s, 22.4054 assertions/s.
1) Failure:
test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
Failed assertion, no message given.
1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
</pre>
</div>
<p>在输出中,<code>F</code> 表示失败测试。你会看到相应的调用栈和测试名。随后还会显示断言实际得到的值和期望得到的值。默认的断言消息提供了足够的信息,可以帮助你找到错误所在。要想让断言失败的消息更具可读性,可以使用断言可选的消息参数,例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "should not save post without title" do
post = Post.new
assert_not post.save, "Saved the post without a title"
end
</pre>
</div>
<p>运行这个测试后,会显示一个更友好的断言失败消息:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
1) Failure:
test_should_not_save_post_without_title(PostTest) [test/models/post_test.rb:6]:
Saved the post without a title
</pre>
</div>
<p>如果想让这个测试通过,可以在模型中为 <code>title</code> 字段添加一个数据验证:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Post < ActiveRecord::Base
validates :title, presence: true
end
</pre>
</div>
<p>现在测试应该可以通过了,再次运行这个测试来验证一下:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake test test/models/post_test.rb test_should_not_save_post_without_title
.
Finished tests in 0.047721s, 20.9551 tests/s, 20.9551 assertions/s.
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
</pre>
</div>
<p>你可能注意到了,我们首先编写一个检测所需功能的测试,这个测试会失败,然后编写代码,实现所需功能,最后再运行测试,确保测试可以通过。这一过程,在软件开发中称为“测试驱动开发”(Test-Driven Development,TDD)。</p><div class="info"><p>很多 Rails 开发者都会使用 TDD,这种开发方式可以确保程序的每个功能都能正确运行。本文不会详细介绍 TDD,如果想学习,可以从 <a href="http://andrzejonsoftware.blogspot.com/2007/05/15-tdd-steps-to-create-rails.html">15 TDD steps to create a Rails application</a> 这篇文章开始。</p></div><p>要想查看错误的输出,可以在测试中加入一处错误:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "should report error" do
# some_undefined_variable is not defined elsewhere in the test case
some_undefined_variable
assert true
end
</pre>
</div>
<p>运行测试,很看到以下输出:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rake test test/models/post_test.rb test_should_report_error
E
Finished tests in 0.030974s, 32.2851 tests/s, 0.0000 assertions/s.
1) Error:
test_should_report_error(PostTest):
NameError: undefined local variable or method `some_undefined_variable' for #<PostTest:0x007fe32e24afe0>
test/models/post_test.rb:10:in `block in <class:PostTest>'
1 tests, 0 assertions, 0 failures, 1 errors, 0 skips
</pre>
</div>
<p>注意上面输出中的 <code>E</code>,表示测试出错了。</p><div class="note"><p>如果测试方法出现错误或者断言检测失败就会终止运行,继续运行测试组件中的下个方法。测试按照字母顺序运行。</p></div><p>测试失败后会看到相应的调用栈。默认情况下,Rails 会过滤调用栈,只显示和程序有关的调用栈。这样可以减少输出的内容,集中精力关注程序的代码。如果想查看完整的调用栈,可以设置 <code>BACKTRACE</code> 环境变量:</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ BACKTRACE=1 rake test test/models/post_test.rb
</pre>
</div>
<h4 id="单元测试要测试什么">3.3 单元测试要测试什么</h4><p>理论上,应该测试一切可能出问题的功能。实际使用时,建议至少为每个数据验证编写一个测试,至少为模型中的每个方法编写一个测试。</p><h4 id="可用的断言">3.4 可用的断言</h4><p>读到这,详细你已经大概知道一些断言了。断言是测试的核心,是真正用来检查功能是否符合预期的工具。</p><p>断言有很多种,下面列出了可在 Rails 默认测试库 <code>minitest</code> 中使用的断言。方法中的 <code>[msg]</code> 是可选参数,指定测试失败时显示的友好消息。</p>
<table>
<thead>
<tr>
<th>断言</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>assert( test, [msg] )</code></td>
<td>确保 <code>test</code> 是真值</td>
</tr>
<tr>
<td><code>assert_not( test, [msg] )</code></td>
<td>确保 <code>test</code> 是假值</td>
</tr>
<tr>
<td><code>assert_equal( expected, actual, [msg] )</code></td>
<td>确保 <code>expected == actual</code> 返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>assert_not_equal( expected, actual, [msg] )</code></td>
<td>确保 <code>expected != actual</code> 返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>assert_same( expected, actual, [msg] )</code></td>
<td>确保 <code>expected.equal?(actual)</code> 返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>assert_not_same( expected, actual, [msg] )</code></td>
<td>确保 <code>expected.equal?(actual)</code> 返回 <code>false</code>
</td>
</tr>
<tr>
<td><code>assert_nil( obj, [msg] )</code></td>
<td>确保 <code>obj.nil?</code> 返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>assert_not_nil( obj, [msg] )</code></td>
<td>确保 <code>obj.nil?</code> 返回 <code>false</code>
</td>
</tr>
<tr>
<td><code>assert_match( regexp, string, [msg] )</code></td>
<td>确保字符串匹配正则表达式</td>
</tr>
<tr>
<td><code>assert_no_match( regexp, string, [msg] )</code></td>
<td>确保字符串不匹配正则表达式</td>
</tr>
<tr>
<td><code>assert_in_delta( expecting, actual, [delta], [msg] )</code></td>
<td>确保数字 <code>expected</code> 和 <code>actual</code> 之差在 <code>delta</code> 指定的范围内</td>
</tr>
<tr>
<td><code>assert_not_in_delta( expecting, actual, [delta], [msg] )</code></td>
<td>确保数字 <code>expected</code> 和 <code>actual</code> 之差不在 <code>delta</code> 指定的范围内</td>
</tr>
<tr>
<td><code>assert_throws( symbol, [msg] ) { block }</code></td>
<td>确保指定的代码块会抛出一个 Symbol</td>
</tr>
<tr>
<td><code>assert_raises( exception1, exception2, ... ) { block }</code></td>
<td>确保指定的代码块会抛出其中一个异常</td>
</tr>
<tr>
<td><code>assert_nothing_raised( exception1, exception2, ... ) { block }</code></td>
<td>确保指定的代码块不会抛出其中一个异常</td>
</tr>
<tr>
<td><code>assert_instance_of( class, obj, [msg] )</code></td>
<td>确保 <code>obj</code> 是 <code>class</code> 的实例</td>
</tr>
<tr>
<td><code>assert_not_instance_of( class, obj, [msg] )</code></td>
<td>确保 <code>obj</code> 不是 <code>class</code> 的实例</td>
</tr>
<tr>
<td><code>assert_kind_of( class, obj, [msg] )</code></td>
<td>确保 <code>obj</code> 是 <code>class</code> 或其子类的实例</td>
</tr>
<tr>
<td><code>assert_not_kind_of( class, obj, [msg] )</code></td>
<td>确保 <code>obj</code> 不是 <code>class</code> 或其子类的实例</td>
</tr>
<tr>
<td><code>assert_respond_to( obj, symbol, [msg] )</code></td>
<td>确保 <code>obj</code> 可以响应 <code>symbol</code>
</td>
</tr>
<tr>
<td><code>assert_not_respond_to( obj, symbol, [msg] )</code></td>
<td>确保 <code>obj</code> 不可以响应 <code>symbol</code>
</td>
</tr>
<tr>
<td><code>assert_operator( obj1, operator, [obj2], [msg] )</code></td>
<td>确保 <code>obj1.operator(obj2)</code> 返回真值</td>
</tr>
<tr>
<td><code>assert_not_operator( obj1, operator, [obj2], [msg] )</code></td>
<td>确保 <code>obj1.operator(obj2)</code> 返回假值</td>
</tr>
<tr>
<td><code>assert_send( array, [msg] )</code></td>
<td>确保在 <code>array[0]</code> 指定的方法上调用 <code>array[1]</code> 指定的方法,并且把 <code>array[2]</code> 及以后的元素作为参数传入,该方法会返回真值。这个方法很奇特吧?</td>
</tr>
<tr>
<td><code>flunk( [msg] )</code></td>
<td>确保测试会失败,用来标记测试还没编写完</td>
</tr>
</tbody>
</table>
<p>Rails 使用的测试框架完全模块化,因此可以自己编写新的断言。Rails 本身就是这么做的,提供了很多专门的断言,可以简化测试。</p><div class="note"><p>自己编写断言属于进阶话题,本文不会介绍。</p></div><h4 id="rails-提供的断言">3.5 Rails 提供的断言</h4><p>Rails 为 <code>test/unit</code> 框架添加了很多自定义的断言:
Rails adds some custom assertions of its own to the <code>test/unit</code> framework:</p>
<table>
<thead>
<tr>
<th>断言</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>assert_difference(expressions, difference = 1, message = nil) {...}</code></td>
<td>测试 <code>expressions</code> 的返回数值和代码块的返回数值相差是否为 <code>difference</code>
</td>
</tr>
<tr>
<td><code>assert_no_difference(expressions, message = nil, &amp;block)</code></td>
<td>测试 <code>expressions</code> 的返回数值和代码块的返回数值相差是否不为 <code>difference</code>
</td>
</tr>
<tr>
<td><code>assert_recognizes(expected_options, path, extras={}, message=nil)</code></td>
<td>测试 <code>path</code> 指定的路由是否正确处理,以及 <code>expected_options</code> 指定的参数是够由 <code>path</code> 处理。也就是说 Rails 是否能识别 <code>expected_options</code> 指定的路由</td>
</tr>
<tr>
<td><code>assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)</code></td>
<td>测试指定的 <code>options</code> 能否生成 <code>expected_path</code> 指定的路径。这个断言是 <code>assert_recognizes</code> 的逆测试。<code>extras</code> 指定额外的请求参数。<code>message</code> 指定断言失败时显示的错误消息。</td>
</tr>
<tr>
<td><code>assert_response(type, message = nil)</code></td>
<td>测试响应是否返回指定的状态码。可用 <code>:success</code> 表示 200-299,<code>:redirect</code> 表示 300-399,<code>:missing</code> 表示 404,<code>:error</code> 表示 500-599。状态码可用具体的数字表示,也可用相应的符号表示。详细信息参见<a href="http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant">完整的状态码列表</a>,以及状态码数字和符号的<a href="http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant">对应关系</a>。</td>
</tr>
<tr>
<td><code>assert_redirected_to(options = {}, message=nil)</code></td>
<td>测试 <code>options</code> 是否匹配所执行动作的转向设定。这个断言可以匹配局部转向,所以 <code>assert_redirected_to(controller: "weblog")</code> 可以匹配转向到 <code>redirect_to(controller: "weblog", action: "show")</code> 等。还可以传入具名路由,例如 <code>assert_redirected_to root_path</code>,以及 Active Record 对象,例如 <code>assert_redirected_to @article</code>。</td>
</tr>
<tr>
<td><code>assert_template(expected = nil, message=nil)</code></td>
<td>测试请求是否由指定的模板文件渲染</td>
</tr>
</tbody>
</table>
<p>下一节会介绍部分断言的用法。</p><h3 id="为控制器编写功能测试">4 为控制器编写功能测试</h3><p>在 Rails 中,测试控制器各动作需要编写功能测试。控制器负责处理程序接收的请求,然后使用视图渲染响应。</p><h4 id="功能测试要测试什么">4.1 功能测试要测试什么</h4><p>应该测试一下内容:</p>
<ul>
<li>请求是否成功;</li>
<li>是否转向了正确的页面;</li>
<li>用户是否通过了身份认证;</li>
<li>是否把正确的对象传给了渲染响应的模板;</li>
<li>是否在视图中显示了相应的消息;</li>
</ul>
<p>前面我们已经使用 Rails 脚手架生成了 <code>Post</code> 资源,在生成的文件中包含了控制器和测试。你可以看一下 <code>test/controllers</code> 文件夹中的 <code>posts_controller_test.rb</code> 文件。</p><p>我们来看一下这个文件中的测试,首先是 <code>test_should_get_index</code>。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PostsControllerTest < ActionController::TestCase
test "should get index" do
get :index
assert_response :success
assert_not_nil assigns(:posts)
end
end
</pre>
</div>
<p>在 <code>test_should_get_index</code> 测试中,Rails 模拟了一个发给 <code>index</code> 动作的请求,确保请求成功,而且赋值了一个合法的 <code>posts</code> 实例变量。</p><p><code>get</code> 方法会发起请求,并把结果传入响应中。可接受 4 个参数:</p>
<ul>
<li>所请求控制器的动作,可使用字符串或 Symbol;</li>
<li>可选的 Hash,指定传入动作的请求参数(例如,请求字符串参数或表单提交的参数);</li>
<li>可选的 Hash,指定随请求一起传入的会话变量;</li>
<li>可选的 Hash,指定 Flash 消息的值;</li>
</ul>
<p>举个例子,请求 <code>:show</code> 动作,请求参数为 <code>'id' => "12"</code>,会话参数为 <code>'user_id' => 5</code>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
get(:show, {'id' => "12"}, {'user_id' => 5})
</pre>
</div>
<p>再举个例子:请求 <code>:view</code> 动作,请求参数为 <code>'id' => '12'</code>,这次没有会话参数,但指定了 Flash 消息:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
get(:view, {'id' => '12'}, nil, {'message' => 'booya!'})
</pre>
</div>
<div class="note"><p>如果现在运行 <code>posts_controller_test.rb</code> 文件中的 <code>test_should_create_post</code> 测试会失败,因为前文在模型中添加了数据验证。</p></div><p>我们来修改 <code>posts_controller_test.rb</code> 文件中的 <code>test_should_create_post</code> 测试,让所有测试都通过:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "should create post" do
assert_difference('Post.count') do
post :create, post: {title: 'Some title'}
end
assert_redirected_to post_path(assigns(:post))
end
</pre>
</div>
<p>现在你可以运行所有测试,都应该通过。</p><h4 id="功能测试中可用的请求类型">4.2 功能测试中可用的请求类型</h4><p>如果熟悉 HTTP 协议就会知道,<code>get</code> 是请求的一种类型。在 Rails 功能测试中可以使用 6 种请求:</p>
<ul>
<li><code>get</code></li>
<li><code>post</code></li>
<li><code>patch</code></li>
<li><code>put</code></li>
<li><code>head</code></li>
<li><code>delete</code></li>
</ul>
<p>这几种请求都可作为方法调用,不过前两种最常用。</p><div class="note"><p>功能测试不检测动作是否能接受指定类型的请求。如果发起了动作无法接受的请求类型,测试会直接退出。</p></div><h4 id="可用的四个-hash">4.3 可用的四个 Hash</h4><p>使用上述 6 种请求之一发起请求并经由控制器处理后,会产生 4 个 Hash 供使用:</p>
<ul>
<li>
<code>assigns</code>:动作中创建在视图中使用的实例变量;</li>
<li>
<code>cookies</code>:设置的 cookie;</li>
<li>
<code>flash</code>:Flash 消息中的对象;</li>
<li>
<code>session</code>:会话中的对象;</li>
</ul>
<p>和普通的 Hash 对象一样,可以使用字符串形式的键获取相应的值。除了 <code>assigns</code> 之外,另外三个 Hash 还可使用 Symbol 形式的键。例如:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
flash["gordon"] flash[:gordon]
session["shmession"] session[:shmession]
cookies["are_good_for_u"] cookies[:are_good_for_u]
# Because you can't use assigns[:something] for historical reasons:
assigns["something"] assigns(:something)
</pre>
</div>
<h4 id="可用的实例变量">4.4 可用的实例变量</h4><p>在功能测试中还可以使用下面三个实例变量:</p>
<ul>
<li>
<code>@controller</code>:处理请求的控制器;</li>
<li>
<code>@request</code>:请求对象;</li>
<li>
<code>@response</code>:响应对象;</li>
</ul>
<h4 id="设置报头和-cgi-变量">4.5 设置报头和 CGI 变量</h4><p><a href="http://tools.ietf.org/search/rfc2616#section-5.3">HTTP 报头</a> 和 <a href="http://tools.ietf.org/search/rfc3875#section-4.1">CGI 变量</a>可以通过 <code>@request</code> 实例变量设置:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
# setting a HTTP Header
@request.headers["Accept"] = "text/plain, text/html"
get :index # simulate the request with custom header
# setting a CGI variable
@request.headers["HTTP_REFERER"] = "http://example.com/home"
post :create # simulate the request with custom env variable
</pre>
</div>
<h4 id="测试模板和布局">4.6 测试模板和布局</h4><p>如果想测试响应是否使用正确的模板和布局渲染,可以使用 <code>assert_template</code> 方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "index should render correct template and layout" do
get :index
assert_template :index
assert_template layout: "layouts/application"
end
</pre>
</div>
<p>注意,不能在 <code>assert_template</code> 方法中同时测试模板和布局。测试布局时,可以使用正则表达式代替字符串,不过字符串的意思更明了。即使布局保存在标准位置,也要包含文件夹的名字,所以 <code>assert_template layout: "application"</code> 不是正确的写法。</p><p>如果视图中用到了局部视图,测试布局时必须指定局部视图,否则测试会失败。所以,如果用到了 <code>_form</code> 局部视图,下面的断言写法才是正确的:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "new should render correct layout" do
get :new
assert_template layout: "layouts/application", partial: "_form"
end
</pre>
</div>
<p>如果没有指定 <code>:partial</code>,<code>assert_template</code> 会报错。</p><h4 id="完整的功能测试示例">4.7 完整的功能测试示例</h4><p>下面这个例子用到了 <code>flash</code>、<code>assert_redirected_to</code> 和 <code>assert_difference</code>:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
test "should create post" do
assert_difference('Post.count') do
post :create, post: {title: 'Hi', body: 'This is my first post.'}
end
assert_redirected_to post_path(assigns(:post))
assert_equal 'Post was successfully created.', flash[:notice]
end
</pre>
</div>
<h4 id="测试视图">4.8 测试视图</h4><p>测试请求的响应中是否出现关键的 HTML 元素和相应的内容是测试程序视图的一种有效方式。<code>assert_select</code> 断言可以完成这种测试,其句法简单而强大。</p><div class="note"><p>你可能在其他文档中见到过 <code>assert_tag</code>,因为 <code>assert_select</code> 断言的出现,<code>assert_tag</code> 现已弃用。</p></div><p><code>assert_select</code> 有两种用法:</p><p><code>assert_select(selector, [equality], [message])</code> 测试 <code>selector</code> 选中的元素是否符合 <code>equality</code> 指定的条件。<code>selector</code> 可以是 CSS 选择符表达式(字符串),有代入值的表达式,或者 <code>HTML::Selector</code> 对象。</p><p><code>assert_select(element, selector, [equality], [message])</code> 测试 <code>selector</code> 选中的元素和 <code>element</code>(<code>HTML::Node</code> 实例)及其子元素是否符合 <code>equality</code> 指定的条件。</p><p>例如,可以使用下面的断言检测 <code>title</code> 元素的内容:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
assert_select 'title', "Welcome to Rails Testing Guide"
</pre>
</div>
<p><code>assert_select</code> 的代码块还可嵌套使用。这时内层的 <code>assert_select</code> 会在外层 <code>assert_select</code> 块选中的元素集合上运行断言:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
assert_select 'ul.navigation' do
assert_select 'li.menu_item'
end
</pre>
</div>
<p>除此之外,还可以遍历外层 <code>assert_select</code> 选中的元素集合,这样就可以在集合的每个元素上运行内层 <code>assert_select</code> 了。假如响应中有两个有序列表,每个列表中都有 4 各列表项,那么下面这两个测试都会通过:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
assert_select "ol" do |elements|
elements.each do |element|
assert_select element, "li", 4
end
end
assert_select "ol" do
assert_select "li", 8
end
</pre>
</div>
<p><code>assert_select</code> 断言很强大,高级用法请参阅<a href="http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html">文档</a>。</p><h5 id="其他视图相关的断言">4.8.1 其他视图相关的断言</h5><p>There are more assertions that are primarily used in testing views:</p>
<table>
<thead>
<tr>
<th>断言</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>assert_select_email</code></td>
<td>检测 Email 的内容</td>
</tr>
<tr>
<td><code>assert_select_encoded</code></td>
<td>检测编码后的 HTML,先解码各元素的内容,然后在代码块中调用每个解码后的元素</td>
</tr>
<tr>
<td>
<code>css_select(selector)</code> 或 <code>css_select(element, selector)</code>
</td>
<td>返回由 <code>selector</code> 选中的所有元素组成的数组,在后一种用法中,首先会找到 <code>element</code>,然后在其中执行 <code>selector</code> 表达式查找元素,如果没有匹配的元素,两种用法都返回空数组</td>
</tr>
</tbody>
</table>
<p>下面是 <code>assert_select_email</code> 断言的用法举例:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
assert_select_email do
assert_select 'small', 'Please click the "Unsubscribe" link if you want to opt-out.'
end
</pre>
</div>
<h3 id="集成测试">5 集成测试</h3><p>集成测试用来测试多个控制器之间的交互,一般用来测试程序中重要的工作流程。</p><p>与单元测试和功能测试不同,集成测试必须单独生成,保存在 <code>test/integration</code> 文件夹中。Rails 提供了一个生成器用来生成集成测试骨架。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails generate integration_test user_flows
exists test/integration/
create test/integration/user_flows_test.rb
</pre>
</div>
<p>新生成的集成测试如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
# test "the truth" do
# assert true
# end
end
</pre>
</div>
<p>集成测试继承自 <code>ActionDispatch::IntegrationTest</code>,因此可在测试中使用一些额外的帮助方法。在集成测试中还要自行引入固件,这样才能在测试中使用。</p><h4 id="集成测试中可用的帮助方法">5.1 集成测试中可用的帮助方法</h4><p>除了标准的测试帮助方法之外,在集成测试中还可使用下列帮助方法:</p>
<table>
<thead>
<tr>
<th>帮助方法</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>https?</code></td>
<td>如果模拟的是 HTTPS 请求,返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>https!</code></td>
<td>模拟 HTTPS 请求</td>
</tr>
<tr>
<td><code>host!</code></td>
<td>设置下次请求使用的主机名</td>
</tr>
<tr>
<td><code>redirect?</code></td>
<td>如果上次请求是转向,返回 <code>true</code>
</td>
</tr>
<tr>
<td><code>follow_redirect!</code></td>
<td>跟踪一次转向</td>
</tr>
<tr>
<td><code>request_via_redirect(http_method, path, [parameters], [headers])</code></td>
<td>发起一次 HTTP 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>post_via_redirect(path, [parameters], [headers])</code></td>
<td>发起一次 HTTP POST 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>get_via_redirect(path, [parameters], [headers])</code></td>
<td>发起一次 HTTP GET 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>patch_via_redirect(path, [parameters], [headers])</code></td>
<td>发起一次 HTTP PATCH 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>put_via_redirect(path, [parameters], [headers])</code></td>
<td>发起一次 HTTP PUT 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>delete_via_redirect(path, [parameters], [headers])</code></td>
<td>发起一次 HTTP DELETE 请求,并跟踪后续全部转向</td>
</tr>
<tr>
<td><code>open_session</code></td>
<td>创建一个新会话实例</td>
</tr>
</tbody>
</table>
<h4 id="集成测试示例">5.2 集成测试示例</h4><p>下面是个简单的集成测试,涉及多个控制器:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
fixtures :users
test "login and browse site" do
# login via https
https!
get "/login"
assert_response :success
post_via_redirect "/login", username: users(:david).username, password: users(:david).password
assert_equal '/welcome', path
assert_equal 'Welcome david!', flash[:notice]
https!(false)
get "/posts/all"
assert_response :success
assert assigns(:products)
end
end
</pre>
</div>
<p>如上所述,集成测试涉及多个控制器,而且用到整个程序的各种组件,从数据库到调度程序都有。而且,在同一个测试中还可以创建多个会话实例,还可以使用断言方法创建一种强大的测试 DSL。</p><p>下面这个例子用到了多个会话和 DSL:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
require 'test_helper'
class UserFlowsTest < ActionDispatch::IntegrationTest
fixtures :users
test "login and browse site" do
# User david logs in
david = login(:david)
# User guest logs in
guest = login(:guest)
# Both are now available in different sessions
assert_equal 'Welcome david!', david.flash[:notice]
assert_equal 'Welcome guest!', guest.flash[:notice]
# User david can browse site
david.browses_site
# User guest can browse site as well
guest.browses_site
# Continue with other assertions
end
private
module CustomDsl
def browses_site
get "/products/all"
assert_response :success
assert assigns(:products)
end
end
def login(user)
open_session do |sess|
sess.extend(CustomDsl)
u = users(user)
sess.https!
sess.post "/login", username: u.username, password: u.password