福州大学软件工程实践个人编程作业
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/SE2020 |
---|---|
这个作业要求在哪里 | https://edu.cnblogs.com/campus/fzu/SE2020/homework/11167 |
这个作业的目标 | 学习 GitHub 的使用、命令行参数格式、Python 对命令行参数的处理、Python 读取 json 文件、Python os 模块的学习、Python 如何进行覆盖率测试和性能测试 |
学号 | 031802509 |
目录
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 90 |
Estimate | 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 120 | 180 |
Analysis | 需求分析 (包括学习新技术) | 180 | 360 |
Design Spec | 生成设计文档 | 30 | 30 |
Design Review | 设计复审 | 30 | 60 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 10 | 20 |
Design | 具体设计 | 30 | 60 |
Coding | 具体编码 | 60 | 180 |
Code Review | 代码复审 | 30 | 90 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 120 |
Reporting | 报告 | 20 | 30 |
Test Report | 测试报告 | 20 | 60 |
Size Measurement | 计算工作量 | 20 | 20 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 700 | 1380 |
解题思路
首先去网上查找了 Git 、 GitHub 、 GitHub Action 的入门教程并学习。
在学习 GitHub Action 的时候学习的太粗糙了使得对 workflow 的理解出现错误,导致花了一整天尝试各种 workflow 但是结果都是错误的。
下载了示例数据,打开发现是 json 格式的数据,于是从网上寻找 Python 读写 json 文件的方法,学习了如何用将 json 中的文件读取出来
根据给出的指令格式发现不只是编译 Python 代码,还需要对输入的命令行参数进行处理,于是从网上查找了学习了 Python argparse 模块
查看指令的格式要求和给出的参考程序,发现 json 文件并不是直接给出的,还需要从文件夹中寻找,所以在网上学些了 Python os 模块的常用方法,并学会如何在文件夹中寻找指定文件
为了进行对性能和覆盖率的测试,学习了 pytest 和 coverage 的用法
设计实现过程
我们需要编写的程序需要满足以下两个要求
- 能够从文件夹中找到所需要的 json 文件(可能为多个)并且读取
- 能够根据命令行参数对 从 json 文件中读取到的数据进行筛选,得出需要的结果
因此主要要完成以下下两个类:
#从文件夹中找到所需要的 json 文件(可能为多个)并且读取
class Data:
#根据 reload 来判断指令是初始化指令还是查询指令,并执行相应的操作
def __init__(self, dict_address: int = None, reload: int = 0)
#初始化指令执行的操作,能够搜索到文件夹中所有的 json 文件并将所有的信息整合到对应三种不同指令的三个新 json 文件中
def __init(self, dict_address: str)
# 添加个人的 4 种事件的数量
def add_user_event(self, dic, user_event)
# 添加每一个项目的 4 种事件的数量
def add_repo_event(self, dic, repo_event)
# 添加每一个人在每一个项目的 4 种事件的数量
def add_user_repo_event(self, dic, user_repo_event)
#返回个人的 4 种事件的数量
def get_user_event(self, user, event)
#返回每一个项目的 4 种事件的数量
def get_repo_event(self, repo, event)
#返回每一个人在每一个项目的 4 种事件的数量
def get_user_repo_event(self, user, repo, event)
#根据命令行参数对 从 json 文件中读取到的数据进行筛选,得出需要的结果
class Run:
#添加参数
def __init__(self)
# 根据接收到的参数来判断时哪种指令并传递给 Data 对象处理
def next(self)
代码说明
import json
import os
import argparse
from time import *
#Date 类:用于处理 json 数据和返回指令要求的数据
class Data:
def __init__(self, dict_address : str = None, reload : int = 0):
# reload=1 即初始化的时候执行
if reload == 1:
self.__init(dict_address)
# dict_address为 None 意味着执行的不是 init 指令,此时如果在文件中找不到 init 操作生成的 json 文件的话意味着出现错误
if dict_address is None and not os.path.exists("user_event.json") and not os.path.exit("repo_event.json") and not os.path.exists("user_repo_event.path"):
raise RuntimeError("error: init failed")
def __init(self,dict_address):
#定义三个字典型数据用于存储执行三个指令所需要的数据
user_event = {}
repo_event = {}
user_repo_event = {}
for root, dic, files in os.walk(dict_address):
# 遍历整个文件夹,找到所有后缀为 .json 的文件
for f in files:
if f[-5:] == ".json":
event = ["PushEvent","IssueCommentEvent","IssuesEvent","PullRequestEvent"]
json_path = f
x = open(dict_address + "/" + json_path, "r", encoding="UTF-8").readlines()
for i in x:
i = json.loads(i)
if i["type"] in event:
self.add_user_event(i, user_event)
self.add_repo_event(i, repo_event)
self.add_user_repo_event(i, user_repo_event)
# 将处理好的数据保存为其他 json 文件,以便于后续指令调用
with open("user_event.json", "a") as f:
json.dump(user_event,f)
with open("repo_event.json", "a") as f:
json.dump(repo_event,f)
with open("user_repo_event.json", "a") as f:
json.dump(user_repo_event, f)
# 添加个人的 4 种事件的数量
def add_user_event(self, dic, user_event):
id = dic["actor"]["login"]
event = dic["type"]
if id not in user_event:
user_event[id] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
user_event[id][event] += 1
# 添加每一个项目的 4 种事件的数量
def add_repo_event(self, dic, repo_event):
repo = dic["repo"]["name"]
event = dic["type"]
if repo not in repo_event:
repo_event[repo] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
repo_event[repo][event] += 1
# 添加每一个人在每一个项目的 4 种事件的数量
def add_user_repo_event(self, dic, user_repo_event):
id = dic["actor"]["login"]
repo = dic["repo"]["name"]
event = dic["type"]
if id not in user_repo_event:
user_repo_event[id] = {}
user_repo_event[id][repo] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
if repo not in user_repo_event[id]:
user_repo_event[id][repo] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
user_repo_event[id][repo][event] += 1
# 个人的 4 种事件的数量
def get_user_event(self, user, event):
x = open("user_event.json", "r", encoding="utf-8").read()
data = json.loads(x)
return data[user][event]
# 每一个项目的 4 种事件的数量
def get_repo_event(self, repo, event):
x = open("repo_event.json", "r", encoding="utf-8").read()
data = json.loads(x)
return data[repo][event]
# 每一个人在每一个项目的 4 种事件的数量
def get_user_repo_event(self, user, repo, event):
x = open("user_repo_event.json", "r", encoding="utf-8").read()
data = json.loads(x)
return data[user][repo][event]
#Run 类:用于接收指令并传递给 Date 类执行
class Run:
def __init__(self):
# 添加命令行参数
self.parser = argparse.ArgumentParser()
self.parser.add_argument('-i', '--init')
self.parser.add_argument('-u', '--user',type=str)
self.parser.add_argument('-r', '--repo',type=str)
self.parser.add_argument('-e', '--event',type=str)
self.next()
# 根据接收到的参数来判断时哪种指令并返回指令要求的数据
def next(self):
args = self.parser.parse_args()
if args.init:
data = Data(args.init, 1)
elif args.user and args.event and not args.repo:
data = Data()
print(data.get_user_event(args.user, args.event))
elif args.repo and args.event and not args.user:
data = Data()
print(data.get_repo_event(args.repo, args.event))
elif args.user and args.repo and args.event:
data = Data()
print(data.get_user_repo_event(args.user, args.repo, args.event))
if __name__ == '__main__':
a = Run()
单元测试覆盖率和性能
单元测试代码
import pytest
import GHAnalysis
def test_init():
data = GHAnalysis.Data("json", 1)
assert 1
def test_user_event():
data = GHAnalysis.Data()
res = data.get_user_event("waleko","PushEvent")
assert res == 2
def test_repo_event():
data = GHAnalysis.Data()
res = data.get_repo_event("katzer/cordova-plugin-background-mode", "PushEvent")
assert res == 0
def test_user_repo_event():
data = GHAnalysis.Data()
res = data.get_user_repo_event("cdupuis", "atomist/automation-client", "PushEvent")
性能:
覆盖率:
代码优化
上述的方法中,为了满足三个不同的指令,分别生成了三个不同的 json 文件,导致初始化的时间较长,于是我省去了 user_event.json 和 repo_event.json 两个文件,只保留 user_repo_event.json ,因为对个人的 4 种事件和项目的 4 种事件的获取可以通过遍历 user_repo_event.json 得出。虽然这增加了查询结果的计算量,增加了需要的时间,但在本次的例子中,这个方法比上一个方法用时更短。(在初始化的次数较少但是会进行大量查询的情况下,上一种方法的速度会更快)
因为上一个方法的代码的覆盖率仅仅只有 26% ,所以除了修改几个方法之外,我还对其他部分代码进行调整,提高了覆盖率
修改后的代码如下:
主要结构
class Data:
#根据 reload 来判断指令是初始化指令还是查询指令,并执行相应的操作
def __init__(self, dict_address: int = None, reload: int = 0)
#初始化指令执行的操作,能够搜索到文件夹中所有的 json 文件并将所有的信息整合到一个新的 json 文件中
def __init(self, dict_address: str)
# 添加每一个人在每一个项目的 4 种事件的数量
def add_user_repo_event(self, dic, user_repo_event)
#返回查询的结果
def analysis(self, user=Node, repo=Node, event=None)
#根据命令行参数对 从 json 文件中读取到的数据进行筛选,得出需要的结果
class Run:
#添加参数
def __init__(self)
# 根据接收到的参数来判断时哪种指令并传递给 Data 对象处理
def next(self)
详细代码:
import json
import os
import argparse
from time import *
#Date 类:用于处理 json 数据和返回指令要求的数据
class Data:
def __init__(self, dict_address : str = None, reload : int = 0):
if reload == 1:
# reload=1 即初始化的时候执行
self.__init(dict_address)
# dict_address为 None 意味着执行的不是 init 指令,此时如果在文件中找不到 init 操作生成的 json 文件的话意味着出现错误
if dict_address is None and not os.path.exists("user_repo_event_ver2.json"):
raise RuntimeError("error: init failed")
# 打开 init 操作所生成的 json 文件,并将其读取到 Date 类的数据成员 json 中
x = open('user_repo_event_ver2', 'r', encoding='utf-8').read()
self.json = json.loads(x)
def __init(self,dict_address):
# 存储的 json 数据中每一项都是以 user:{repo1:{event1:(int), event2:(int)...},repo2:{...},... } 格式存储, 整个json文件只有一条 json 数据
user_repo_event = {}
# 遍历整个文件夹,找到所有后缀为 .json 的文件
for root, dic, files in os.walk(dict_address):
for f in files:
if f[-5:] == ".json":
event = ["PushEvent","IssueCommentEvent","IssuesEvent","PullRequestEvent"]
#print("读取" + str(f))
json_path = f
# 用 readlines 读取周到的 json 文件, 将每一条 json 数据转化为 string 并存到列表中
x = open(dict_address + "/" + json_path, "r", encoding="UTF-8").readlines()
for i in x:
# 将每一个 string 转化为 json 数据并中分析出有用的参数,按照上述的格式存储到 user_repo_event 中
i = json.loads(i)
if i["type"] in event:
self.add_user_repo_event(i, user_repo_event)
# 将处理好的数据保存为另一个文件,以便于后续指令调用
with open("user_repo_event_ver2.json", "a") as f_json:
json.dump(user_repo_event, f_json)
# 将读取到的一条 json 数据进行处理并存入 user_repo_event 中
def add_user_repo_event(self, dic, user_repo_event):
id = dic["actor"]["login"]
repo = dic["repo"]["name"]
event = dic["type"]
if id not in user_repo_event:
user_repo_event[id] = {}
user_repo_event[id][repo] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
if repo not in user_repo_event[id]:
user_repo_event[id][repo] = {"PushEvent":0,"IssueCommentEvent":0,"IssuesEvent":0,"PullRequestEvent":0}
user_repo_event[id][repo][event] += 1
# 根据不同的参数返回查询结果
def analysis(self,user=None,repo=None,event=None):
num = 0
if user:
if repo:
return self.json[user][repo][event]
else:
dic = self.json[user]
for key in dic:
num += dic[key][event]
return num
else:
for key in self.json:
if repo in self.json[key]:
num += self.json[key][repo][event]
return num
#Run 类:用于接收指令并传递给 Date 类执行
class Run:
def __init__(self):
# 添加命令行参数
self.parser = argparse.ArgumentParser()
self.parser.add_argument('-i', '--init',type=str)
self.parser.add_argument('-u', '--user',type=str)
self.parser.add_argument('-r', '--repo',type=str)
self.parser.add_argument('-e', '--event',type=str)
self.next()
# 根据接收到的参数来判断时哪种指令并返回指令要求的数据
def next(self):
args = self.parser.parse_args()
if args.init:
date = Data(args.init, 1)
else:
data = Data()
print(data.analysis(user=args.user, repo=args.repo, event=args.event))
if __name__ == '__main__':
a = Run()
单元测试覆盖率和性能
单元测试代码
import pytest
import GHAnalysis
def test_init():
data = GHAnalysis.Data("json", 1)
assert 1
def test_user_event():
data = GHAnalysis.Data()
res = data.analysis(user="waleko",event="PushEvent")
assert res == 2
def test_repo_event():
data = GHAnalysis.Data()
res = data.analysis(repo="katzer/cordova-plugin-background-mode", event="PushEvent")
assert res == 0
def test_user_repo_event():
data = GHAnalysis.Data()
res = data.analysis(user="cdupuis", repo="atomist/automation-client", event="PushEvent")
性能:
覆盖率:
可见修改后的代码的性能比修改前更好,而且在覆盖率上也有了显著的提升
代码规范链接
https://github.com/Victorique0/2020-personal-python/blob/master/codestyle.md
总结
遇到的困难:
-
对 json.loads() json.load json.dumps() json.dump() 之间的区别没有分清,导致刚开始的时候一直无法正确读取 json 文件
python中dump 和dumps load和loads的区别 -
在用 Python 读取文件的时候,对于文件路径的表达方式了解不多,导致一直找不到想要的 json 文件
Python中读取文件时的路径格式问题
之前对于 GitHub 和 Python 语言的使用并没有太多的了解,通过这次作业,查询了大量资料,学习到了不少 GitHub 和 Python 的知识。