测试学习

python基础

环境安装

这一章节介绍一下怎么安装python相关环境


安装Python

打开 python官网下载地址 https://www.python.org/downloads/ ,在下方找到Python 2.7.13,然后 点击 https://www.python.org/downloads/release/python-2713/ 打开,选择跟自己操作系统匹配的安装包,然后下载,如windows 64,则选择 Windows x86-64 MSI installer,安装完成后,打开一个命令行窗口,然后输入python,如果出现python的提示符,说明安装成功。

警告

Windows安装过程中需勾选 Add python.exe to Path ,如下图所示:

_images/img1.png

pip简介

pip是python自带的包管理器,安装完python后,即可拥有,pip常用命令如下:

>>> pip install <包名> # 安装python包
>>> pip install -U <包名> # 更新包
>>> pip uninstall <包名> # 删除python包
>>> pip list # 查看安装的包

pip下载python包是从https://pypi.python.org/下载的,pypi服务器在国外,因此国内访问可能速度会比较慢,但使用时可以指定国内源,也就是从国内的镜像服务器下载,如使用清华的源:

>>> pip install -i  https://pypi.tuna.tsinghua.edu.cn/simple flask

除了在命令行指定源外,也可以在本地配置,使用方法可以在网络上搜索一下


安装ipython

ipython是一个python的交互命令行工具,比python自带的增强了代码高亮,自动完成,代码提示功能,非常适合初学者

Windows

下载安装 pyreadline,而后执行:

>>> pip install ipython
>>> ipython
Python 2.7.13 (default, Apr 26 2017, 20:42:49)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]:

Linux&Unix

执行:

>>> pip install ipython
>>> ipython
Python 2.7.13 (default, Apr 26 2017, 20:42:49)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]:

安装pycharm

pycharm是个人认为目前对初学者最友好的IDE,初学者下载社区版(免费的)就可以了,下载地址: 链接,选择Community,然后下载安装。

数据类型和变量

下表为python中的基本数据,下面分别对每种类型做下简单介绍

类型 示例 备注
int 1、 1000、-1000 整数类型
str ‘abc’、‘123’、u’!@#$%^’ 字符串类型
float 1.1、-1.1、1.1e-10 浮点数类型
bool True、False 布尔类型
None None 空值

int类型

int类型为整数类型,如1、-1、61284123等

创建一个int类型变量非常简单,打开一个命令行窗口,进入ipython交互环境,敲入下面代码:

In [1]: a = 1

In [2]: print(a)
1

In [3]: type(a)
Out[3]: int

In [4]:

注解

type 函数的意思是查看一个python对象的类型

像1、-1这样的值叫做字面量,简单理解就是,你看到它就知道是什么意思,int类型的字面量除了1、-1这种10进制的表达方式外,还可以使用16进制、8进制、2进制,如下所示:

In [1]: 0xa
Out[1]: 10

In [2]: 010
Out[2]: 8

In [3]: 0b10
Out[3]: 2

In [4]:

注解

0x开头的代表16进制,0开头的代表8进制,0b开头的代表2进制

在python里,可以使用int函数把数字字符串转换为数字类型,如下所示:

In [1]: int('123')
Out[1]: 123

In [2]: int('a', 16)
Out[2]: 10

In [3]: int('10', 2)
Out[3]: 2

In [4]: int('a')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-b3c3f4515dd4> in <module>()
----> 1 int('a')

ValueError: invalid literal for int() with base 10: 'a'

In [5]:

注解

int函数的第二个参数表示该字符串的进制,默认不传为10进制,传入16代表16进制,如果不能转换则会报错


float类型

float为浮点数类型,通俗的说就是小数,学用的字面量表示方式有:1.1、-1.1、4.3e-3(0.0043):

In [1]: 1.1
Out[1]: 1.1

In [2]: -1.1
Out[2]: -1.1

In [3]: 3.4e-3
Out[3]: 0.0034

In [4]: a = 1.1

In [5]: type(a)
Out[5]: float

In [6]:

字符串

下一节再讲


布尔类型

布尔是逻辑学大师,布尔类型只有两个值,真和假,真就是 True ,假是 False

In [1]: a = True

In [2]: b = False

In [3]: a
Out[3]: True

In [4]: b
Out[4]: False

In [5]: type(a)
Out[5]: bool

In [6]: 1 == 1
Out[6]: True

In [7]: 1 > 2
Out[7]: False

In [8]:

变量

变量就是为了方便记忆,给python对象起了个有意义的名字,如 nameage 等,在python中定义变量非常简单,如下所示:

In [1]: name = u'小明'

In [2]: age = 22

In [3]: money = 1000.11

In [4]:

除了给一个变量赋值外,还可以同时给多个变量赋值:

In [4]: a = b = 1

In [5]: a
Out[5]: 1

In [6]: b
Out[6]: 1

In [7]:

python的变量需要遵守以下规则:

  1. 变量名可以包括字母、数字、下划线,但是数字不能做为开头:

    In [1]: a = 1
    
    In [2]: a1 = 1
    
    In [3]: _ = 1
    
    In [4]: a_ = 1
    
    In [5]: 1a = 1
      File "<ipython-input-5-a0b8bd664e29>", line 1
        1a = 1
        ^
    SyntaxError: invalid syntax
    
  2. 变量名区分大小写

    例如 nameName 是两个不同的变量

  3. 不要使用python关键字做为变量名

    如int是python里的一个关键字,所以一定不要使用 int = xxx ,除非真的要替换掉int

  4. 变量名要有意义

    例如,你要定义一个叫名字的变量,你可以起 ab 或者 asqewr ,但最好使用 name 或者 xiaoming_name 这样有意义的单词组合

  5. 变量名单词组合使用’_’分隔

    在python中,如果变量名是若干个单词,如猪的数量,在python中应命名为 pig_num ,而不是 pigNum ,当然这只是推荐,如果一定要使用 pigNum 也不会报错的


练习

运行python

在解释器中运行

在命令行中输入python或者ipython,解释器交互环境,然后输入python脚本,敲击回车,即可执行:

➜ ~ python
Python 2.7.13 (default, Apr 26 2017, 20:42:49)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

该种模式下退出交互环境,输入exit():

>>> exit()
➜ ~ ipython
Python 2.7.13 (default, Apr 26 2017, 20:42:49)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]:

该种模式下退出输入quit或exit

直接运行python文件

在命令行模式下,执行输入 python <文件名> ,即可执行python脚本,如python脚本(a.py)内容为:

print(123)

执行a.py:

➜ ~ python a.py
123
➜ ~

如何新建python脚本文件

新建一个普通的纯文本文件,保存后缀为.py即可

危险

在windows下不允许使用notepad(记事本)、word新建脚本文件

列表和元组

列表可以理解为一组数据,这组数据里可以包含不同的类型,可以往列表里增加数据,也可以删除,同时列表是可迭代的,也就是说可以用for循环去遍历。元组和列表基本一样,唯一不一样的是元组定义好之后就不能变了,不能增加也不能删除,下面我们介绍一下常见用法

定义一个列表

l1 = []  # 定义一个空列表
l2 = [1, 2, 3, 4, 5]  # 定义一个列表,里面包含同种类型的元素
l3 = [1, 'a', None, 2.1, int]  # 定义一个列表,里面包含不同类型的元素

append:向列表未尾增加元素

l1 = [1, 2, 3, 4, 5]
l1.append(6)
print(l1)

输出为:

[1, 2, 3, 4, 5, 6]

pop:移除列表最后一个元素,并返回该元素

