用python实现常用测试模块

python AES 加解密模块

Definition:

AES:高级加密标准(Advanced Encryption Standard)是最常见的对称加密算法之一,微信小程序就是用合格加密算法。

对称加密算法也就是加密和解密使用相同的密钥,具体的流程图如下图所示:

pytest-1

AES使用分组密码,分组密码也就是将明文分成一组一组的,每组的长度相等,每次加密一组数据,知道将整个明文都加密完成。在AES标准规范中,分组长度只能是128位,也就是说,每个分组16个字节(每个字节8位)。密钥的长度可以使用128位、192位、256位等。

密钥的长度不等,推荐加密的轮数也不相等。

一般我们最常使用的就是AES-128,是密钥的长度为128位,加密轮数位10轮。

AES加解密的流程中包括的主要步骤:

明文分组 、字节代换、行位移、列混淆、轮密钥加

AES算法代码实现:

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
import sys
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex

class prpcrypt():

def __init__(self, key):
self.key = key.encode('utf-8') self.mode = AES.MODE_CBC
# 加密函数,如果 text 不是 16 的倍数【加密文本 text 必须为 16 的倍数!】,那就补足为 16 的 倍数

def encrypt(self, text):
text = text.encode('utf-8')
# 创建一个新的 AES 密码
# key 为对称加密中使用的密钥,mode_CBC 是 AES 五种加密模式中的密码分组链接模式
cryptor = AES.new(self.key, self.mode, self.key)
# 这里密钥 key 长度必须为 16(AES-128)、24(AES-192)、或 32(AES-256)Bytes 长
#度.目前 AES-128 足够用 length = 16
count = len(text)
if (count % length != 0):
add = length - (count % length)
else:
add = 0
text = text + ('\0' * add).encode('utf-8')
# 真正的加密操作,使用初始化时的密钥加密数据
self.ciphertext = cryptor.encrypt(text)
# 因为 AES 加密时候得到的字符串不一定是 ascii 字符集的,输出到终端或者保存时候可能存
在问题
# 所以这里统一把加密后的字符串转化为 16 进制字符串
return b2a_hex(self.ciphertext)

# 解密后,去掉补足的空格用 strip() 去掉
def decrypt(self, text):
cryptor = AES.new(self.key, self.mode, self.key)
plain_text = cryptor.decrypt(a2b_hex(text))
# return plain_text.rstrip('\0')
return bytes.decode(plain_text).rstrip('\0')

if __name__ == '__main__':

pc = prpcrypt('keyskeyskeyskeys') # 初始化密钥
e = pc.encrypt("0123456789ABCDEF") # 对输入明文进行加密
d = pc.decrypt(e) # 解密密文操作
print(e, d)
e = pc.encrypt("00000000000000000000000000")
d = pc.decrypt(e)
print(e, d)

下面我们来看下代码的运行结果:

pytest-2

python unittest 单元测试模块

unittest 是 python 的一个基础常用的单元测试框架,

便于我们编写测试用例以及测试执行。其中中最核心的四个概念是:

test case

test suite

test runner

test fixture

pytest-3

unittest 进行单元测试的流程:

  1. 写好 TestCase,然后由 TestLoader 加载 TestCase 到 TestSuite
  2. 然后由 TextTestRunner 来运行 TestSuite,运行的结果保存在 TextTestResult 中
  3. 我们通过命令行或者 unittest.main()执行时,main 会调用 TextTestRunner 中的 run 来执行,或者我们可以直接通过 TextTestRunner 来执行用 例。

一个 class 继承 unittest.TestCase 即是一个 TestCase,其中以 test 开头的方法 在 load 时被加载为一个真正的 TestCase。

在 TestRunner 中的 verbosity 参数可以控制执行结果的输出,0 是简单报告、1 是一般报告、2 是详细报告。

下面完成一次基本的 unittest 单元测试:

1
2
3
4
5
6
7
8
9
# 准备好待测函数
def add(a, b):
return a+b
def minus(a, b):
return a-b
def multi(a, b):
return a*b
def divide(a, b):
return a/b

