本博客是第二届构建之法论坛暨软件工程培训活动预培训文档中【适用于结对编程部分的C++版本】,需要实验者有一部分C++基础。
目录
Part0.背景
阿超家里的孩子上小学一年级了,这个暑假老师给家长们布置了一个作业:家长每天要给孩子出一些合理的,但要有些难度的四则运算题目,并且家长要对孩子的作业打分记录。
作为程序员的阿超心想,既然每天都需要出题,那何不做一个可以自动生成小学四则运算题目与解决题目的命令行 “软件”呢。他把老师的话翻译一下,就形成了这个软件的需求:
- 程序接收一个命令行参数 n,然后随机产生 n 道加减乘除(分别使用符号+-*/来表示)练习题,每个数字在 0 和 100 之间,运算符在2个 到 3 个 之间。
- 由于阿超的孩子才上一年级,并不知道分数所以软件所出的练习题在运算过程中不得出现非整数,比如不能出现 3÷5+2=2.6 这样的算式。
- 练习题生成好后,将生成的n道练习题及其对应的正确答案输出到一个文件 subject.txt 中。
- 当程序接收的参数为4时,以下为一个输出文件示例。
13+17-1=29
11*15-5=160
3+10+4-16=1
15÷5+3-2=4
这次阿超选择使用他最拿手的 C++ 语言来完成这样的需求,工欲善其事必先利其器,第一步就需要先安装一个好用的IDE,在这里我们推荐使用 Visual Studio 2017。
Part1.配置环境
Visual Studio 2017 有着宇宙最强IDE的美称,它对C++的支持也很好,在本教程中,为简化学员的开发难度,我们选择使用VS2017社区版(社区版(Community)指的是可免费提供给单个开发人员,给予初学者及大部分程序员支持,可以无任何经济负担、合法地使用的版本。)
下面提供两种安装方法:
- 下载迅雷,输入以下ed2k链接:
ed2k://|file|mu_visual_studio_community_2017_version_15.3_x86_x64_11100062.exe|1069960|5984B3CD547F9F213DE21EFE5887F08D|/
- 百度网盘:链接: https://pan.baidu.com/s/1jJXyRMA 密码: ub6c
下载的文件只是一个引导安装程序,下载完成后,双击运行,如果提示 .Net Framework版本不够,如下图所示:
则需要首先自行安装版本较高的 .Net Framework( .NET Framework 4.6 可以在这里下载)如果没有出现该提示,请忽略。
在点击开exe文件后,一路继续,可以看到如下的选择界面。由于我们只需要C++库,所以只需要勾选 【使用 C++ 的桌面开发】即可。由于VS本身体积较大,推荐修改存储位置,将其安装在非系统盘目录。
如果顺利的话,过一大段时间 VS2017 就会下载好相应文件,此时会提示要求重启,此时按照指示重启即可。
在完成安装后,我们会在开始菜单处看到 VS2017 应用(按 Windows键可以呼出开始菜单):
Windows 10 系统下也可以使用 Cortana 搜索:
安装成功后,首次使用 VS 2017 还需要对其进行简单的配置,包括开发环境的主题风格。如果要求登录的话,可以使用你的outlook账号登录,或者可以选择忽略。
这里开发设置选择 C++即可,主题推荐深色主题。
Part2.克隆项目
阿超的项目放在了当下最流行的源代码管理平台Github上,仓库地址。那么,我们如何在阿超项目的基础上进行开发呢?
1.在 https://github.com/join 这个网址处申请注册一个Github账号,申请成功后可在https://github.com/login 处利用刚刚注册的账号进行登录,才能开始在Github上进行开发。
2.成功登录后,输入阿超仓库的网址,点击右上角的 Fork,将阿超的四则运算库拷贝到自己的同名仓库中,如下图所示:
3.拷贝成功后,可以看到自己已经拥有了一个同名仓库。这里我们登录的是 buaase 的账号:
4.在自己的电脑上安装 Git 软件,Git 的安装教程在这里。在自己拷贝项目的主页的绿色按钮处可以找到一个可克隆的项目地址,下面是一个示例:
5.在 我的电脑 中任意找一个目录,打开Git 命令行软件(Windows上可在空白处右键打开Git Bash),输入 git clone
6.完成上述操作后,可在当前目录下看到一个与仓库同名的文件夹【Calculator】,这就是克隆到本地的项目。注意,默认克隆的分支是 java,请使用 git checkout cplusplus
命令切换到 C++ 项目。进入项目文件夹,新建一个文件夹,重命名为你的Github账号名。
7.以Github账号命名的文件夹作为项目目录,打开VS2017,点击左上角的 【文件】- 【新建】-【项目】,如下图所示,选择 【Visual C++】中的【控制台应用程序】。注意更改【位置】参数到刚刚新建的文件夹所在的路径。比如我的账户名为【buaase】,刚刚新建的文件夹路径为【D:Calculatoruaase】,解决方案的名称也可以是【Calculator】,自定义即可。
8.新建项目后会出现一些默认的stdafx.cpp与.h为后缀的头文件。找到与解决方案同名的cpp文件,比如解决方案为【ConsoleApplication1】,那么会有一个名叫【ConsoleApplication1.cpp】的文件。将src
目录下Calculator.cpp
文件的内容拷贝到该文件中。右键点击【头文件】,新建一个头文件,修改名称为 Calculator.h,并将 src
目录下 Calculator.h
文件的内容拷贝到新的头文件中。此时,右键点击解决方案,选择【编译解决方案】,成功后点击【本地Windows调试器】即可运行,示意图如下:
9.接下来接连使用 git add,git commit -m "Message"(Message是你要写的内容)即可利用 Git 记录下所有的改动。如果是初次使用 Git,请在使用上述两条命令前使用如下两条命令配置自己的个人邮箱与Commit时的用户名,这里的邮箱和用户名最好与 Github 账号保持一致。
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
下面是一些常见的Git操作,可留作备忘
$ git clone [url]
下载一个项目以及它所有的版本历史
$ git add [file]
将文件进行快照处理用于版本控制
$ git commit -m"[descriptive message]"
将文件快照永久地记录在版本历史中
$ git push
上传当前本地分支commit到GitHub上
$ git pull
下载服务器上最新的本部并合并更改到本地
$ git reset [commit hash]
撤销所有[commit hash]后的的commit,在本地保存更改
$ git log
列出当前分支的版本历史
对于Github平台有疑问的,可以在 http://github.com/help 找到解决方案。
Part3.单元测试
要想在 VS2017 里对 C++项目进行单元测试,首先要新建一个测试项目。右键单击解决方案,可以添加一个新建项目,在类型里选择【单元测试】,我们这里新建了一个名为 CalculatorUnitTest 的单元测试项目。
在项目创建成功后,为单元测试项目 CalculatorUnitTest 增加对原项目的引用,以实现调用原项目函数接口的功能。
光设置引用还不够,接下来让我们手动设置一下测试项目的附加依赖项。选中单元测试项目,右键点击选择【属性】
在打开的左侧菜单栏中选择 【配置属性】- 【链接器】-【输入】,在【附加依赖项】的下拉选择框中选择【<编辑...>】,将被测试项目产生的所有 obj 文件路径写到附加依赖项中,如下图所示:
这两个obj 文件可以在如下图文件夹中找到:
在完成单元测试的项目配置后,下面我们就可以开始写单元测试代码了。首先看向新创建的单元测试项目,里面应该会有一个默认的 unittest1.cpp 文件,打开该文件,应该是长这样的:
#include "stdafx.h"
#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace CalculatorUnitTest
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
//请输入测试代码
}
};
}
为了能顺利引用刚刚写好的接口,我们需要引入Calculator工程的接口定义头文件,即 calculator.h。在头文件部分插入一行
#include "../Calculator/calculator.h"
然后我们就可以顺利引用相关接口了。通过阅读方法 Solve 的注释与代码,我们不难发现,它的参数是字符串形式的四则运算表达式,返回的应该是该表达式的答案。现在我们来填充第一个测试用例,TEST_METHOD 其实就是一个宏,后面跟着的 TestMethod1 才是单元测试真正的名字。在该方法中插入如下代码块:
Calculator* calc = new Calculator();
string ret = calc->Solve("11+22");
Assert::AreEqual(ret, (string)"11+22=33");
这里我们用到了 Assert(断言)。编写单元测试时,我们总是会做出一些假设,比如我们期望一个函数在接受预期的输入后就返回预期的输出,断言就是用于在代码中捕捉这些假设。一般来说,单元测试中都会有断言的存在,没有断言存在的单元测试其实是“假大空”的,没有任何对程序输入输出的假设约束。
下面我们来运行一下这个单元测试,看 Solve 函数是否符合我们的期望。找到菜单栏中的【测试】,运行所有测试即可,如下图所示
在单元测试运行完毕后,VS的左侧会弹出一个测试结果窗口。绿色代表通过,红色代表失败。从本次的结果来看,我们通过了这个单元测试。
那么也就是说,当 solve 函数的输入为 "11+22” 时,其实际的输出就是 "11+22=33",与预期现象吻合。当然,我们这里只是通过了一个简单的测试用例,不能说这个函数就是一定正确的。所以我们需要加一些单元测试,以验证在怎样的情况下这个函数可能会出错,现在请你帮助阿超补充一些针对 solve 方法的单元测试用例吧。
Part4. 基本操作
上面我们学习了如何使用 IDE 进行单元测试,也测试出了 solve 方法的确有些问题。那么该如何定位问题所在呢?这就要用到 IDE的调试功能了。下面我们就来介绍一下 Visual Studio 的调试方法。
断点
调试程序首先要会设置断点和单步运行。在VS中设置断点非常简单,在要设置断点的行号旁用鼠标单击一下就行了(注意要点到与右侧编辑器颜色明显不同的区域),如下图所示,我们在第 31 行设了个断点:
单步运行
在设置好断点后,我们就可以启动Debug模式。我们这里由于默认启动项目是 Calculator ,所以直接点击如下所示的【本地 Windows 调试器】按钮即可开始调试。
如果一个解决方案中有多个Cpp项目,要首先指定启动项目。
启动调试后,这时可以看到程序已经运行到刚刚打的断点处前。下方的自动窗口可以看到各个变量的值。
与其他 IDE 类似,我们也可以通过手动设置监视一些感兴趣的变量。点击选项卡到【监视 1】中,按照下图所示方式添加监视,可在界面中只显示监控变量的值。
此时我们的第31行代码并没有执行,下面我们利用单步运行的方法执行该语句。单步运行有两种:Step Into(逐语句,快捷捷F11) 和 Step Over(逐过程,快捷捷F10),分别对应这两个图标。(这些图标都在刚才启动本地调试器按钮的旁边)
这两种单步运行功能在运行语句时没有区别,在执行方法调用语句时,Step Into会跳入方法实现,Step Over会直接执行完方法,实际使用中我们优先使用Step Over,只有方法执行出错了,说明程序问题在被调用方法中,这时再回来通过Step Into进入方法进行调试。我们单击一下Step Over图标(或F10),程序停在了第33行(向右的小黄箭头指示目前执行到第几行,也可以使用这个图标代表的按钮
,这时我们可以看到执行了 31 行代码后 formulaChar 目前的值。
条件断点
有些情况下我们只希望断点在某些条件下才成立,该如何做呢?其实非常简单,右键单击红色的断点符号,即可弹出条件选项。
在这里我们可以输入 Condition,设定为只有某些前置变量的值满足条件时我们才会触发断点,帮助我们高效率测试。比如我们这里设定 Condition 为 j == 0:
从下图中我们可以看到当断点生效时,j 的值为 0。
我们现在的程序中暗藏了两个BUG,一个是不符合题目的需求,另一个是实现上有一个小问题。现在请你利用刚学习到的单元测试与Debug的相关知识,找出程序的bug吧!
Part5.回归测试
单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复、改进或重构之后的正确性。也就是说,在每次修改完bug之后,我们其实都需要运行一遍来看看是不是满足之前所有的单元测试样例。所以,在每次因为现有的 failed test 而修复原有代码后,最好都全部运行一遍单元测试,保证以前 passed test 仍然是可以通过的。
同样地,Git 的使用也是讲究勤提交,提交的粒度最好是细到每个小功能的完成。一个小功能可以是一处小bug的修复,也可以是一个简单函数的实现。所以,在我们本次的编程训练任务中,Git 至少会提交 2 次或以上。
Part6.效能工具介绍
为了测试并改进程序生成四则运算算式的效率,我们需要使用效能分析工具。效能分析工具并不能帮助我们直接改进算法的效率,但它可以帮我们分析找到代码中执行效率最差,也就是所谓【效能瓶颈】的部分。这之后我们就可以把精力花费在改进瓶颈上,从而高效快速地提升程序性能。
Visual Studio 内置了非常棒的效能工具,学名叫做【性能探查器】。点击IDE顶部菜单栏中的【分析】,即可看到【性能探查器】。
我们这里关注在程序的执行效率方面,所以我们选择测试【CPU 使用率】即可。如果想探查内存泄露问题,也可以选择使用其他选项。
先别急着开始探查。我们的代码目前只产生1个四则运算算式,不存在性能问题。我们首先来给代码多加几百万个循环,让它运行【足够长】时间,才能准确测出代码的效能问题。增加循环体后的main 函数体如下所示
int main()
{
for (int i = 0; i < 10000000; i++) {
Calculator* calc = new Calculator();
string question = calc->MakeFormula();
cout << question << endl;
string ret = calc->Solve("11+22");
cout << ret << endl;
}
}
好了,现在让我们开始效能分析。即使程序没有执行完成,效能分析也是可以强行结束的。让程序跑几十秒之后,就可以结束。点击效能分析工具界面左上角的【停止收集】即可停止收集数据。
下图就是一份完整的效能分析报告。
从图中我们可以看到,Solve 方法在总 CPU 耗时中占了约 30% 左右,但如果想获得更详细的信息(比如具体到哪一行占了这么久,详细报告阅读起来更易懂),我们需要点击上图中的【创建详细的报告...】,创建完成后会自动打开一个后缀为 .vspx 的文件,如下图所示:
我们点进 Calculator::Solve 看看,可以看到非常清晰的每行代码占用 CPU 的时间比例,如下图左侧所示。然后我们就可以着手改进代码的效率,比如结合场景用更高效的数据结构,优化一些没有用的代码等等。
Part7.提交代码
在完成 Debug 与 单元测试之后,我们现在来学习一下如何提交代码到 Github 上,并利用 Github 进行团队协作。之前我们已经介绍过了 git add 与 git commit 命令,但这两条命令只会对本地的仓库进行修改,也就是说之前的所有操作都是离线的。我们要想让 Github 上也跟踪到最新的改变,就需要使用 git push
命令。
在使用该命令前,请确保所有本地的改动都已经 add 并 commit 了。可以用 git status 来检查:
出现如图所示的【nothing to commit】即说明已经可以 push 了。使用 push 命令后,会弹出一个窗口要求登录 Github,此时输入Github的用户名或邮箱 与 密码 即可成功 push。
成功的提示如下所示,其中 master 部分应该是 java / cplusplus。
在完成 push 后,我们就可以开始向源仓库(即阿超的仓库)发起Pull Request(简称PR,指发起请求给仓库贡献代码)。打开你 Fork 后的项目主页,如图所示,点击按钮【New pull request】
如果你按照教程的节奏一步步走下来了,那么在点击 New pull request后,应该出现如下所示界面。如果不是,请联系群内的助教/老师,或者在本博客下留言补充错误截图。
此时点击【Create pull request】即可发起请求。等待仓库主人阿超通过审核后,你的代码就可以成功合并进阿超的仓库。至此就完成了整个教程,恭喜!