l1 = [1, 2, 3, 4, 5]
poped_element = l1.pop()
print(poped_element)
print(l1)

输出为:

5
[1, 2, 3, 4]

count:对其中的某个元素计数

l1 = [1, 1, 2, 3, 4, 5]
print(l1.count(1))

输出为:

2

sort:排序

l1 = [1, 5, 2, 3, 4, 0]
l1.sort()
print(l1)

输出为:

[0, 1, 2, 3, 4, 5]

reverse:倒置列表

l1 = ['a', 'b', 'c', 'd']
l1.reverse()
print l1

输出为:

['d', 'c', 'b', 'a']

index:查找其中某一个元素的位置

该方法有三个参数,index(v, i, j),其中第一个为要查找的元素,第二个,第三个默认可以不用传,第二个参数是从第几个元素开始查找,第三个参数是查找到第几个元素结束:

l1 = [1, 5, 2, 3, 4, 0, 1]
print l1.index(1)
print l1.index(1, 1)
print l1.index(1, 1, 5)

输出为:

0
6
Traceback (most recent call last):
  File "/Users/sunhui/PycharmProjects/ps/a.py", line 7, in <module>
    print l1.index(1, 1, 5)
ValueError: 1 is not in list

extend:扩展列表

这个方法可以把一个列表拼接到另一个列表的后面:

l1 = [1, 5, 2, 3, 4, 0, 1]
l2 = ['a', 'b', 'c']
l1.extend(l2)
print l1

输出结果为:

[1, 5, 2, 3, 4, 0, 1, 'a', 'b', 'c']

列表相加

两个列表直接相加可以返回拼接后的列表:

l1 = [1, 2, 3]
l2 = ['a', 'b', 'c']
l3 = l1 + l2

print l3

输出为:

[1, 2, 3, 'a', 'b', 'c']

遍历列表

使用for循环可以遍历列表:

l1 = [1, 2, 3, 4, 5]
for i in l1:
    print i

输出为:

1
2
3
4
5

元组

因元组不能改,所以元组可以使用的方法比较少,只有count和index,用法和列表是一样的:

In [9]: t1.
            t1.count
            t1.index

切片

还是先看一下下面的这个图,需要好好理解一下:

+---+---+---+---+---+---+
| P | y | t | h | o | n |
+---+---+---+---+---+---+
0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1
l1 = ['p', 'y', 't', 'h', 'o', 'n']
print l1[0]  # p
print l1[5]  # n
print l1[-1]  # n
print l1[1:3]  # ['y', 't']
print l1[-3:-1]  # ['h', 'o']
print l1[3:]  # ['h', 'o', 'n']
print l1[:-3]  # ['p', 'y', 't']
print l1[::2]  # ['p', 't', 'o']

输出为:

p
n
n
['y', 't']
['h', 'o']
['h', 'o', 'n']
['p', 'y', 't']
['p', 't', 'o']

练习

题1,把下面这个列表中的6取出来:

l1 = [
    [1, 2, 3,
    [4, 5, 6]
    ]
]

题2,把下面这个列表中的每个元素+1:

l = [1, 2, 3, 4, 5]

字典

字典和列表一样,是python里面非常重要的一个基础数据结构,通俗的理解,字典就像其名一样,一个单词对应一个解释,如’a’:’我是小a’,’yuyu’:我是鱼鱼,字典对象里可以包含0个或多个键值对(key-value)

定义一个字典

有两种方式可以定义字典:

d1 = {'name': 'yuyu'}
d2 = dict(name='yuyu')

print d1
print d2

输出为:

{'name': 'yuyu'}
{'name': 'yuyu'}

添加元素

d1 = {}
d1['a'] = 1
print d1

d2 = {}
d2.update(b=2)
print d2

取值

d1 = {'a': 1, 'b': 2}
print d1['a']
print d1['c']

输出为:

1
Traceback (most recent call last):
  File "C:/Documents and Settings/Administrator/PycharmProjects/study/demo2.py", line 5, in <module>
    print d1['c']
KeyError: 'c'

除了使用[]取值外,还可以使用get,并且设置默认值:

d1 = {'a': 1, 'b': 2}
print d1.get('a')
print d1.get('c', 0)

输出为:

1
0

取出字典的key到列表

d1 = {'a': 1, 'b': 2}
print d1.keys()

输出为:

['a', 'b']

取出字典的value到列表

d1 = {'a': 1, 'b': 2}
print d1.values()

输出为:

[1, 2]

取出字典的键值对为到列表

d1 = {'a': 1, 'b': 2}
print d1.items()

输出为:

[('a', 1), ('b', 2)]

遍历字典的键值对

d1 = {'a': 1, 'b': 2}
for k, v in d1.items():
    print 'key: %s, key: %s' % (k, v)

输出为:

key: a, key: 1
key: b, key: 2

判断某个key是否在字典中

d1 = {'a': 1, 'b': 2}
print 'a' in d1
print 'c' in d1

输出为:

True
False

字符串

字符串也是一种数据类型,比如 "abc""~!@#$""我是好人" 等用来表示人类语言,在编程过程中我们会经常和字符串打交道,python提供了很多处理字符串的方法,如 replacefindlower 等等,后面会一一介绍

编码

在介绍字符串之前,我们先来了解一下编码,大家都知道在计算机中数据都是以二进制保存的,也就是 01101111001010 这样子,那我们如何保存字符串呢?因为最开始计算机在美国比较流行,美国讲英语,也就26个字母,算上大小写再加上数字标点符号也不够127个,所以美国人把他们用到的字符集用一个字节就可以完全表示了,如B可以用66表示,即 01000010,美国人把这种编码规则叫做 ASCII 码。

注解

在计算机中一位二进制叫做一位,即1 bit,8位叫一个字节

再后来呢,计算机开始在全世界流行了,但是其它国家并不全使用英文字母语言,比如欧洲很多国家(如德国、法国、瑞典、俄罗斯、波兰等)的字母就和英文字母不一样,其它国家就更不用说了,中国就有几万个汉字,日本也有一万多汉字还有平假名、片假名,所以这些国家也开始制定自己国家语言的字符集,如中国的字符集 gbkGB2312gbk 编码是兼容 ASCII 编码的,但是因为汉字太多了,所以使用了两个字节表示,两个字节16位,也就是最多可以表示65536个字符,足够用了。同时其它国家也制定了类似 gbk 这样的字符集,好吧,这下子乱了套了,比如在中国字符集中用 0b1100111011010010 来表示 ,但翻译成日文字符集(Shift_JIS)却就成了 ホメ ,假如一个字符串中同时包括中文和日文,这叫计算机怎么解码呢?按中文编码解是错的,按日文也是错的,好纠结。。。

In [54]: wo = u'我'

In [55]: wo_gbk = wo.encode('gbk')

In [56]: wo_gbk
Out[56]: '\xce\xd2'

In [57]: print(wo_gbk.decode('Shift_JIS'))
ホメ

In [58]:

于是乎,编码这个问题成了计算机的一个大问题,为了解决这个问题,ISO制定了一个宏大的计算,把全世界的字符都包含进来,全名为 unicode 也叫 统一码万国码 ,unicode一般占用2个字节,下面我们显示一下在python里定义unicode字符

In [1]: ustr = u"我是好人"

In [2]: ustr
Out[2]: u'\u6211\u662f\u597d\u4eba'

In [3]: ustr2 = u'\u6211\u662f\u597d\u4eba'

In [4]: ustr2
Out[4]: u'\u6211\u662f\u597d\u4eba'

In [5]: print(ustr2)
我是好人

