【软件工程】《构建之法》 & Git+ & CI/CD
《构建之法》 & Git+ & CI/CD
个人阅读作业#2
项目 | 内容 |
---|---|
本作业所属课程 | 2020春季软件工程(罗杰 任健) |
本作业要求 | 个人阅读作业#2 |
我的课程目标 | 具备一个软件工程师所需要的素质 |
本作业帮助 | 整体上软件工程,熟悉版本控制以及CI/CD工具 |
一、阅读提问
单元测试
(P26)单元测试的运行/通过/失败/不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性。
面对需要处理大量数据的模块,人为构造数据就最造成很大的重复性工作。比如上学期写编译器的时候,为了测试我的词法分析模块的正确性,对一个十分简单的源程序,我就需要写上百行的期望输出,而且单元测试运行完之后,对那些错误部分进行分析,发现大部分并不是我要测试的语法分析模块的错误,反而而是我手动构造的期望输出的错误。在对语法分析部分进行测试的时候,这种情况尤甚。有的时候为了构造样例去写一些测试样例的生成器,但是生成器本身也会发生错误。
针对处理数据量较大的模块,我们该怎样手动构造测试样例呢?还是说,我们应该换一种单元测试的粒度?
断言
(P70)当你觉得某事肯定如何时,就可以用断言。
虽然书中上下文比较的是断言和异常处理的问题,但是断言(assert)很长时间来是我用着很纠结的地方。就我以往用断言的经验来看,主要在以下一些地方:
- 自己不打算写单元测试,用断言来确定程序运行到某个部分确实是按照我所期望的;
- 为了获得IDE的辅助,比如当我确定某个向下转型是安全的时候,写了
assert obj instanceof MyClass;
,后面的语句需要将obj
强制转换为MyCalss
时,IDE就会帮我添上强转; - 相当于一句注释;
但是有单元测试的情况下,第一类的情况还需要写断言吗?
结对编程
(P80)总之,如果运用得当,结对编程可以取得更高的投入产出比。
这句话给我的第一感觉,就像这句话一样:
如果方法得当,高考前一个月也可以提高300分。
虽然我的类比有些夸张,但意思是一样的:后半句话看起来那样美好,但是它的前提条件就有些说不太清楚了。
书中随后介绍了结对编程的流程与分工,也列举了一些注意事项,甚至还提到了一些结对编程中的交流技巧。但是却忽略了一个问题:什么样的人/团队适合结对编程呢?
后文提到了两人合作的阶段:萌芽\(\rightarrow\)磨合\(\rightarrow\)规范\(\rightarrow\)创造。但同时也指出了,并不是所有的合作都能走到最后一步,可能磨合太多后进入“解体”阶段。最终走向失败的合作有可能是是双方的方式不对,但也可能是双方口味不合,甚至有一方深深排斥自己工作的时候有另一个在边上。
国内为何很少有人做结对编程呢?是确实不好还是属于中国特色? - 小白的回答 - 知乎
这个回答中就提到,并不是所有人都能在交流中保持专注,也并不是所有人都喜欢在一个充满交流的环境中工作,甚至说,很多人走上了软件开发的道路就是因为自己需要安静的环境来保持专注。
选择结对编程,就意味着要让两个人去完成一个人的工作,这本身就是不利于投入产出比的,如果结对编程不能带来额外的好处,就是亏的。也就是说如果一组合作伙伴快速走向解体,那么就很可能是一次亏本的尝试。那么相较于关注怎样进行结对编程,我们是不是更应该关注哪些人适合结对、怎样挑选结对的伙伴呢?
典型用户
(P206)怎样才能定义典型用户呢?我们首先要定义用户的角色。正如戏剧中有正面和反面角色,软件系统中也有受欢迎的和不受欢迎的典型用户。
受欢迎的用户是我们软件的目标,自然需要作为典型用户来分析。
但是为什么也要分析不受欢迎的用户?一方面,针对不受欢迎的用户的一些策略,如保密、网络安全等,本身就是软件质量需要考量的一部分,甚至是受欢迎用户的需求,单独把这部分用户拎出来分析也不会起到很大的补充效果。另一方面,确定会有哪些不受欢迎的用户并不容易,比如微信起初就没有把淘宝链接、抖音连接、有偿朋友圈转发等视为不欢迎的,这些问题是随着运营而产生/发现的。
对不受欢迎的用户的分析投入产出比并不高,可以将其去掉吗?
功能说明书
(P215)
第一,定义好相关的概念。第二,规范好一些假设。第三,避免一些误解,界定一些边界条件。第四,描述主流的用户/软件交互步骤。第五,一些好的功能还会有副作用。第六,服务质量的说明。
功能说明书的意义就在于严格地、无歧义的描述软件的外部功能,讲述交互的方式。但是这样的功能说明书必定是很长的,相信大部分用户也不会有闲心来详细阅读功能说明书。那么怎样能快速、有效地引导用户来按照我们设计的方式来与软件进行交互呢?这一部分应当是谁的工作呢?
二、调研源代码版本管理软件
上网调研并了解目前被广泛使用的基于源代码版本管理软件Git的项目管理工具,如GitHub、Gitlab、Bitbucket 等,比较它们之间的异同(包括但不限于团队协作流程,项目管理等)。
我个人用gitee
和github
比较多,之前OO课使用过gitlab
但是并没有充分体验其中的功能。
总的来说二者差异都不大,都支持sshkey、私有/开源、fork、star、pull-request、issues、wiki、在线编辑、gitignore/LICENSE模板、统计等。
GitHub | Gitee | |
---|---|---|
服务器 | 国外 | 国内 |
权限管理 | 需要付费或者开源 | 免费即可进行权限管理 |
私有仓库人数 | 单个仓库不超过5人 | 个人的全部私有仓库的成员总数不超过5人 |
CI/CD | Github Action | 支持百度、腾讯云的服务,官方服务还在测试中 |
github
由于受众更广,有更多知名项目,gitee
也提供了直接从github
导入的功能。
gitlab
相比二者,最大的特点在于可以免费部署私服,私服可以获得管理员权限,自由度更高。
三、调研持续集成/部署工具
调研一下持续集成(Continuous Integration, CI)、持续部署(Continuous Delivery, CD)工具(例如:Gitlab CI、Github Action、Travis等),请你选择至少2个持续集成解决方案来进行动手实践,对每个解决方案的要求如下:
.NET 5 + Github Action
仓库链接 : https://github.com/SnowPhoenix0105/SimpleCalculator
实验结果:
yml配置文件:
name: .NET
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build --no-restore
- name: Test
run: dotnet test --no-build -v n
由于使用的是Microsoft当前正在推进的.NET 5
,所以Github Action
对此支持是比较好的,大致直接使用模板即可。下面简单介绍一下各个step的作用
step1 环境配置
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
这一步是向环境中加载.NET 5
环境。
step2 还原依赖
- name: Restore dependencies
run: dotnet restore
对于外部依赖项,我们一般不作为仓库的一部分,而是将其信息保存到相关文件中(.NET 就是 sln/csproj 文件),在构建时通过包管理器重新加载。
step3 构建
- name: Build
run: dotnet build --no-restore
由于dotnet build
命令默认会执行一遍restore
,但是我们前面已经手动完成了,所以这里要指定不再进行restore
操作。
step4 测试
- name: Test
run: dotnet test --no-build -v n
同理step3,dotnet test
命令默认会执行一遍build
,这里通过选项指定不再重新构建。
-v n
全称为--verbosity normal
,用于指示输出的信息等级,normal等级下将输出所有进行的测试,并输出测试结果以及测试用时。默认选项为m[inial]
,对于通过的测试,仅做最终统计,对于未通过的测试才有详细信息。
值得注意的是,由于我在顶层目录创建了解决方案SimpleCalculator.sln
并将SimpleCalculator
和SimpleCalculator.Test
两个工程添加到其中,故而所有dotnet
命令都没有指定工程,而是缺省使用了顶层的sln作为目标。
此外step2~4中,显然后一项都会缺省执行前一项,这里我们却故意将其拆开,手动一步一步执行,其目的一方面可以更快定位哪一步出错,另一方面也方便调节输出信息的等级,使得不同步骤的结果信息能够相互隔离。
Maven + JUnit + Gitlab CI
由于学校的gitlab我没有权限新建仓库,我选择了OO课程的pre作业,创建新的cicd
分支。并且由于我也没有权限将其设置为public
,所以代码部分我在gitee上放了一份作备用,但是CI/CD
功能使用的是gitlab的。
仓库链接:https://gitlab.buaaoo.top/oo_2019_homeworks/oo_2020_preview2_18231045_pre2_task6/tree/cicd
备用仓库链接:https://gitee.com/snowphoenix/primary-geometry
实验结果:
我这里选用了一张包括单元测试不通过的情况的截图。
yml配置文件:
image: local-registry.inner.buaaoo.top/image-dev/java:8u201
stages:
- build
- test
before_script:
- java -version
- javac -version
- mvn -v
mvn_build:
stage: build
script:
- echo "Build Project"
- mvn compile -q
mvn_test:
stage: test
dependencies:
- mvn_build
script:
- echo "Run JUnit"
- mvn test -q
- echo "Code coverage 99%"
coverage: '/Code coverage \d+/'
大致直接使用了课程组提供的模板,稍加改造。下面简单分析一下各个步骤的作用:
step1 显示版本
before_script:
- java -version
- javac -version
- mvn -v
step2 构建
mvn_build:
stage: build
script:
- echo "Build Project"
- mvn compile -q
我这里为maven的编译选项加了-q
,主要是mvn下载外部依赖的包时,会产生大量信息,掩盖掉有用信息。
step3 单元测试
mvn_test:
stage: test
dependencies:
- mvn_build
script:
- echo "Run JUnit"
- mvn test -q
- echo "Code coverage 99%"
coverage: '/Code coverage \d+/'
dependencies
选项制定了依赖的动作,表示测试需要在构建完成之后进行。
和构建相同,maven下载外部依赖的包的时候会产生大量信息,掩盖有用信息,尤其是单元测试的结果信息相较于构建信息可能更加重要,所以也加了-q
选项,帮助我快速找到哪些单元测试没有通过。
echo "Code coverage 99%"
只是为了测试coverage: '/Code coverage \d+/'
,后者的作用是通过正则匹配,从运行的信息中提取出覆盖率信息,显示在gitlab的UI上,真实情况下应该不会通过echo
命令来输出覆盖率。
CI/CD小结
Github Action
和Gitlab CI
相比,直观上感觉前者的视觉设计更加合理,各个步骤的信息都是分开的,可以方便找到需要的信息,而后者的信息就很庞杂,一个外部依赖包的下载就可能导致整整一页的信息,不得不打开-q
选项,但这样又可能丢失有用信息。这一点也可能是因为我对maven或者CI/CD
工具不熟悉导致的吧。
就目前的简单实用体验来看,利用CI/CD
工具构建相比于本地进行构建的区别大致有如下几点:
- 需要代码具有跨平台能力、跨设备能力。我最开始本来想用我之前写的编译器来试一试的,但是我当初写编译器时,单元测试中有大量不可移植代码,比如硬编码的绝对路径,所以无法使用。
CI/CD
依赖命令行接口,而本地测试我们大多可以直接通过IDE的GUI界面来完成。- 运行较慢,可能是因为我这几次使用的都是托管的Runner来运行,所以每次push后都需要等待很久才能查看结果,但是本地进行相同的步骤往往就很快;当然,还有一个因素就是本地的环境是固定的,而利用
CI/CD
功能需要先拉取/配置环境,构建与测试完成后还要进行还原,而本地就不需要。 - 共享性,每一个访问仓库的成员都可以看到这次push后的构建的结果,而本地则不然。