然后写出待测函数的测试方法:

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
import unittest
from mathfunc import *

class TestMathFunc(unittest.TestCase):
"""Test mathfuc.py"""
def setUp(cls):
print("do something before test.Prepare environment.")

def tearDown(cls):
print("do something after test.Clean up.")

def test_add(self):
"""Test method add(a, b)"""
print("add")
self.assertEqual(3, add(1, 2))
self.assertNotEqual(3, add(2, 2))

def test_minus(self):
"""Test method minus(a, b)"""
print("minus")
self.assertEqual(1, minus(3, 2))

def test_multi(self):
"""Test method multi(a, b)"""
print("multi")
self.assertEqual(6, multi(2, 3))

def test_divide(self):
"""Test method divide(a, b)"""
print("divide")
self.assertEqual(2, divide(6, 3))
self.assertEqual(2.5, divide(5, 2))

if __name__ == '__main__':
unittest.main()

以上代码已经可以完成一次简单的单元测试了,但是还无法按照指定的顺序 来执行测试用例。因此我们这里使用 suite 来控制每个 case 的先后执行顺序:同 时,如果同时有多个测试文件,我们也可以通过 suite 来控制他们进行一起执行。

1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*-
import unittest
from test_mathfunc import TestMathFunc
from HTMLTestRunner import HTMLTestRunner

if __name__ == '__main__':
suite = unittest.TestSuite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMathFunc))
with open('UnittestTextReport.txt', 'a') as f:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
runner.run(suite)

然后我们试着运行,可以看到运行结果如下:

pytest-4

如果出现有 ok 的记号,则表示本次测试的结果为成功;如果出现 fail,则表 示测试结果出现问题,这时我们就需要寻找问题,看看测试结果问题在哪里。

python assert 断言

对于断言,官方有一段官方的解释:Assert statements are a convenient way toinsert debugging assertions into a program,其作用就是类似于我们 debug 的工具,

一般的用法是:

1
assert condition

用来让程序测试这个 condition,如果 condition 为 false,那么 raise 一个AssertionError 出来。逻辑上等同于:

1
2
if not condition:
raise AssertionError()

我们来简单做一个小小的断言测试:

1
assert 1==2, "以下结果出现断言"

结果如下:

pytest-5

python pyserial 串口模块

pyserial 模块封装了对串口的访问。在支持的平台上有统一的接口,通过 python属性访问串口设置。支持不同的字节大小、停止位、校验位和流控设置。

在串口的配置中,有以下一些比较重要的属性需要我们了解:波特率、数据位、停止位、校验位、字节大小、读写超时设置等。 下面我们来看下对于串口的设置方法:

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
import sys
import glob
import time
import serial
import serial.tools.list_ports
8
def serial_ports():
""" Lists serial port names
:raises EnvironmentError:
On unsupported or unknown platforms
:returns:
A list of the serial ports available on the system
"""
# 如果是 windows 平台的系统,对应的串口号形式 if sys.platform.startswith('win'):
ports = ['COM%s' % (i + 1) for i in range(256)]
# 如果是 Linux 平台的系统,对应的串口号形式
elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
# this excludes your current terminal "/dev/tty"
ports = glob.glob('/dev/tty[A-Za-z]*') # 如果是 OS X 的系统,对应的串口号形式
elif sys.platform.startswith('darwin'):
ports = glob.glob('/dev/tty.*') else:
raise EnvironmentError('Unsupported platform') result = []
# 可以通过 Serial 函数对每一个串口进行控制其操作 for port in ports:
try:
s = serial.Serial(port) s.close() result.append(port)
except (OSError, serial.SerialException): pass
return result
# 这句命令可以在终端打印出所有的串口设备
print(list(serial.tools.list_ports.comports()))
if __name__ == '__main__': print(serial_ports())

下面看下我电脑上打印出来的相关串口号,可以看到一共有 3 个,表示的是每个 串口分配到的地址:

pytest-6