好,现在世界的字符编码统一了,但又有问题了,欧美那些国家因为是使用字母,以前1个字节就搞定了,现在为了照顾其他国家,竟然要多用1个字节,这要浪费多少硬盘、内存和网络带宽啊,不公平,抵制~!!!于是乎,又出现了 UTF-8 编码,这个字符集的特点是兼容 ASCII ,即 ASCII 是它的一个字集,它把原先的 ASCII 字符仍编码为1个字节,另外一些常用的其它国家字符编码为2个字节,一些生僻字编码为3-6个字节,于是即照了欧美众国的程序员情绪、兼容了之前用ASCII编码的代码,还节省了存储空间,节约了传输带宽,具体的编码格式如下所示:

Unicode编码(十六进制) UTF-8 字节流(二进制)
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF 11110xxx10xxxxxx10xxxxxx10xxxxxx

也就是说UTF-8编码的字符没有固定长度,是变长的,来我们看一下unicode和utf-8的转换:

In [11]: unicode_str = u"我"

In [12]: utf8_str = unicode_str.encode('utf-8') #  使用encode(翻译成中文是编码)方法、utf-8来编码unicode字符

In [13]: utf8_str
Out[13]: '\xe6\x88\x91'

In [14]: utf8_str.decode('utf-8') #  使用decode(翻译成中文是解)方法、utf-8来解码unicode字符
Out[14]: u'\u6211'

In [15]:

大家试一下,体会一下,讲到这里,大家是不是有点感觉了?其它unicode和utf-8还有很多非常细的知识点,不过我们掌握这些基础的一般就够用了,下面我们来试一下unicode和其它编码的转换:

In [6]: unicode_wo = u"我"

In [7]: gbk_wo = unicode_wo.encode('gbk')

In [8]: gbk_wo
Out[8]: '\xce\xd2'

In [9]: Shift_JIS_wo = unicode_wo.encode('Shift_JIS')

In [10]: Shift_JIS_wo
Out[10]: '\x89\xe4'

介绍到这里,我们不但学习了字符串编码知识,同时还学会了字符串的两个方法 encode & decode

字符串方法

学习完编码这么烧脑的概念后,我们来看点轻松的,怎么在python中处理字符串,首先字符串是一个不可变对象,也就是说不能修改它的内容,但可以修改后再存到另一个变量里

# coding=utf-8是什么鬼?

这个注释声明放在python脚本文件第一行,意思是声明该文件的编码方式,也就是说脚本里的字符串是使用什么编码,如下示范,我们使用utf-8编码,然后就可以使用utf-8编码去解码

# coding=utf-8

s = '我'
print(s.decode('utf-8'))
定义一个unicode字符串

在python2.7中,字符串编码默认是跟文件编码走的,但是我们可以显式的指定字符为unicode格式:

# coding=utf-8

s = u'我'

print(s)

print(s.encode('utf-8'))
计算其中某一个字符的数量
In [21]: s = 'abbcccdddd'

In [22]: s.count('a')
Out[22]: 1

In [23]: s.count('b')
Out[23]: 2

In [24]: s.count('d')
Out[24]: 4
是否以指定字符串开头/结尾
In [25]: s = 'abbcccdddd'

In [26]: s.startswith('ab')
Out[26]: True

In [27]: s.startswith('abc')
Out[27]: False

In [28]: s.endswith('ddd')
Out[28]: True

In [29]: s.endswith('ddde')
Out[29]: False
转换大小写
In [39]: s = 'aBcD'

In [40]: s.lower()
Out[40]: 'abcd'

In [41]: s.upper()
Out[41]: 'ABCD'
查找字符

find 方法可以从字符串左边开始查找字符,找到后返回字符位置,否则返回-1, rfind 可以从右边开始查找

In [48]: s = 'abccba'

In [49]: s.find('a')
Out[49]: 0

In [50]: s.find('b')
Out[50]: 1

In [51]: s.find('d')
Out[51]: -1

In [52]: s.rfind('a')
Out[52]: 5
替换字符

replace可以传入两个参数,第一个是要替换内容,第二个是替换内容,替换之后返回替换后的字符串

In [53]: s = 'abcdefg'

In [54]: s.replace('a', '1')
Out[54]: '1bcdefg'
去除字符串头尾的字符串

strip 这个方法是非常有用的,可以去除字符串头尾指定的字符串,默认是空格、换行符、制表符等,rstrip 是只去除右边的, lstrip 是只去除左边的,除了默认的字符串,也可以指定值:

In [59]: s = '\n\t abc  \n\t'

In [60]: s.strip()
Out[60]: 'abc'

In [61]: s.lstrip()
Out[61]: 'abc  \n\t'

In [62]: s.rstrip()
Out[62]: '\n\t abc'

In [65]: s.strip('\t')
Out[65]: '\n\t abc  \n'

# 注意下面这个例子
In [66]: s.strip('\n')
Out[66]: '\t abc  \n\t'
格式化字符串

目前比较常用的有两种方式,第一种是在字符串内容使用 % 占位,然后在字符串后跟数据

In [70]: s = '我是%s人' % '好'

In [71]: print(s)
我是好人

In [72]: s1 = '我是%s%s' % ('我', '吗')

In [73]: print(s1)
我是我人吗

第二种是使用字符串的format函数:

In [74]: s = '我是{what}人'.format(what='好')

In [75]: print(s)
我是好人
拼接字符串

在python中可以直接使用 + 来拼接字符串,但是两边的python字符串编码要兼容

In [79]: s1 = 'abc'

In [80]: s2 = '123'

In [81]: s1 + s2
Out[81]: 'abc123'

除此之外,还有一个 join 方法去把字符/字符串列表(或者可迭代对象均可)拼接起来:

In [83]: s1 = ['ab', 'cd', 'ef']

In [84]: ''.join(s1)
Out[84]: 'abcdef'

In [85]: '.'.join(s1)
Out[85]: 'ab.cd.ef'

In [86]: s2 = 'abcdefg'

In [88]: '.'.join(s2)
Out[88]: 'a.b.c.d.e.f.g'

运算

在python中,经常用到的运算符有算术运算、关系运算、赋值运算、逻辑运算、成员运算,除此之外还有位运算,但用到的并不多,就不再讲解了

算术运算

算术运算就是我们数学上的加(+)、减(-)、乘(*)、除(/)、取模(%)、幂(**),加减乘比较简单就不再说了

这里的除比较特别,如果是两个整数相除,得到了是商的整数部分,如:

In [1]: 5/3
Out[1]: 1

In [2]: 5/2
Out[2]: 2

但是除数与被除数如果其中有一个为小数,得到的就是准确结果:

In [3]: 5/3.0
Out[3]: 1.6666666666666667

In [4]: 5/2.0
Out[4]: 2.5

In [5]: 5.0/2.0
Out[5]: 2.5

所以做运算时,如果需要准确结果,可以把其中一个转为小数:

In [6]: 5 / float(2)
Out[6]: 2.5

取模

取模就是获取余数部分,例如5除以2,商2余1,取模结果就是1:

In [7]: 5 % 2
Out[7]: 1

In [8]: 5.0 % 2
Out[8]: 1.0

幂

幂就是乘方,如果2的3次幂,即2的3次方,2*2*2:

In [9]: 2**3
Out[9]: 8

In [10]: 10**3
Out[10]: 1000

关系运算

即判断运算符两边的表达式的关系,如大于,小于,不等于,返回布尔类型, TrueFalse

In [12]: 1 > 2
Out[12]: False

In [13]: 1 == 2
Out[13]: False

In [14]: 1 < 2
Out[14]: True

