-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnote
More file actions
executable file
·1492 lines (1137 loc) · 44.4 KB
/
Copy pathnote
File metadata and controls
executable file
·1492 lines (1137 loc) · 44.4 KB
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
# http://www.cnblogs.com/kuihua/p/5505621.html
# http://selenium-python.readthedocs.io/index.html
# 扩展作业
# 原版是1.11,书籍是1.7
yum install firefox
git version
pip3 -V
python3 -V
# python 3.4.2 -> django 1.7
# 版本间需要匹配 14年9月1.7
# sudo pip3 install django==1.7
# sudo pip3 install --upgrade selenium
pip install "django<1.12" "selenium<4"
使用功能测试协助安装Django
# 遵从测试山羊的教诲,没有测试什么也别做
vi functional_test.py
from selenium import webdriver
browser = webdriver.Firefox()
browser.get('http://localhost:8000')
assert 'Django' in browser.title
python !$
# 基础测试的写法
# 让Django运行起来
mkdir tdd_django
django-admin.py startproject superlists .
#不加点会有2个类似的目录
python manage.py runserver
python functional_test.py
# 创建Git仓库
mv functional_test.py superlists/
cd superlists/
git init .
echo "db.sqlite3" >> .gitignore
echo "geckodriver.log" >> .gitignore
echo "virtualenv" >> .gitignore
git add .
git status
git rm -r --cached superlists/__pycache__
echo "__pycache__" >> .gitignore
echo "*.pyc" >> .gitignore
git add .gitignore
git commit
使用unittest模块扩展功能测试
# 使用功能测试驱动开发一个最简可用的应用
python manage.py runserver
python functional_test.py
# Python标准库中的unittest模块
assert'To-Do' in browser.title, "Browser title was " + browser.title
# 显示标题内容
vi functional_test.py
# 修改为类的形式
# 继承测试类,test_开头,setUp,tearDown测试前后,self.xx断言,self.fail提示,__main__方法,
# https://docs.python.org/3/library/unittest.html LiveServerTestCase
# 隐式等待
# self.browser.implicitly_wait(3)
# 显示未定义,待调整
# 提交
git diff
git commit -a
使用单元测试测试简单的首页
# 第一个Django应用,第一个单元测试
# 以应用的方式组织代码,一个项目中放多个应用
python manage.py startapp lists
# 单元测试及其与功能测试的区别
# 功能测试,用户角度,外部
# 单元测试,开发角度,内部
# 先写功能,用户角度描述功能,功能测试失败后,编写代码让它通过
# 单元测试失败后,最少代码让其通过,再功能测试
# 其实不是很明白
# Django中的单元测试
vi lists/tests.py
python manage.py test
git status
vim s
git add lists/
git diff --staged
git commit -m "Add app for lists, with deliberately failing unit test"
# Django中的MVC、URL 和视图函数
https://docs.djangoproject.com/en/1.11/faq/general/
# url-http进入,决定某个视图函数处理,视图函数处理请求,返回响应
# 测试内容:
# 能否解析 /,对应到某个函数
# 能否让视图返回一些HTML
vi lists/tests.py
python manage.py test
# 内置函数 resolve
# home_page 定义的函数
# 终于可以编写一些应用代码了
vi lists/views.py
python manage.py test
# 阅读调用跟踪
# 先查看调试的最底端,错误本身
# 调试开头说明哪个测试失败了
# 接着是导致失败的示例代码
# urls.py
# main urls.py 主urls
vi superlists/urls.py
# url(r'^$', 'superlists.views.home', name='home'),
url(r'^$', 'lists.views.home_page', name='home'),
vi lists/views.py
# home_page=None
def home_page():
pass
# 排查历程:
# ImportError: cannot import name 'home_page' #导入未定义函数 views
# raise Resolver404({'tried': tried, 'path': new_path}) #找不到url映射 urls
# Could not import superlists.views.home. Parent module superlists.views does not exist. #视图不存在urls
# Could not import lists.views.home. View does not exist in module lists.views # 函数名错误
# Could not import lists.views.home_page. View is not callable #home_page无法调用
# 第一个测试通过,排错结束
# 为视图编写单元测试
# 新的测试函数
vi lists/tests.py
# 创建了一个HttpRequest对象, 请求网页时,看到的就是这个对象
# HttpRequest对象传给home_page视图,得到响应
# 判断响应的.content属性,原始字节 html,/html,To-Do
# https://docs.djangoproject.com/en/1.7/topics/python3/
# 运行单元测试,改动少量代码
# 我决定装个新版,和网页一致 ok:
# 填加参数
python manage.py test
# TypeError: home_page() takes 0 positional arguments but 1 was given
vi lists/views.py
# 填加返回值
python manage.py test
# html = response.content.decode('utf8')
# AttributeError: 'NoneType' object has no attribute 'content'
vi lists/views.py
# 返回html标签头部
# self.assertTrue(html.startswith('<html>'))
# AssertionError: False is not true
# 返回标题
# AssertionError: '<title>To-Do lists</title>' not found in '<html>'
# 返回html尾部
# self.assertTrue(html.endswith('</html>'))
# AssertionError: False is not true
python manage.py test
# OK
python functional_tests.py
# AssertionError: Finish the test!
# 编写这些测试有什么用
# 编程就像从井里打水
# 使用Selenium测试用户交互
python functional_tests.py
# 扩充功能测试
vi functional_tests.py
# find_element_by_tag_name 找不到报错
# find_elements_by_tag_name 找不到反回空组
# find_element_by_id
# 还使用了keys类, 及any函数
python functional_tests.py
# Message: Unable to locate element: h1
# 遵守“不测试常量”规则,使用模板解决这个问题
# 使用模版重构
# https://refactoring.com/ 重构
python manage.py test
mkdir lists/templates
touch !$/home.html
vi lists/views.py
# from django.shortcuts import render
# HttpResponse 改成 render,输入参数,模板名
python functional_tests.py
# django.template.base.TemplateDoesNotExist: home.html
# 注册应用
vi superlists/settings.py
# 在INSTALLED_APPS 添加lists
python manage.py test
self.assertTrue(html.endswith('</html>'))
AssertionError: False is not true
# 转用模板后在响应尾引入的额外空行 增加strip()
# 理论上可以用 render_to_string 更新测试用例,但是失败了?
# 关于重构
# 接着修改首页
vi lists/templates/home.html
# 增加h1
# Message: Unable to locate element: [id="id_new_item"]
# 增加输入框
# AssertionError: '' != 'Enter a to-do item'
# 增加占位文字
# Message: Unable to locate element: [id="id_list_table"]
# 写成表格的形式
# any(row.text == '1: Buy peacock feathers' for row in rows) AssertionError: False is not true
vi functional_tests.py
# 修改功能测试,看到写入的备注,测试结束
# 总结:TDD流程
保存用户输入
# 编写表单,发送POST请求
# 提交表单
vi lists/templates/home.html
#<form method="POST">
# <input name="item_text" id="id_new_item" />
# 面对面调试
python functional_tests.py
# 错误无法追踪,打印变量,改进错误,手动访问,延时
# 开放访问权限
# 我用的是headless的方式访问,打算修改配置让其他主机访问.
# 发现被django限制了, 开发限制的方法如下:
vi superlists/settings.py
# ALLOWED_HOSTS = ['*'], 测试环境使用,生产请输入准确ip
# CSRF错误
# 打开页面后看到了著名的403 Forbidden, CSRF认证失败
# http://www.cl.cam.ac.uk/~rja14/book.html
# 模板标签
vi lists/templates/home.html
{% csrf_token %}
# 更新模板加入token标签
# 在服务器中处理POST请求
# 新的单元测试
# 因为还没指定action属性,提交表单后默认返回之前渲染的页面"/"
# 需要修改视图函数,先写一个新的单元测试
vi lists/tests.py
def test_can_save_a_POST_request()
# 想办法通过测试
vi lists/views.py
python manage.py test
# 修改判断再测试
# 把Python变量传入模板中渲染
# 变量导入模板
vi lists/templates/home.html
# <table id="id_list_table">
# <tr><td>{{ new_item_text }}</td></tr>
# </table>
# 测试传入值
vi lists/tests.py
python manage.py test
AssertionError: No templates used to render the response
# 更新视图函数
vi lists/views.py
python manage.py test
# 回归
django.utils.datastructures.MultiValueDictKeyError: "'item_text'"
vi lists/views.py
{
'new_item_text': request.POST.get('item_text', ''),
}
# https://docs.python.org/3/library/stdtypes.html#dict.get
python superlists/functional_tests.py
# AssertionError: False is not true : New to-do item did not appear in table
# 改进错误消息
# 上节报的错误看不出太多内容,更新一下
vi functional_tests.py
f"New to-do item did not appear in table. Contents were:\n{table.text}"
# 需要学习一下f的用法 格式字符串迭代?
# https://docs.python.org/3/whatsnew/3.6.html#pep-498-formatted-string-literals
# 调整测试方法
python superlists/functional_tests.py
# AssertionError: False is not true : New to-do item did not appear in table.
vi functional_tests.py
# self.assertIn('1: Buy peacock feathers', [row.text for row in rows])
AssertionError: '1: Buy peacock feathers' not found in ['Buy peacock feathers']
# 作弊
vi lists/templates/home.html
# <tr><td>1: {{ new_item_text }}</td></tr>
# 修改模板,把1独立出来,骗过了测试
vi functional_tests.py
python superlists/functional_tests.py
# 增加第二条记录,测试报错
# 事不过三,三则重构
# 重复三次的代码需要重构
vi superlists/functional_tests.py
# 增加函数check_for_row_in_list_table()
# 增加第二条记录的测试代码,使用新建测试函数测试清单显示
# 到此为止,发现需要持久化储存
#建一个新的测试类,记得导入Item模块
vi lists/tests.py
#class ItemModelTest(TestCase):
# ...
# 创建对象,属性赋值,save(), .objects()方法, .count()方法
# https://docs.djangoproject.com/en/1.11/intro/tutorial01/
# 单元测试,集成测试,单元测试不涉及数据库?
python manage.py test
# ImportError: cannot import name 'Item'
vi lists/models.py
#建一个Item类,这次不写Item=None了?
python manage.py test
# AttributeError: 'Item' object has no attribute 'save'
vi lists/models.py
#报没有save属性,object改为继承model类?
# Django ORM和第一个模型
#Object-Relational Mapper ORM
# 第一个数据库迁移
django.db.utils.OperationalError: no such table: lists_item
#之前修改之后报错,没有表 lists_item
python manage.py makemigrations
ls lists/migrations
# 可以查看一下,新生成的文件,自动生成了建表语句
#额外扩展,查看sqlite数据库
ls db.sqlite3
sqlite3 db.sqlite3
.tables
.schema --indent
select * from xxx;
.dump xxx
.quit
# 也可以在命令行里创建表,不过不是本次的讨论内容.
# 测试向前走得挺远
# 再次测试
# AttributeError: 'Item' object has no attribute 'text'
# 之前测试内容,保存2个代办事项,检查是否存入数据库
# 为什么存入数据库的时候没有报错?
# id做为表的主键是自动的,其他需要自己指定
# https://docs.djangoproject.com/en/1.11/intro/tutorial01/#creating-models
# https://docs.djangoproject.com/en/1.11/ref/models/fields/
vi lists/models.py
text = models.TextField()
# 补充对text的定义,再测试
# django.db.utils.OperationalError: no such column: lists_item.text
# 原因,添加了新字段需要再进行一次测试
# 添加新字段就要创建新迁移
python manage.py makemigrations
# 提示选2,不容许添加没有默认值的字段,退出修改?
vi lists/models.py
text = models.TextField(default='')
python manage.py makemigrations
python manage.py test
# 把POST请求中的数据存入数据库
# 修改测试方法 test_can_save_a_POST_request()
vi lists/tests.py
# objects.count() 等价于 objects.all().count()
# objects.first() 等价于 objects.all()[0]
# 再测试
# self.assertEqual(Item.objects.count(), 1) AssertionError: 0 != 1
# 据说这个故障是期望值,目前不了解,回头再看
# 修改视图 home_page()方法
vi lists/views.py
# 修改视图函数,之前直接显示在模板上,现在先存入数据库,再显示
python manage.py test
# 再测试,通过
# 对views做一些重构
vi lists/views.py
# 把之前直接使用提交的值修改为前面定义的变量,测试通过
# 需求增加
# 不要保存空白,POST请求测试太长,显示多个待办事项,支持多个清单,但我还没想好怎么做, 先处理第一个问题吧
# 处理空值
vi lists/tests.py
# 新建测试模块 test_only_saves_items_when_necessary()
vi lists/views.py
# 修改视图函数,有POST方法就写入数据库,没有就置空
python manage.py test
# 处理完POST请求后重定向
# 修改测试模块test_can_save_a_POST_request()
# 待分析重看,被作者坑了,这块源码也测试不过,跟着改
python manage.py test
# AssertionError: 'A new list item' not found in ''
vi lists/tests.py
# 修改了test文件,测试模块分为两个,删除content内容,测试通过
# 一个测试一件事,处理请求一个测试,重定向一个测试
# 第二个小需求,POST的测试太长解决
# 在模板中渲染待办事项
# 测试显示多个项目,建立一个多项目的测试,并修改模板和视图文件
vi lists/tests.py
# test_displays_all_list_items()
vi lists/templates/home.html
# {% for item in items %}
# <tr><td>1: {{ item.text }}</td></tr>
# {% endfor %}
# <tr><td>1: {{ new_item_text }}</td></tr> x
# 还是写错了,哪怕抄了一次,不是自己写的东西没有回应
# 建立多记录的测试, 修改模板, 修改视图函数添加变量,函数
# 在模板中使用遍历句法
#https://docs.djangoproject.com/en/1.11/topics/templates/
python manage.py test #通过
python functional_tests.py #失败
# 使用迁移创建生产数据库
# 功能测试失败,没有相应的表,因为测试程序是自己建的表
vi python manage.py test
https://docs.djangoproject.com/en/1.11/ref/settings/#databases
# 查看django中的数据库配置及相关的django文档
python manage.py migrate
# 正式生成数据库结构的命令
python functional_tests.py
# 代码还是要目测啊,多了一个 row.text都没发现
AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Buy peacock feathers', '1: Use peacock feathers to make a fly']
# 修改序号问题,还是在模板,看来用好模版很重要
python manage.py migrate
python functional_tests.py
vi lists/templates/home.html
rm db.sqlite3
python3 manage.py migrate --noinput
python functional_tests.py
# 完成最简可用的网站
# 确保功能测试之间相互隔离
# 修改功能测试的位置
mkdir functional_tests
touch functional_tests/__init__.py
git mv functional_tests.py functional_tests/tests.py
git status # shows the rename to functional_tests/tests.py and __init__.py
python3 manage.py test functional_tests
from django.test import LiveServerTestCase
# class NewVisitorTest(LiveServerTestCase):
# 使用 self.live_server_url 代替 self.live_server_url
# 删除__main__代码段,删除导入 unittest,神奇
git diff --staged -M
# 只运行单元测试
python manage.py test
python manage.py test lists
# https://github.com/mozilla/geckodriver/releases
vi functional_tests/tests.py
# time.sleep
# https://martinfowler.com/articles/nonDeterminism.html
# def wait_for_row_in_list_table()
# 用一个超时函数来替代睡眠的设定
# 设10秒超时,设循环,代入老的方法,成功则返回
# 设定故障捕捉,两个时间相减,超时报警,不超时休眠
# 测试一下,是否正确把刚才新增函数的row_tex变量改为foo,测试失败
# AssertionError: 'foo' not found in ['1: Buy peacock feathers']
# 再修改一下查找的id_new_item,
# Message: Unable to locate element: [id="id_nothing"]
# 必要时做少量的设计
# 分析一下,最简单情况下需要的功能,确定使用哪种设定方式
# 迷你产品分析,场景
# 每个用户有自己的清单,清单有多个事项,需要保存清单,需要唯一URL
# YAGNI 你不需要这个
# REST 表现层状态转化
# https://www.obeythetestinggoat.com/book/appendix_rest_api.html
/lists/new
/lists/<list identifier>/add_item
# 使用TDD 实现新设计
# 修改功能测试
# 细化场景,提交代办事项后,跳转新页面,相应修改测试函数
vi functional_tests/tests.py
# 新增test_multiple_users_can_start_lists_at_different_urls函数,定义了跳转,
# 定义了多个场景,新用户,看不见之前的清单,提交,看到自己的,看不到之前的
# https://docs.python.org/3/library/unittest.html
# 逐步迭代,实现新设计
# URL出现在重定向POST请求之后
vi lists/tests.py
# 修改test_redirects_after_POST()
# 写一个假设值 /lists/the-only-list-in-the-world/
# 测试下lists看看,故障再修改视图试一下
vi lists/views.py
# 修改了视图输出后,过了,但功能测试又不过了,
# 为每一个清单添加唯一的URL和标识符?
# 使用Django测试客户端一起测试视图、模板和URL
# 一个新测试类
# 使用self.client属性来测试客户端, 屏蔽细节
# AssertionError: 404 != 200 : Couldn't retrieve content: Response code was 404
# 一个新URL
# 设置唯一清单的URL
vi superlists/urls.py
# 设置路由,指向view_list函数,测试报错,没有这个函数
# AttributeError: module 'lists.views' has no attribute 'view_list'
# 一个新视图函数
vi lists/views.py
# ValueError: The view lists.views.view_list didn't return an HttpResponse object. It returned None instead
# 又玩pass这个梗,果断报错
# 把之前输出的代码复制过来,测试,通过了
vi lists/views.py
python manage.py test lists/
python manage.py test functional_tests/
# AssertionError: 'Buy peacock feathers' unexpectedly found in 'Your To-Do list\n1: Buy peacock feathers'
# 功能测试仍然失败
vi lists/templates/home.html
<form method="POST" action="/">
# 之前的POST操作后,并不需要变更地址,我们设了特殊的URL后
# 提交完,地址就变成设定的地址了,修改,测试通过
# 修改后,提交完,返回'/'路径,报了之前遇到的错误,测试完成
# AssertionError: 'Buy peacock feathers' unexpectedly found in 'Your To-Do list\n1: Buy peacock feathers'
# 重构
#删除 test_displays_all_list_items()测试模块
vi lists/tests.py
python manage.py test lists/
# 一个新模板,用于查看清单
# 测试成功后,我们发现需要一个新的页面,来显待办事项,首页只显示输入框
# 先写个新的测试来测新的模模板
vi lists/tests.py
python manage.py test lists/
# AssertionError: No templates used to render the response
# ok 可以修改禝和模板了
vi lists/views.py
# home.html改成 list.html
# django.template.exceptions.TemplateDoesNotExist: list.html
touch lists/templates/list.html
# AssertionError: False is not true : Couldn't find 'itemey 1' in response
# 放了一个空模板,开始报找不到的错误了
cp lists/templates/home.html lists/templates/list.html
# 删除首页的表格显示部分,只保留输入提交
# AssertionError: No templates used to render the response
# 测试报错,要特别注意redirect之类测试的'/'问题
# AssertionError: '1: Buy milk' not found in ['1: Buy peacock feathers', '2: Buy
# # milk'
# 单元测试成功,功能测试报上述错误,与期望值相符
# 用于添加待办事项的URL和视图
# 用来测试新建清单的测试类
vi lists/tests.py
# 打开lists/tests.py把 test_can_save_a_POST_request test_redirects_after_POST移到新的测试类
# 使用新方法 assertRedirects 替换掉两条语句
# 不但移动了,内容还改变了,这一点记录失败
python manage.py test lists/
# AssertionError: 404 != 302 : Response didn't redirect as expected: Response code was 404 (expected 302)
# self.assertEqual(Item.objects.count(), 1)
# AssertionError: 0 != 1
# 报错表明,路由失败,提交也失败,下面开始恶意构建相关内容
# 用于新建清单的URL和视图
# 构建新的路由,并测试
vi superlists/urls.py
# AttributeError: module 'lists.views' has no attribute 'new_list'
修改视图
vi lists/views.py
# ValueError: The view lists.views.new_list didn't return an HttpResponse object. It returned None instead.
# 似曾相识的故障,
vi lists/views.py
# return redirect 跳转位置
# 还是要注意符号啊,'/'又一次被坑了,手打比复制好很多
# self.assertEqual(Item.objects.count(), 1)
# AssertionError: 0 != 1
python manage.py test lists/
python manage.py test functional_tests/
# 测试通过,期望达成
# 删除当前多余的代码和测试
vi lists/views.py
python manage.py test lists/
# 删除主页之前判断是POST就提交跳转代码
vi lists/views.py
# 删除测试 test_only_saves_items_when_necessary()
# 让表单指向刚添加的新URL
# 功能测试失败,继续修改模板
python manage.py test functional_tests/
# milk不是feathers,期望达成,测试内容就是不能看到别人的待办事项
# 调整模型
vi lists/tests.py
# from lists.models import Item, List
# 导入list模块,并插入相关语句,测试,报找不到list模块
# ImportError: cannot import name 'List'
vi lists/models.py
class List(object)
# AttributeError: 'List' object has no attribute 'save'
vi lists/models.py
class List(models.Model):
# django.db.utils.OperationalError: no such table: lists_list
vi lists/models.py
# 并不需要设 text 属性,对moudle的了解还不够
python3 manage.py makemigrations
# AttributeError: 'Item' object has no attribute 'list'
# 通过外键实现的关联
vi lists/models.py
list = models.TextField(default='')
python3 manage.py test lists
# django.db.utils.OperationalError: no such column: lists_item.list
python3 manage.py makemigrations
# NameError: name 'List' is not defined
# 位置换一下才行,执行有先后次序的
# AssertionError: 'List object' != <List: List object>
vi lists/models.py
# list = models.ForeignKey(List, default=None)
rm lists/migrations/0004_item_list.py
python3 manage.py makemigrations
# 抄text的果然还是不行,结果要删除重做
# 根据新模型定义调整其他代码
# django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id
# 测试后多处报错,说明老的测试模块不能适应每个待办事项拥有独立url的需要
vi lists/tests.py
# File "/tmp/book-example/lists/views.py", line 9, in new_list
# Item.objects.create(text=request.POST['item_text'])
# 把 list_ 写入test_displays_all_items(),测试后发现新的报错
vi lists/views.py
# 视力导入list模块,并插入相关语句后,测试通过
# 每个列表都应该有自己的URL
vi ists/tests.py
# 修改 ListViewTest 类下的测试函数
# List.objects.create() 的使用,还不是很熟悉
python3 manage.py test lists
# AssertionError: 404 != 200 : Couldn't retrieve content: Response code was 404 (expected 200)
# AssertionError: No templates used to render the response
# 捕获URL中的参数
vi superlists/urls.py
url(r'^lists/(.+)/$', views.view_list, name='view_list'),
# 学习参数从URL传向视图的方法,虽然现在看到的并不是正确的方法
# TypeError: view_list() takes 1 positional argument but 2 were given
vi lists/views.py
def view_list(request, list_id):
# 根据报错,手工添加一个参数
# AssertionError: 1 != 0 : Response should not contain 'other list item 1'
# 期望达成,继续下一部分
vi lists/views.py
# 修改视图函数,过滤所需值 list_id
# 按照新设计调整new_list视图
# ERROR: test_redirects_after_POST (lists.tests.NewListTest)
# ValueError: invalid literal for int() with base 10:
# 'the-only-list-in-the-world'
vi lists/tests.py
# 修改 new_list 函数的跳转指向
# ValueError: invalid literal for int() with base 10: '{list_.id}'
# self.client.get(f'/lists/{list_.id}/') 少了个f,而且对f的用法还是不太会用
# 功能测试同之前一样,修改顺利
# 还需要一个视图,把待办事项加入现有清单
# 先写两个测试函数,一个测试提交,一个测试跳转
vi lists/tests.py
# 小心霸道的正则表达式
# AssertionError: 301 != 302 : Response didn't redirect as expected: Response code was 301 (expected 302)
# 期望值是404,为什么会出现301
vi superlists/urls.py
# 修改正则,使用 \d+ 替换掉 .+
# AssertionError: 0 != 1
# AssertionError: 404 != 302 : Response didn't redirect as expected: Response
# code was 404 (expected 302)
# 再测试,报错更新
# 最后一个新URL
vi superlists/urls.py
# 添加 add_item 的相关路由,测试
# AttributeError: module 'lists.views' has no attribute 'add_item'
# 最后一个新视图
vi lists/views.py
def add_item(request):
# 新建 add_item 视图
# TypeError: add_item() takes 1 positional argument but 2 were given
vi lists/views.py
add_item(request, list_id)
# ValueError: The view lists.views.add_item didn't return an HttpResponse object. It returned None instead.
# 把new_list 和 view_list 的部分代码复制过来
# self.assertEqual(Item.objects.count(), 1) AssertionError: 0 != 1
# 写入POST语句,测试通过
# 如何在表单中使用那个URL
vi lists/templates/list.html
# 修改模版,添加POST跳转
# <form method="POST" action="/lists/{{list.id}}/add_item">
vi lists/tests.py
test_passes_correct_list_to_template(self)
# KeyError: 'list'
# 我们还没有把 list 打入模版
vi lists/views.py
# 修改 view_list() 函数
# AssertionError: False is not true : Couldn't find 'itemey 1' in response
vi lists/templates/list.html
{% for item in list.item_set.all %}
# 修改模版的遍历方法
# https://docs.djangoproject.com/en/1.11/topics/db/queries/#following-relationships-backward
# 使用URL 引入做最后一次重构
# 重构URL
cp superlists/urls.py lists/
vi superlists/urls.py
# 把 superlists/urls.py 复制到 lists目录去,修改原位置的 urls文件
# 使用 include 方法引入lists 的urls文件
vi lists/urls.py
# 子 urls 文件继承项目传过来的url变量,分级处理,原lists部分域名省去
python3 manage.py test
# 测试通过,开发完成
git diff --staged
python3 manage.py migrate
# python3 manage.py migrage
第二部分 Web开发要素
美化网站布局、样式及测试方法
# 如何在功能测试中测试布局和样式
# 重写一个功能测试
vi functional_tests/tests.py
def test_layout_and_styling(self):
# AssertionError: 106.5 != 512 within 10 delta
# 设定一个窗口看是否居中,结果不居中
# 想办法调试过去
vi lists/templates/home.html
# <p style="text-align: center;">
# 证明测试有效
# 继续编写测试仍然失败
vi functional_tests/tests.py
# 去除模板上的p标记,改用代码处理
# 使用CSS框架美化网站
# http://getbootstrap.com/ css框架
wget -O bootstrap.zip https://github.com/twbs/bootstrap/releases/download/v3.3.4/bootstrap-3.3.4-dist.zip
unzip bootstrap.zip
mkdir lists/static
mv bootstrap-3.3.4-dist lists/static/bootstrap
rm bootstrap.zip
# https://getbootstrap.com/docs/3.3/getting-started/
# https://coding.smashingmagazine.com/2013/03/customizing-bootstrap/
# 需要在模板中嵌入相关信息来使用bootstrap
# Django模板继承
# 现在已经两个模板了,不希望每个文件都做大量相同改动
# 使用模版继承
diff lists/templates/home.html lists/templates/list.html
# 头部文本不一样,提交地址不一样,一个模板有表单
cp lists/templates/home.html lists/templates/base.html
vi !$
# 定义好块, 再修改原来的两个模板
vi lists/templates/base.html
vi lists/templates/home.html
# {% bloke_x header_text %} 注意拼写错误
vi lists/templates/list.html
python manage.py test functional_tests/
# 恢复到继承之前状态,本节结束
git diff -b
# 集成Bootstrap
vi lists/templates/base.html
# 头部集成,不是很明白,先照着做
# 行和列
vi lists/templates/base.html
# container 定义固定宽度
# row 定义列
# col-md-6 col-md-offset-3
# 中屏6列,右侧偏移3
# https://getbootstrap.com/docs/3.3/getting-started/
# Django中的静态文件
# 处理静态文件时需要知道,如何区分,文件位置
vi superlists/settings.py
STATIC_URL = '/static/'
# django会在各应用目录中找 static 目录
vi lists/templates/base.html
ls lists/static/bootstrap/css/bootstrap.min.css
# 注意,lists后面的部分全部要写进去,前面是应用名
# 换用StaticLiveServerTestCase
python manage.py test functional_tests/
vi functional_tests/tests.py
# 测试通过,但是值有点不对,不知道是css的问题,还是无头浏览器的问题,回头修正吧 原因遗漏</head>
# https://code.djangoproject.com/ticket/21227
# 使用Bootstrap中的组件改进网站外观
# 超大文本块
vi lists/templates/base.html
# col-md-6 col-md-offset-3 jumbotron
# 大型输入框
vi lists/templates/base.html
# class="form-control input-lg"
# 样式化表格
vi lists/templates/list.html
# <table id="id_list_table" class="table">
# 使用自己编写的CSS
vi lists/templates/base.html
# 插入一个link
vi lists/static/base.css
# 定义一个属性,分离配置的写法,值注意,框中
# https://coding.smashingmagazine.com/2013/03/customizing-bootstrap/
python manage.py test functional_tests
# 补遗:collectstatic命令和其他静态目录
# 在生产上,不会使用django伺服静态文件,效率太低
# 使用Nginx的性能更好
# 目标,静态文件放置与版本库之外
vi superlists/settings.py
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
python manage.py collectstatic
ls static
vi superlists/settings.py
# 暂时注销管理后台
rm -rf static/
python manage.py collectstatic --noinput
git diff
echo /static >> .gitignore
# 没谈到的话题
# 使用LESS定制Bootstrap
# 使用{% static %} template tag
# 使用npm 和 bower
使用过渡网站测试部署
# obeythetestinggoat@gmail.com
# 这一节,书和原文的差异比较大,书上的内容会做为注释标注
# 列表:
# 修改功能测试
# 假设服务器
# git代码上传
# Django过渡域名部署
# python虚拟环境
# 使用功能测试
# Gunicorn,Upstart,套接字
# 自动化脚本
# 生产部署
# TDD以及部署的危险区域
# 一如既往,先写测试
vi functional_tests/tests.py
# LiveServerTestCase 的缺陷之一,总假设使用他自身的测试服务器
# 当使用过渡服务器时,劫持域名
# 使用了一个环境变量 STAGING_SERVER
python manage.py test functional_tests
# 测试通过正常
STAGING_SERVER=superlists-staging.ottg.eu python manage.py test functional_tests
# AssertionError: 'To-Do' not found in ''
# 故障,因为还没部署域名
# 注册域名
# 域名我到是有的,但是有外网ip的服务器没有,用hosts替代一下吧
# 手动配置托管网站的服务器
# 配置服务器
# 部署代码
# 选择在哪里托管网站
# Heroku, OpenShift, PythonAnywhere
# 搭建服务器
# Ubuntu
# root
# 外网可访问
# 可以ssh登录