“笨办法”学Python 3基础篇系列文章
“笨办法”学Python 3基础篇 第一部分-打印与输入
“笨办法”学Python 3基础篇 第二部分-文件操作
“笨办法”学Python 3基础篇 第三部分-函数
“笨办法”学Python 3基础篇 第四部分-数据容器与程序结构
“笨办法”学Python 3基础篇 第五部分-面向对象的类
“笨办法”学Python 3基础篇 第六部分-项目骨架与自动测试
“笨办法”学Python 3基础篇 第七部分-搭建简易的网站
项目骨架与自动测试
前言
本节将介绍python项目的基本结构,python项目的运行环境以及自动测试所撰写的python代码。从本节开始,我们所关注的python程序将不再是一个孤立的.py程序,而是作为一个python工程项目来进行设计。这是本质区别。
6.1 准备工作-安装虚拟环境virtualenv和测试工具nose
我们再也不能随便找一个文件目录,然后新建一个python脚本就编程了。我们现在需要为python项目新建一个虚拟环境,并据此创建项目的目录。虚拟环境就是一个用来安装软件的“假的”地方,在虚拟环境中,你可以针对不同项目使用不同版本的相互冲突的软件包,且不会污染主系统级别的配置。
6.1.1 安装virtualenv
这里只介绍Windows 10的安装方法。利用pip命令来实现基本的安装,在PowerShell中输入
pip install virtualenv
接下来就是等待下载安装了。
安装好了virtualenv后,就需要在起始目录下创建一个.venvs文件夹,我的起始目录是C:\User\wqai, 这里的wqai是我的用户名。
mkdir .venvs
在.venvs文件夹里,我们通过virtualenv来创建名为csdn_py的虚拟环境,
virtualenv --system-site-packages .venvs/csdn_py
安装完后,激活虚拟环境csdn_py,在Scripts文件夹中运行activate脚本
.\.venvs\csdn_py\Scripts\activate
此时shell执行了activate脚本,将csdn_py设为了虚拟环境。后续我们的python项目就在这个虚拟环境中创建。激活成功,PowerShell中将出现(csdn_py),如图所示。
6.1.2 安装测试工具
书中介绍的是nose工具,不幸的是nose测试工具已经停止更新了。但这并不影响使用,在学习初级python编程时并没有什么大的影响。所谓触类旁通,学会nose测试工具后再学习新的测试工具相信也能很快的掌握。
安装nose测试工具很简单,但前提是powershell使用的是上一小节的虚拟环境。采用pip命令来下载和安装nose,nose将会被安装到.venvs\csdn_py\lib的虚拟环境下面,而非主系统软件包目录。具体命令为
pip install nose
安装过程如图:
6.2 创建一个名为“room”的项目骨架目录
6.2.1 Python基本项目骨架
当我们用python编写一个项目时,也需要同其他编程方式一样搭建项目架构。根据《“笨办法”学Python 3》中的介绍,一个Python项目的基本骨架目录结构为:
项目名称是可自定义的。在项目名称目录下,分支出bin、tests、docs和NAME目录,其中:
- NAME目录是项目的主模块,可以进行自定义的命名,程序的python脚
本存放在该目录下 - tests目录是存放自动测试脚本的模块
我们采用如下命令,来构建一个名为"room"的项目基本架构:
mkdir projects
cd projects
mkdir room
cd room
mkdir bin
mkdir docs
mkdir tests
mkdir room
执行命令后,项目目录变为
- projects\
-
room\
-
room\
-
bin\
-
docs\
-
tests\
6.2.2 创建__init__.py和setup.py
接下来,需要为room这个主模块和tests模块构建初始文件。采用如下命令分别在两个目录下生成__init__.py初始化脚本。
new-item -type file room\__init_.py
new-item -type file tests\__init__.py
完成这一命令后,就创建好了空的名称为room的Python模块目录。接下来需要干的事情,就是把python代码放入对应的模块中。
另外,如果你的python项目想让别人安装使用,还需要在room的主目录下创建一个setup.py。该脚本根据项目的不同有所不同,我还没有尝试过自己生成的脚本在另一个环境中安装,因此,这里把书中提供的模板提供出来:
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
config = {
'description': 'My Project',
'author': 'My Name',
'url': 'URL to get it at.',
'download_url': 'Where to download it.',
'author_email': 'My email.',
'version': '0.1',
'install_requires': ['nose'],
'packages': ['name'],
'scripts': [],
'name': 'projectname'
}
setup(**config)
6.2.3 创建一个空的测试骨架
在tests/目录下建立一个简单的测试专用骨架room_tests.py,代码如下:
from nose.tools import *
import room
def setup():
print("SETUP!")
def teardown():
print("TEAR DOWN!")
def test_basic():
print("I RAN!")
对于刚刚建立的空项目room,我们可以在room/目录下运行nosetests来检查项目配置是否正确。
6.3 自动化测试脚本
自动化测试脚本是用来帮助程序员减轻代码Bug检查的工作量,写代码测试的过程可以强迫程序员清晰地理解之前写的代码,帮助理解代码实现的功能和原理,也可以培养良好的编程习惯。
6.3.1 机器人找金币程序
这里采用一个机器人找金币的例子,如图所示,移动区域为(0,0)至(3,3),金币处于(1,1)的位置。
首先在room\目录下编写一个robot.py程序,定义一个robot类,功能为机器人当前位置坐标的初始化,上下左右移动,以及判断游戏进度。具体代码为:
class Robot:
def __init__(self, pos):
self.x_pos = pos[0]
self.y_pos = pos[1]
def move(self, direction):
if direction == 'north':
self.y_pos += 1
elif direction == 'east':
self.x_pos += 1
elif direction == 'south':
self.y_pos -= 1
elif direction == 'west':
self.x_pos -= 1
else:
print("illegal action!")
return (self.x_pos, self.y_pos)
def reward(self):
if (self.x_pos, self.y_pos) == (1,1):
print(f"x_pos: {self.x_pos}, y_pos: {self.y_pos}")
print("Win!")
return 'Win!'
elif self.x_pos < 0 or self.x_pos > 3 or self.y_pos < 0 or self.y_pos > 3:
print("Die!")
return 'Die!'
else:
print(f"x_pos: {self.x_pos}, y_pos: {self.y_pos}")
print("Keep finding!")
return 'Keep finding!'
这段代码定义了一个Robot类,包含_init__, move和reward三个函数。在__init__函数中,参数pos的数据类型为元组(tuple),元组数据类型是一种受保护的数据容器,元组中的数据无法被修改,只能被使用。在初始化时,将pos中的元组数据元素赋值给机器人的x和y坐标。move 函数则主要根据运动方向:北,东,南,西来改变机器人的位置坐标,并以元组形式返回当前坐标。award 函数则是判断此时机器人的位置坐标与死亡或奖励的关系。当坐标处于地图之外时,游戏失败。当坐标位于金币所在位置时,游戏成功。当坐标位于地图内未达到目标位置时,则继续寻找。
6.3.2 为robot.py编写一个自动测试脚本
在tests\目录下新建一个robot_test.py的测试脚本,对Robot类中的__init__, move 和 reward函数进行测试。测试采用assert_equal(A, B)实现,该函数实现的是断言功能,用来判断A与B是否相同。值得注意的是,在编写robot_test.py测试脚本时,需要从room\robot.py 引入 Robot 类。具体的自动测试代码如下:
from nose.tools import *
from room.robot import Robot
def test_robot():
robot_start = Robot((0,0))
assert_equal(robot_start.x_pos, 0)
assert_equal(robot_start.y_pos, 0)
def test_move():
robot_start = Robot((0,0))
assert_equal(robot_start.move('north'), (0, 1))
assert_equal(robot_start.move('east'), (1, 1))
assert_equal(robot_start.move('south'), (1, 0))
assert_equal(robot_start.move('west'), (0, 0))
def test_reward():
robot_win = Robot((1, 1))
robot_failure = Robot((-1, 3))
robot_keep = Robot((0, 2))
assert_equal(robot_win.reward(), 'Win!')
assert_equal(robot_failure.reward(), 'Die!')
assert_equal(robot_keep.reward(), 'Keep finding!')
在PowerShell中运行nosetests,结果为:
测试结果表明,在robot_test.py自动测试脚本中的test_robot, test_move, test_reward均测试成功,说明Robot 类功能正常。
6.3.3 编写一个游戏引擎
在room\下新建一个engine.py脚本,引用了robot.py脚本中的Robot类。该引擎脚本的主要功能是在地图的随机点生成机器人对象,并通过用户输入来移动机器人寻找金币。具体的代码如下(附加了注释,这里就不再详细描述每段代码的功能,总之就是要好好运用类)
from sys import exit
import random
from robot import Robot
class Engine:
#机器人随机出生点
def __init__(self):
self.x_pos = random.randint(0, 3)
self.y_pos = random.randint(0, 3)
#运行游戏
def play(self):
robot_obj = Robot((self.x_pos, self.y_pos)) #生成机器人对象
self.target = robot_obj.reward()
# 机器人开始运动,寻找金币
while self.target == 'Keep finding!':
self.direction = input("Awaiting command:> ") #输入运动方向指令
robot_obj.move(self.direction) #机器人移动到新位置
self.target = robot_obj.reward()
# 机器人走出地图,死亡, 游戏结束
if self.target == 'Die!':
print("""
Game Over!
""")
exit(1)
# 机器人找到金币点,游戏结束
else:
print("""
You Are Lucky!
""")
exit(1)
game = Engine()
game.play()
PowerShell的运行结果如图。两次机器人的出生点都不一样。第一次找到金币结束游戏,第二次走出地图死亡。
结语
写好项目框架和自动测试脚本,将使得编程更加规范,也一定程度上加快了有效编程的进度。我个人认为是非常好用的,也是非常方便的,尤其是通过测试工具反向设计源代码的方法很有意思。后续,在读别人的代码时,可以通过编写测试脚本来进一步加深代码的理解。