In [15]: 1 != 2
Out[15]: True

In [16]: 1 <= 2
Out[16]: True

In [17]: 2 >= 1
Out[17]: True

逻辑运算

逻辑运算就是我们高中数学里的与(and)、或(or)、非(not),表达式即可以是布尔类型,也可以不是,在python中, None空字符串空字典数字0 在进行逻辑运算时都会被当做 False

  • 使用与的时候,从左向右运算,如果第一个为假,则返回第一个值,不再进行第二个运算,如果第一个为真,则返回第二个值:

    In [34]: 0 and 1
    Out[34]: 0
    
    In [35]: 1 and 2
    Out[35]: 2
    
    In [36]: [] and 1
    Out[36]: []
    
    In [37]: '' and 1
    Out[37]: ''
    
  • 使用或的时候,第一个为真时,则返回第一个值,不再进行第二个值运算,第一个为假时,返回第二个值:

    In [44]: 1 or 0
    Out[44]: 1
    
    In [45]: 1 or a
    Out[45]: 1
    
    In [46]: 0 or 1
    Out[46]: 1
    
    In [47]: 0 or a
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-47-1585eb30ceed> in <module>()
    ----> 1 0 or a
    
    NameError: name 'a' is not defined
    
  • 使用非的时候,返回表达式的相反的布尔值:

    In [51]: not True
    Out[51]: False
    
    In [52]: not False
    Out[52]: True
    
    In [53]: not 1
    Out[53]: False
    
    In [54]: not 0
    Out[54]: True
    
    In [55]: not []
    Out[55]: True
    
    In [56]: not ''
    Out[56]: True
    

成员运算

判断一个元素是不是在另外一个序列里,序列是指一系列元素,可以是列表、元组、字典以及类似对象,最后返回布尔类型, TrueFalse

In [26]: 1 in [1, 2]
Out[26]: True

In [27]: 3 in [1, 2]
Out[27]: False

In [28]: 3 not in [1, 2]
Out[28]: True

In [29]: 'a' in {'a': 1}
Out[29]: True

流程控制

python中主要提供两种流程控制,条件判断和循环,关键字有三个, for inif elsewhile,下面一一介绍一下

条件判断

if else 这种条件判断大家应该都很熟悉了,每种编程语言都会有,翻译成中文就是,如果AAA成立,执行XXX,或者BBB成立,执行YYY,如果都对不上,执行ZZZ,如下面的例子,如果你的性别是男的,输出帅哥,如果是女,输出美女:

# coding=utf-8

sex = u'男'

if sex == u'男':
    print(u'帅哥')
elif sex == u'女':
    print(u'美女')
else:
    print(u'死人妖')

注解

注意是elif,不是else if

循环

for in 循环

为什么这里写 for in 而不是 for 呢,因为在python中的for循环 forin 是成对出现的,我们先写一个例子:

# coding=utf-8

for i in range(10):
    print(i)

这个例子循环了10次,打印出了0到9,是不是每简单?自增也不用写了,其实在python中 for in 用来遍历可迭代对象的,把可迭代对象里的元素一个一个取出来,然后再做处理,当然,你不做处理也可以,仅当做计数器

在这里我们引入了一个新的函数 range ,故名思意,就是范围,它可以生成指定范围的数据序列,示例如下:

In [1]: range(10)
Out[1]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [2]: range(2, 10)
Out[2]: [2, 3, 4, 5, 6, 7, 8, 9]

In [3]: range(2, 10, 2)
Out[3]: [2, 4, 6, 8]

In [4]:

注解

可迭代对象,就是实现了可迭代协议的python对象,比如list、tuple、dict等等

while 循环

while循环的意思是:当AAA条件为真的时候,执行XXXX,很简单,我们写个例子:

# coding=utf-8

num = 10

while num > 0:
    print(num)
    num = num - 1

 跳过循环、终止循环

使用continue可以跳过本次循环,进入下次循环,使用break中以跳出循环语句:

# coding=utf-8

'''不打印2'''

for i in range(10):
    if i == 2:
        continue
    print(i)

print('*' * 30)

num = 10

while num > 0:
    if num == 2:
        continue
    print(num)
    num = num - 1
# coding=utf-8

'''3的时候终止循环'''

for i in range(10):
    if i == 3:
        break
    print(i)

print('*' * 30)

num = 10

while num > 0:
    if num == 3:
        break
    print(num)
    num = num - 1

函数

函数在python里是非常重要的,是一等公民,函数的定义和使用非常简单,但又有非常高级的用法,函数就像我们中学时学的函数 y = x * x ,有输入有输出

定义一个函数

def calc(a, b):
    return a + b

total = calc(1, 2)
print(total)

在这里我们定义了一个函数 calc ,用来计算两个数值的和,调用时直接写函数名,然后传入相应的参数,返回两个值的和,需要注意的是

  • 函数名命名规则和变量是一致的
  • 函数都有返回值,如果没有写return语句则返回None
  • 注意是怎么缩进的

值传递和引用传递

给函数传参时,要注意值传递和引用传递,值传递就是只传值,函数里的操作不会修改原变量,引用传递时在函数里的操作会修改原变量,在python里,字典、列表、对象是引用传递,其它为值传递,下面我们看一个值传递的例子

def test_value(v):
    v = 2

value = 1
test_value(value)
print(value)

输入仍为1,也就是说在函数里对变量的值修改,并没有影响到传的参数,下面再看一个引用传递的例子

def test_ref(v):
    v['a'] = 'test_ref'

value = {'a': 1}
test_ref(value)
print(value)

输出结果为 {'a': 'test_ref'} ,大家可以观察一下

默认参数

有时候定义一个函数,有很多参数,但并不想每次都给所有参数传值,比如下面一个函数

def make_order(order_num, order_from, order_to, order_info):
    print(order_num, order_from, order_to, order_info)

使用的时候,需要传入4个参数,但其实大部分时候我们只需要传order_num这个参数,其它参数一般都不会变,这种情况下,我们就可以使用默认参数了

ef make_order(order_num, order_from='shanghai', order_to='henan', order_info='some thing'):
    print(order_num, order_from, order_to, order_info)

make_order(123123)
make_order(123123, order_info='i do not know')

输入结果为

(123123, 'shanghai', 'henan', 'some thing')
(123123, 'shanghai', 'henan', 'i do not know')
[Finished in 0.2s]

警告

需要注意的是,所有的默认值参数必须定义在位参后面

函数返回值

使用return关键字返回函数结果,结果可以是任何python基础数据类型或者对象,如果不写return的话,则返回None,如下示例:

# coding=utf-8

# 反转输入的名字
def reverse_your_name(name):
    return name[::-1]

my_name = 'yuyu'
print(reverse_your_name(my_name))

结果为:

uyuy
[Finished in 0.2s]

下面这个例子不指定返回值:

# coding=utf-8

def non_return():
    pass

print(non_return())

结果为:

None
[Finished in 0.2s]

可变参数

有时候,当我们定义一个函数的时候,我们并不知道要传多少参数,也就是说参数的个数是未知的,为了解决这个问题,我们可以采用如下办法,把位置参数全部放进一个列表传进去,关键值参数放入一个字典里:

def demo(args, kwargs):
    print args
    print kwargs

demo([1, 2, 3, 4], {'a': 1, 'b': 2})

结果为:

[1, 2, 3, 4]
{'a': 1, 'b': 2}
[Finished in 0.1s]

我们是做到了把不定个数的参数传给了函数,但是这样好像并不符合我们的传参习惯,为此python在语法做了优化,可以按照之前的传参方式,传入变长参数:

def demo(*args, **kwargs):
    print args
    print kwargs

demo(1, 2, 3, 4, a=1, b=3, c=3)

结果为:

(1, 2, 3, 4)
{'a': 1, 'c': 3, 'b': 3}
[Finished in 0.1s]

递归函数

如果一个函数在函数内部调用它自己,这就是递归,递归的好处是,针对把复杂层次深的问题(有相似逻辑)简单化,如下面这个列表:

l = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11]]]]

使用函数把它摊平:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

这个该怎么写呢?正常的思维是,遍历列表里的每一项,如果是列表的话就再遍历,来我们写一下:

def expand(l):
    result = []
    for i in l:
        if isinstance(i, list):
            for j in i:
                if isinstance(j, list):
                    for m in j:
                        if isinstance(m, list):
                            for n in m:
                                if isinstance(n, list):
                                    pass
                                else:
                                    result.append(n)
                        else:
                            result.append(m)
                else:
                    result.append(j)
        else:
            result.append(i)

    return result
print expand(l)

结果为:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[Finished in 0.1s]

有没有一种日了狗的感觉。。。 还好这层级也并不是特别深,但是如果不知道层级怎么办?大家注意这些判断,其实都是在判断是不是列表,如果是列表就再遍历,不是就加进结果集,而且内部的逻辑跟外部是一样的,我们把问题简化一下,再看一个例子:

l = [1, 2, [3, 4]]

result = []

def a(l):
    for i in l:
        result.append(i)
    return result


def b(l):
    for i in l:
        if isinstance(i, list):
            a(i)
        else:
            result.append(i)

b(l)
print result

结果为:

[1, 2, 3, 4]
[Finished in 0.1s]

这是一个只有两级的例子,在a函数中做的是最后一级,大家可以看出来,两个函数里,其实做了类似的循环,延伸一下,如果在b里调用b自己不就可以了吗?

l = [1, 2, 3, [4, 5, 6, [7, 8, 9, [10, 11]]]]

result = []
def expand(l):
    for i in l:
        if isinstance(i, list):
            expand(i)
        else:
            result.append(i)



expand(l)
print result

结果为:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[Finished in 0.1s]

有点绕,不理解也没关系,一般情况下也不会用到的。。需要注意的是使用递归时一定要有终止条件,否则就会溢出报错了。

装饰器

装饰器其实很简单,就是高阶函数,把函数传给函数,再返回函数,也称包装器,听起来比较唬人,我们看个例子就明白了:

def demo(a, b):
    return a + b

def wrapper_demo(func):
    def wrapper(*args):
        print 'begin'
        result  = func(*args)
        print 'end'
        return result
    return wrapper

w = wrapper_demo(demo)
r = w(1, 2)
print r

结果为:

begin
end
3
[Finished in 0.1s]

理解了之后,我们再讲一种简单的解法,python里提供了一个 @ 符号,用来简化使用:

def wrapper_demo(func):
    def wrapper(*args):
        print 'begin'
        result  = func(*args)
        print 'end'
        return result
    return wrapper

@wrapper_demo
def demo(a, b):
    return a + b

print demo(1, 2)

大家试一下,结果是一样的

python是脚本语言,但同时也是面向对象的,那什么是面向对象呢?面向对象是以人类的思维方式去抽象代码,比如人们一般会说,XXX是什么,XXX在干什么,XXX有什么,XXX怎么怎么,来我们分析下人类语言的语法,主谓宾状补等,主要是主谓宾,首先主语,比如你、我、他、它、小明、小王都是一个具体的对象,谓语,比如跑、说、打等属于形为,对象的形为,最后宾语,是形为的参数;

再比如,我有一个耳朵,一个鼻子,1万块钱,这是我的属性

为什么要使用类

首先就是可以更好的封装代码,然后就是

怎么定义一个类

TODO

使用类

TODO

属性

TODO

方法

TODO

继承

TODO

命名空间

TODO

运算符重载

TODO

多态

TODO

模块和包

python里用模块和包组织代码,模块就是一个个以 .py 为后缀的文件,包就是一个个带有 __init__.py 文件的文件夹。

怎么理解模块

可以把模块理解成一个日记本,不同的日记本里都可以出现6月6号的日记,但是因为分属于不同的日记本,所以引用的时候不会出错,比如我们可以描述为A日记本某日的日记,B日记本某日的日记

定义、使用模块

新建一个以 .py 结尾的文件,就是新建了一个模块,大家新建一个 a.py 文件,然后输入以下内容:

# coding=utf-8

num = 1

def demo():
    print 'im in a.py'

使用模块有如下三种方式,第一种:

from a import *

这种方式从字面理解是从a模块中导入所有内容(*是所有的意思),然后就可以使用模块中的变量和函数了,如新建一个 b.py ,然后输入以下内容

from a import *

print num
demo()

看,在b模块使用a模块的变量和函数,就好像在a块中一样,但这种方式存在一个严重问题,但a模块中变量非常多,如果全部导入会污染b模块的命名空间,如,b里也有一个变量叫num,如果导入语句出现在num定义后,则会出现变量被覆盖的情况,所以非常不推荐这种方法。

第二种:

import a

这种方式是非常好的,导入模块对象,然后使用的时候可以通过 点号 取值,即 a.numa.demo 这样子,这样即可以知道这些变量和函数是a模块里的,也不会污染b的命名空间。

第三种:

from a import num, demo

第三种方法看起来和第一种很相似,只是把 * 换成了具体的变量名,虽说原理相似,但这种按需导入的方式,即使变量名有冲突,也可以很快的找出来,使用方式同第一种。

__name__是什么?

经常在模块里看到如下语句:

if __name__ == '__main__':
    xxxx

那么 __name__ 是什么? __main__ 又是什么呢?故名思意, __name 是名字,模块的名字,比如:

import a
print a.__name__

大家可以看到输出为 a 也就是模块的名称,但是在a模块里执行 print(__name__) 会是什么呢?结果是 __main__ ,也就是说,当模块被当做运行主体时, __name__ 的值是 __main__ ,但被导入的时候,值为模块名。所以可以用 if __name__ == '__main__':xxx 去判断只在运行该模块运行时执行下面的语句,被导入时不执行。

引用标准库里的模块

python标准库里有很多现在的模块可以使用,如果 systimedatetime 等等,使用标准库里的模块与我们写的方式是一样的,如:

# coding=utf-8

import sys
import time
import datetime

print sys.path
print time.time()
print datetime.datetime.now()

标准库里的模块非常之多,覆盖数据结构、文件系统、字符处理、时间日期等等, 大家可以看一下这个目录,https://docs.python.org/2/library/index.html

什么是包?

上面我们把模块比喻成了日记本,但在我们可以把包比喻成盒子,盒子里可以放日记本,每个盒子都有自己的名字,如 box_abox_b ,包在实现体现就是文件夹,新建一个文件夹,然后在里面放一个名为 __init__.py 的文件(空的也可以,但名字要对),这时这外文件夹也被称为包。包是用来组织模块的,以用来避免模块名重复的问题,同时也可以把功能类似的模块放到一个包里,即可以起到分类的作用。

怎么使用包

使用包非常简单,就跟模块差不多,只是要加上包名,如 from box_a.a import num ,先是包名,然后是点号,通过点号索引下面的模块。

嵌套包

包内不仅可以模块,还可以再放入包,思考如下结构:

└── outer (外面的包)
    ├── __init__.py
    ├── a.py
    ├── b.py
    └── inner (里面的包)
        ├── __init__.py
        └── c.py

2 directories, 5 files

怎么导入c模块呢?同样可以使用点号去索引里面的包,如:

from outer.inner import c

import是怎么查找包或模块的呢?

比如说,你有两个包,一个在C盘,一个在D盘,两个包名字一样,那么导入的时候,是导入哪一个呢?答案是可能都导入不了,python是按照 sys.path 里的路径去查找包或模块的,该列表的内容大致如下:

['',
'/usr/local/Cellar/pyenv/1.0.10/versions/2.7.13/bin',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python27.zip',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-darwin',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-mac',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/plat-mac/lib-scriptpackages',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-tk',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-old',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/lib-dynload',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages',
'/usr/local/opt/pyenv/versions/2.7.13/lib/python2.7/site-packages/IPython/extensions',
]

大家可能跟我的不太一样,但是类似,在这个列表里,第一个元素是空字符串,后面是一些路径,路径就不用解释了,这个空字符串是代表当前目录,也就是你所在的目录。当在当前目录找不到时,会按着这个列表顺序去下个路径找,以此类推,最后还找不到,就会报错。回到正题,我们怎么运行例如 c:\outer 包呢?

第一种方式

我们看一下,outer包在c盘下,也就是说c盘在查找路径里,我们就可以找到该包,所以在需要引用outer包的脚本里加上 sys.path.append(r'c:/') ,这种修改只是临时性的,脚本执行完后,又会恢复原状。

第二种方式

除了修改查找路径,我们还可以在当前路径上做文章,我们去包路径下执行不就得了?因为此时,当前路径就是包所在的路径了,所以自然可以找到包。

内建函数

如下表格是python的内建函数,内建函数是指,python解释器启动时会自动加载的函数,使用时无需导入,内建函数大概有70多个,但常用的并不多,我们介绍一些常用的。

abs() divmod() input() open() staticmethod()
all() enumerate() int() ord() str()
any() eval() isinstance() pow() sum()
basestring() execfile() issubclass() print()super() bin()
file() iter() property() tuple() bool()
filter() len() range() type() bytearray()
float() list() raw_input() unichr() callable()
format() locals() reduce() unicode() chr()
frozenset() long() reload() vars() classmethod()
getattr() map() repr() globals() max()
reversed() zip() compile() hasattr() memoryview()
round() __import__() complex() hash() min()
set() delattr() help() next() setattr()
dict() hex() object() slice() dir()
id() oct() sorted() xrange() cmp()

abs

取绝对值:

a = -1
b = -2
print abs(a)
print abs(b)

all

判断一个可迭代对象里的元素是否全为真,全为真返回真,否则返回假:

l1 = [1, 2, 3, 0, 'a']
print all(l1)

l2 = ['a', 1, 'r']
print all(l2)

any

判断一个可迭代对象,里面的元素全为假时才返回假,只要有一个为真就为真:

l1 = [0, None, '', 1]
print any(l1)

float

转化为浮点数:

print(float(1))
print(float('1.1'))

getattr

获取指定对象属性:

class A(object):

    a = 1

print getattr(A, 'a')

reversed

反转一个可迭代对象,返回的是生成器:

l  = [4, 3, 2, 1, 'a']

for i in reversed(l):
    print i

round

四舍五入:

In [11]: round(1.1)
Out[11]: 1.0

In [12]: round(1.4)
Out[12]: 1.0

In [13]: round(1.5)
Out[13]: 2.0

In [14]: round(1.9)
Out[14]: 2.0

set

集合类型,返回集合对象:

In [16]: set([1, 2, 2, 1, 3])
Out[16]: {1, 2, 3}

dict

字典类型,返回字典对象:

In [18]: dict(a=1, b=2)
Out[18]: {'a': 1, 'b': 2}

divmod

除法函数,但返回商和余数:

In [19]: divmod(5, 3)
Out[19]: (1, 2)

enumerate

枚举可迭代对象:

In [23]: l = [1, 2, 'a', True]

In [24]: for i in enumerate(l):
    ...:     print i
    ...:
(0, 1)
(1, 2)
(2, 'a')
(3, True)

len

求可迭代对象长度:

In [25]: l  = [1, 2, 3, 3, 4]

In [26]: len(l)
Out[26]: 5

练习题

题一

找出小于1000的正整数中,是3或5的倍数的数,并计算其和,例如:小于10中,3或5的倍数的数有3、6、5、9,和是23

题二

求600851475143的质因数

小技巧

质因数(素因数或质因子)在数论里是指能整除给定正整数的质数。除了1以外,两个没有其他共同质因子的正整数称为互质。因为1没有质因子,1与任何正整数(包括1本身)都是互质。正整数的因数分解可将正整数表示为一连串的质因子相乘,质因子如重复可以指数表示。根据算术基本定理,任何正整数皆有独一无二的质因子分解式。只有一个质因子的正整数为质数。如:13195的质因数是5、7、13、29

题三

0~9这10个数字可以组成多少不重复的3位数?

题四

水仙花数是指一个n位数(n≥3),它的每个位上的数字的n次幂之和等于它本身。 例如:1^3+5^3+3^3=153。

求100~999之间所有的水仙花数。

题五

输入某年某月某日,判断这一天是这一年的第几天?

题六

随机选择一个三位以内的数字作为答案。用户输入一个数字,程序会提示大了或是小了,直到用户猜中。

小技巧

使用raw_input函数接收用户输入,如 num = raw_input('please input your num')

题七

要审查的帖子在这个文本文档里,要求将所有的和谐,三个代表,言论自由,64替换为*号

题八

程序执行后,输入一串字符,然后把它反转后打印出来,如果敲击回车,则终止

接口自动化

接口测试概述

首先我们先看一下经典金字塔

_images/jinzita.jpg

这个金字塔从底层到上层依次是单元测试、服务测试、UI测试,越接近接近底层,收益成本比越高,我这要说的就是服务测试,也叫做接口测试、API测试

什么是接口

通常我们接触到的接口分以下几种

  • 系统内部服务层级之间的接口调用,比如后端服务应用层调用service层的接口,service层掉用DAO层的接口,这个地方的接口测试属性白盒范畴,一般开发自己做就可以了
  • 服务之间的接口调用,比如你有两个服务,一个是用户中心,一个是订单中心,订单中心会去调用户中心的服务,查询用户信息,这些接口我们一般是要测试的
  • 系统之间的接口调用,比如你商户系统会去调公共服务系统的接口

说完的接口的种类,那么怎么理解接口呢,通俗一点就是输入数据,返回数据,不同种类的接口应用层协议可能不一样,传输的数据格式也可能不一样,在这里我们主要介绍HTTP接口,因为这种接口使用的最广泛,搞明白之后,其它接口大同小异,无非是client不一样而已

怎么开展接口测试

一般来说,我们开展接口测试的流程如下:

  1. 需求确定后,开发出API文档
  2. 拿到API文档,编写接口测试用例
  3. 开发交付
  4. 实施接口测试

接口测试用例跟常规的功能测试用例基本一样,可以从接口功能测试、接口业务测试、接口性能测试、接口安全测试等角度考虑

接口功能的关注点是

  1. 接口每个参数输入正确与错误
  2. 接口参数却是
  3. 接口参数边界值
  4. 接口参数类型

接口业务测试主要是从业务的角度出发,把接口组合成一条业务链,比如登录之后下个订单,然后再取消,数据库中的数据是否正确等等

接口的性能测试是指接口是否满足业务的要求,比如业务要求系统可以满足50个人同时下单,那么下单这个接口就要可以承担50 TPS,目前业内一般使用JMETER去做性能测试

针对接口的功能和业务逻辑测试,如果采用手工的方式可以使用POSTMAN、HTTPCLIENT等工具,也可以使用JMETER、SOAPUI工具,同时也可以自己写脚本去测试,这种方式更为灵活,更容易实现一些复杂的场景和业务逻辑

使用python做接口自动化

使用python做接口自动化测试是非常简单的,python入门非常简单,通过短时间的学习后,就可以开展自动化测试了,但需要掌握如下知识:

  • 理解HTTP
  • 熟悉python基础,python数据类型、函数、模块、类
  • 熟悉常用的python模块,如requests、json、configobj、pyyaml等
  • 熟悉python单元测试框架,如unittest、pytest、nose等
  • 熟悉python操作数据库的方法

要不要做持续集成

必须的,天天跑,每天都能看到自己项目的质量情况、质量报告,即能展示自己的工作成绩,又可以求心安

HTTP

在讲HTTP之前,我们先讲一下什么是协议

什么是协议

通俗来讲,协议可以理解为约定,比如说下午一到6点,你就知道要下班了,这是因为公司有制度规定6点下班,协议可以很简单,但也可以很复杂,比如语言就是一种很复杂的协议,有大量的词汇代表不同的意思

协议分层

在学HTTP前一定要理解协议分层这个概念,打个比方,打电话,你打开电话,拨通号码,跟需要通话的人建立了连接,然后你说一句话,声音通过话筒由声波信号转换成了电信号,然后电信号通过通讯媒介传输到对方电话,最后通过对方的听筒转化成声波。

声波转换成电信号,最后电信号又转换成声波,这就要遵守一定的协议,比如多少频率转化成多少伏的电压,如果双方协议不一样,转换出来的声音可能就完全不一样了。声波的传输是需要协议,同时双方通话的人也是,比如你是广东人,对方是上海人,两边各讲方言,结果都蒙逼了,然后双方使用一样的协议都讲普通话,OK,这要就可以HAPPY通信了。

_images/tel.png

在这里,双方通话语言的协议就在声波和电信号转换的协议之上

那为什么要分层呢?这个问题比较好回答,比如如果你跟你老婆打电话,如果不通过声波和电信号之间的转换协议,你声音得多大她才能听到??这是必要情况。还有一种情况,可能不是必要的,但也要分层,这么做的目的是简化实现,降低耦合,大家可以先这么理解,有兴趣的话可以看一些专业书籍。

再看一下下面这个图,可以理解一下为什么要分层(这个图摘自图解TPC/IP)

_images/tel2.png

TCP/IP协议

我们先看一下下面的百科介绍,写的比较专业,我来翻译一下:说白了,就是怎么样把你电脑上运行的程序的消息发送给网络上另一个电脑的程序的一系列协议

注解

Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。

我们来了看一下下面的图加深理解(这个图也摘自图解TCP/IP)

_images/floors.png

HTTP

我们在这只需了解一下什么是HTTP,以及一些入门的知识,并不深入讨论,深入讨论半年也说不完

HTTP是什么

最开始HTTP是为了传输文件产生的,它属于应用层协议,基于传输层TCP协议,但随着互联网的演进,最初的静态文件传输已经演变成动态生成“文件”再传输

HTTP原理比较类似于去小卖铺买东西,你说要一个泡面( 请求 ),然后店员给你一个泡面( 响应 ),如果商店没有,店员告诉你没有货了( 404 ),如果买东西的人太多了,店员懒得给你找,就回复给你小店炸了,没空理你( 500 ),再或者,店员干脆懒得理你,回都不回( timeout

或者,商店里有很多种泡面,你得告诉店员要哪一种,然后你说要包装是纸桶装的( header )泡面

再或者,你在店里办了会员,每次不会花钱就能买东西,然后你到店里后,要把会员卡给店员,店员看一下你是不是会员,是会员了再看你有什么要求( cookies

再再或者,你买的东西比较私密,不想让旁边的人听到,然后你说了只有你和店员知道的暗语( 加密

同理HTTP就是你的电脑和服务器电脑通信的,你输入 http://www.baidu.com ,百度的服务器把网页数据返回给你

HTTP方法

这里只介绍四种常见的,也就是增删改查

  • GET 查,我需要查点资料
  • POST 增,我要增加点东西,如向数据库增加记录
  • PUT 改,修改某一项已经存在的数据的内容
  • DELETE 删,删除一些数据

但其实在实际使用过程中,程序员们并不严格遵守,大家了解一下就好啦

HTTP状态码

首先看一下状态码分类

状态码 类别 原因短语
1XX 信息性状态码 接收的请求正在处理
2XX 成功状态码 请求正确,处理完毕
3XX 重定向状态码 需要进行附加操作完成请求
4XX 客户端错误状态码 服务器无法处理请求
5XX 服务器错误状态码 服务器处理请求出错

大家对各种状态码先有一个大致了解,至于每组状态码之间有什么细微差别(如400和404),可以看一下这个百科: http://baike.baidu.com/item/HTTP%E7%8A%B6%E6%80%81%E7%A0%81

HTTP请求首部

首部也就是我们平常见到的HTTP headers,HTTP首部是除了传输数据外的一些额外信息,比如下面这个:

GET /en/latest/ HTTP/1.1
Host: test-study.readthedocs.io
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
DNT: 1
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Cookie: csrftoken=NdDltTfq4fl3a70u7SrJ4amYJSezAQHK; __utmt=1; __utma=157584191.1561414775.1496201127.1496888218.1496925908.21; __utmb=157584191.1.10.1496925908; __utmc=157584191; __utmz=157584191.1496201127.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
If-None-Match: W/"59380209-24de"
If-Modified-Since: Wed, 07 Jun 2017 13:39:21 GMT

下面我们介绍几个比较重要的首部

Host

该首部指明了要访问的服务器地址和端口

Accept

表示客户端接受哪种类型的数据,如 text/html 表示客户端希望得到html文本,image/jpeg 表示希望得到图片,`` application/msword`` 表示希望得到word文档

Content-Type

表示客户端发送的数据类型,如 application/x-www-form-urlencoded 表示发送的是表单键对数据, application/json 表示发送的是序列化后的json数据

Cookie

表示客户端存储的Cookie发送给服务器

JSON

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

JSON语法

  • 对象表示为键值对
  • 数据由逗号分隔
  • 花括号保存对象
  • 方括号保存数组

怎么表示对象 对象以一对花括号包含,里面包含键值对,如 {"a":1, "b": "1"} ,这样子,键值必须以双引号括起来,数据之前以逗号分隔

怎么表示数组 数组以一对方括号包含,如 [1, 2, "2, {"a": 1}] 这样子

JSON对象和JSON字符串 JSON对象比较像python里的字典,但还是有区别的,JSON对象里的字符串只能用双引号,python可以用单引号,JSON对象里只能包括基础数据类型,即数字(包含小数)、字符串、数组、字典、空值,但python字典可以包含其它对象,下面定义一个JSON对象:

{
    "key1": 1,
    "key2": "1",
    "key3": [1, "1"],
    "key4": {
        "key5": 1
    }
}

JSON字符串是将JSON序列化后的字符串,将上面的JSON对象序列化后为:

'{"key3": [1, "1"], "key2": "1", "key1": 1, "key4": {"key5": 1}}'

在python中使用json

python标准库中自带json模块,可以序列化和反序列化json,在python中json对象就是字典,但python字典不一定是json对象

序列化JSON:

# coding=utf-8

import json

raw_json = {
        "key1": 1,
        "key2": "1",
        "key3": [1, "1"],
        "key4": {
            "key5": 1
        }
    }

json_str = json.dumps(raw_json)
print(repr(json_str))

输入为:

'{"key3": [1, "1"], "key2": "1", "key1": 1, "key4": {"key5": 1}}'
[Finished in 0.2s]

反序列化JSON:

# coding=utf-8

import json

raw_json = {
        "key1": 1,
        "key2": "1",
        "key3": [1, "1"],
        "key4": {
            "key5": 1
        }
    }

json_str = '{"key3": [1, "1"], "key2": "1", "key1": 1, "key4": {"key5": 1}}'
raw_json = json.loads(json_str)
print(repr(raw_json))

输出结果为:

{u'key3': [1, u'1'], u'key2': u'1', u'key1': 1, u'key4': {u'key5': 1}}
[Finished in 0.2s]

自己写接口

这一节,我们自己写一组接口以方便后面的接口测试

准备工作

pip install flask
pip install flask_restful

脚本编写

使用pycharm新建一个文件叫api.py,然后粘贴进去如下内容:

from flask import Flask
from flask_restful import reqparse, abort, Api, Resource

app = Flask(__name__)
api = Api(app)

TODOS = {
    'todo1': {'task': 'build an API'},
    'todo2': {'task': '?????'},
    'todo3': {'task': 'profit!'},
}


def abort_if_todo_doesnt_exist(todo_id):
    if todo_id not in TODOS:
        abort(404, message="Todo {} doesn't exist".format(todo_id))

parser = reqparse.RequestParser()
parser.add_argument('task')


# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
    def get(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        return TODOS[todo_id]

    def delete(self, todo_id):
        abort_if_todo_doesnt_exist(todo_id)
        del TODOS[todo_id]
        return '', 204

    def put(self, todo_id):
        args = parser.parse_args()
        task = {'task': args['task']}
        TODOS[todo_id] = task
        return task, 201


# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
    def get(self):
        return TODOS

    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201

##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')


if __name__ == '__main__':
    app.run(debug=True)

然后在pycharm上运行这个文件,或者在命令行执行 python api.py ,然后服务就启动起来了:

$ python api.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader

脚本解析

可以暂时不理会脚本是怎么实现的,这段脚本是从flask_restfull的官方文档粘下来的, http://flask-restful.readthedocs.io/en/0.3.5/quickstart.html#full-example

这段脚本主要实现了维护TODO LIST的功能,实现了如下几个接口

路径 方法 说明
/todos GET 获取TODO列表
/todo/<todo_id> GET 获取单个TODO任务
/todos/<todo_id> DELETE 删除指定TODO
todos POST 添加一个新TODO
/todo/<todo_id> put 更新一个TODO

使用python requests调用接口

在这一节,我们使用python的requests库调用上一节中写的接口

准备工作

  • 运行上一节的脚本

  • 安装requests库:

    pip install requests
    

测试获取TODO LIST

在交互环境或者IDE中输入如下代码:

# coding=utf-8
import requests

# 请求该接口
response = requests.get('http://localhost:5000/todos')

# 获取响应数据,并解析JSON,转化为python字典
result = response.json()

# 打印响应状态码
print(response.status_code)

# 打印result
print(result)

# 打印结果中的 ‘todo1’ 任务
print(result['todo1'])

然后运行,输出如下:

200
{u'todo1': {u'task': u'build an API'}, u'todo3': {u'task': u'profit!'}, u'todo2': {u'task': u'?????'}}
{u'task': u'build an API'}
[Finished in 0.4s]

增加一条TODO

在交互环境或者IDE中输入如下代码:

# coding=utf-8
import requests

# 定义要添加的任务
task = {'task': 'my task'}

# 请求该接口
response = requests.post('http://localhost:5000/todos', data=task)

# 获取响应数据,并解析JSON,转化为python字典
result = response.json()

# 打印响应状态码
print(response.status_code)

# 打印result
print(result)

响应如下:

201
{u'task': u'my task'}
[Finished in 0.6s]

requests

看了上面的两个例子,大家对requests有了大致的了解,现在总结一下基本的使用方法,更多可以查看一下官方文档 http://docs.python-requests.org/en/master/ 或者中文文档(不是最新的,不过影响不大) http://cn.python-requests.org/zh_CN/latest/

  • 发送不同的http请求,直接使用 requests.<方法名小写> 即可,如 requests.getrequests.post

  • 传送url查询参数的方法如下,试验过程中,大家可以抓包看一下效果:

    import requests
    
    p = {'a', 1}
    requests.get('http://xxxx', params=p)
    
  • 传送form表单格式的数据,使用方法如下:

    import requests
    d = {'a': 1}
    requests.post('http://xxxxx', data=d)
    
  • 传送json数据使用方法如下:

    import requests
    d = {'a': 1}
    requests.post('http://xxxxx', json=d)
    

练习

下面大家测试一下剩余的接口

Fiddler

fiddler是目前Windows操作系统上最易用,且免费的web调试工具合集,功能强大,简单易用,我们先看一下它都提供了哪些功能,我们捡主要的说

  • HTTP/HTTPS录制
  • WEB调试
  • WEB session操作
  • 性能测试
  • 安全测试

工作原理

在讲使用之前,我们先说一下它的工作原理,fiddler其实就是一个代理 + http client,它在真实的客户端和服务器之间,拦截转换客户端的请求和服务器的响应,然后处理后再放行,如下图所示:

_images/fiddler.png

Fiddler启动代理服务,然后客户端(PC或手机)把代理设置为Fiddler的代理,此后,客户端发送的HTTP请求就会先到达Fiddler,Fiddler把请求处理之后,再转发给服务器;当服务器有响应后,先把响应发送到Fiddler,然后Fiddler把响应处理后再发送给客户端。

客户端代理设置

在PC端,打开Fiddler后,Fiddler会自动设置本机的代理服务,因此在PC上不须设置,在手机端需要手动设置一下手机的代理服务

第一步

Jmeter

下载安装

下载地址为: http://jmeter.apache.org/download_jmeter.cgi

Step1:下载

打开下载页面,找到 apache-jmeter-3.2.zip 如下图所示:

_images/img1.jpg

点击下载,然后下压缩包解压(随便解压到哪一个目录都要以),解压后的目录大至如下:

apache-jmeter-3.2
├── LICENSE
├── NOTICE
├── README.md
├── bin/
├── docs/
├── extras/
├── lib/
├── licenses
└── printable_docs/

Step2:运行

进行到 apache-jmeter-3.2 目录下,然后进入 bin 目录下

执行测试

这一节,我们来执行一个Jmeter脚本,一般日常测试中,我们都是对单个接口或一组接口做测试,并不会去测页面上的表态资源,所以也不需要录制,所以我们后面都是自己添加sampler,录制相关可以查看官方文档

操作步骤

1. 打开命令行

windows下打开命令的提示符窗口,linux下打开shell

2. 进入到Jmeter的bin目录下

执行:

cd apache-jmeter-3.1
cd bin

3. 打开Jmeter

windows下执行jmeter.bat,linux下执行bash jmeter.sh(使用其它shell也行),如下图所示:

_images/executing1.jpg

4. 保存测试计划

使用快捷键 ctrl+s 保存

5. 添加线程组

_images/executing2.jpg

6. 添加HTTP请求

_images/executing3.jpg

添加好之后,输入如下配置:

_images/executing4.jpg

7. 添加监听器

_images/executing5.jpg

8. 运行

_images/executing6.jpg

9. 查看结果

点击左栏的察看结果树和聚合报告查看测试结果

web自动化

性能测试

Indices and tables