软件工程第2次作业——Visual Studio 2017下基于C/C++的VSTS单元测试实践
Write one minute, test all day long.
环境确定
IDE:Microsoft Visual Studio 2017 Community
语言:C++
单元测试工具:VSTS
IDE
此处使用Visual Studio 2017 Community作为开发环境,安装Visual Studio 2017如图:
语言
这里使用C++作为程序语言,因为我最开始接触的是C++
单元测试工具
C++语言项目的单元测试是通过VSTS在Windows对于C++的托管平台C++/CLI上完成的,通过创建C++的单元测试项目即可对C++的程序项目进行单元测试。
测试案例
本测试用例旨在实现一个二维向量类Vector2
并实现其一些简单的运算操作,关于类自身的组成如下
除了类本身以外还存在一些运算功能,将在代码中列出。
案例实现
这里将给出测试用例的具体定义和实现,在Visual Studio 2017中新建一个Visual C++的项目选择空项目,并创建项目,由于该项目只是一个功能模块,因此在项目属性中设置该项目为“静态库(.lib)”:
然后新建文件Vec.h和Vec.cpp进行功能的实际编码:
接口:
///File : Vec.h #pragma once class Vector2 { public: union { float comp[2]; struct { float x; float y; }; }; //向量的分量 Vector2(); Vector2(float, float); float Length(); //模长 float DirRad(); //方向角 Vector2 Unit(); //此向量对应单位向量 void Normalize(); //对该向量单位化(破坏性) bool IsNormalized(); //判断向量是否是单位向量 bool IsZero(); //判断向量是否是零向量 bool IsValid(); //判断向量是否有效(不含有NAN和无穷) }; extern Vector2 operator+(Vector2 v0, Vector2 v1); // 向量加法 extern Vector2 operator-(Vector2 v0, Vector2 v1); // 向量减法 extern Vector2 operator*(float s, Vector2 v); // 向量数乘 extern Vector2 operator*(Vector2 v, float s); // 向量数乘(交换律) extern Vector2 operator/(Vector2 v, float s); // 向量数除(倒数相乘) extern bool operator==(Vector2 v0, Vector2 v1); // 向量判等 extern bool operator!=(Vector2 v0, Vector2 v1); // 向量不等 extern float DotProd(Vector2 v0, Vector2 v1); // 数量积 extern float operator^(Vector2 v0, Vector2 v1); // 夹角
实现:
注意:
这里的实现实际上有问题,其问题将在测试时被暴露出来
///File : Vec.cpp #include "Vec.h" #include <cmath> using namespace std; Vector2::Vector2() : x(0), y(0) {} Vector2::Vector2(float xf, float yf) : x(xf), y(yf) {} float Vector2::Length() { return sqrt(x*x+y*y); } float Vector2::DirRad() { if (IsZero()||!IsValid()) { return NAN; } return atan2(y, x); } Vector2 Vector2::Unit() { return *this / Length(); } void Vector2::Normalize() { Vector2 vUnit = this->Unit(); *this = vUnit; } bool Vector2::IsNormalized() { return abs(Length()-1.0f) < 1e-6; } bool Vector2::IsZero() { return abs(x) < 1e-6 && abs(y) < 1e-6; } bool Vector2::IsValid() { return (isnan(x) || isnan(y) || isinf(x) || isinf(y)); } Vector2 operator+(Vector2 v0, Vector2 v1) { return Vector2(v0.x + v1.x, v0.y + v1.y); } Vector2 operator-(Vector2 v0, Vector2 v1) { return Vector2(v0.x - v1.x, v0.y - v1.y); } Vector2 operator*(float s, Vector2 v) { if (isnan(s) || !v.IsValid()) { return Vector2(NAN, NAN); } return Vector2(v.x*s,v.y*s); } Vector2 operator*(Vector2 v, float s) { return s * v; } Vector2 operator/(Vector2 v, float s) { return Vector2(v.x/s,v.y/s); } bool operator==(Vector2 v0, Vector2 v1) { return abs(v0.x-v1.x) < 1e-6 && abs(v0.y-v1.y) < 1e-6; } bool operator!=(Vector2 v0, Vector2 v1) { return ! (v0 == v1); } float DotProd(Vector2 v0, Vector2 v1) { return v0.x*v1.x + v0.y*v1.y; } float operator^(Vector2 v0, Vector2 v1) { float dp = DotProd(v0, v1); if (!isnan(dp)) { dp = dp / (v0.Length()*v1.Length()); } return dp; }
单元测试代码与测试用例
单元测试本应当考虑覆盖所有测试内容,但这里由于被测试的功能非常多,并对应多种可能情况,这里只为了说明代码测试的功能,本次测试用例仅满足函数覆盖(但实际上至少应达到语句覆盖)
在原有项目所在的解决方案下新建一个本机单元测试项目,并在引用中加入原有项目。
然后对原有项目进行一次生成,并将该lib文件输入到测试项目的链接器中:
并编写如下的测试代码(该测试仅满足函数覆盖):
///File : unittest1.cpp #include "stdafx.h" #include "CppUnitTest.h" #include "..\Assignments02\Vec.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; namespace UnitTestA02 { TEST_CLASS(UnitTest1) { public: TEST_METHOD(TestConstruct) { Vector2 v0; Vector2 v1(3, 4); Assert::AreEqual(v0.x, 0.0f, FLT_EPSILON); Assert::AreEqual(v1.x, 3.0f, FLT_EPSILON); Assert::AreEqual(v1.y, 4.0f, FLT_EPSILON); } TEST_METHOD(TestMemberFunc) { Vector2 v; float realdir = NAN; Assert::IsTrue(v.IsZero()); Assert::IsTrue(isnan(v.DirRad())); Assert::AreEqual(v.Length(), 0.0f, FLT_EPSILON); Assert::IsFalse(v.Unit().IsValid()); v = { 3,4 }; realdir = atan2(4, 3); Assert::IsFalse(v.IsZero()); Assert::AreEqual(v.Length(), 5.0f, FLT_EPSILON); Assert::IsTrue(v.IsValid()); Assert::AreEqual(v.DirRad(), atan2f(4,3), FLT_EPSILON); Assert::AreEqual(v.Unit().x, 3.0f / 3.0f*cosf(atan2f(4, 3)), FLT_EPSILON); Assert::AreEqual(v.Unit().y, 4.0f / 4.0f*sinf(atan2f(4, 3)), FLT_EPSILON); Assert::IsFalse(v.IsNormalized()); v.Normalize(); Assert::IsTrue(v.IsValid()); Assert::AreEqual(v.DirRad(), realdir, FLT_EPSILON); Assert::IsTrue(v.IsNormalized()); Assert::AreEqual(v.x, v.Unit().x, FLT_EPSILON); Assert::AreEqual(v.y, v.Unit().y, FLT_EPSILON); } TEST_METHOD(TestOperation) { Vector2 v0(2.0f, 4.0f); Vector2 v1(-5.0f, 7.0f); Vector2 vres; float fres = 0.0f; vres = v0 + v1; Assert::AreEqual(vres.x, v0.x + v1.x, FLT_EPSILON); Assert::AreEqual(vres.y, v0.y + v1.y, FLT_EPSILON); vres = v0 - v1; Assert::AreEqual(vres.x, v0.x - v1.x, FLT_EPSILON); Assert::AreEqual(vres.y, v0.y - v1.y, FLT_EPSILON); vres = v0 * 2; Assert::AreEqual(vres.x, 4.0f, FLT_EPSILON); Assert::AreEqual(vres.y, 8.0f, FLT_EPSILON); vres = v0 / 2; Assert::AreEqual(vres.x, 1.0f, FLT_EPSILON); Assert::AreEqual(vres.y, 2.0f, FLT_EPSILON); fres = DotProd(v0, v1); Assert::AreEqual(fres, 18.0f, FLT_EPSILON); fres = v0 ^ v1; } }; }
之后运行测试,发现存在测试错误:
将存在的测试错误完整信息复制,得到:
测试名称: TestOperation 测试全名: UnitTestA02::UnitTest1::TestOperation 测试源: p:\visual studio 2017 projects\softwareengineeringassignments\unittesta02\unittest1.cpp : 第 47 行 测试结果: 失败 测试持续时间: 0:00:00.0101092 结果 StackTrace: at UnitTestA02::UnitTest1::TestOperation() in p:\visual studio 2017 projects\softwareengineeringassignments\unittesta02\unittest1.cpp组名称: UnitTestA02 分组依据: Hierarchy 组全名: UnitTestA02 持续时间: 0:00:00.4947101 2 个测试失败 0 个测试跳过 1 个测试通过 结果1 名称: TestMemberFunc 结果1 结果: 失败 结果1 持续时间: 0:00:00.4836651 结果1 StackTrace: at UnitTestA02::UnitTest1::TestMemberFunc() in p:\visual studio 2017 projects\softwareengineeringassignments\unittesta02\unittest1.cpp:line 29 结果1 消息: Assert failed 结果1 StandardOutput: 结果1 StandardError: 结果2 名称: TestOperation 结果2 结果: 失败 结果2 持续时间: 0:00:00.0103944 结果2 StackTrace: at UnitTestA02::UnitTest1::TestOperation() in p:\visual studio 2017 projects\softwareengineeringassignments\unittesta02\unittest1.cpp:line 60 结果2 消息: Assert failed. Expected:<-nan(ind)> Actual:<4> 结果2 StandardOutput: 结果2 StandardError: 结果3 名称: TestConstruct 结果3 结果: 已通过 结果3 持续时间: 0:00:00.0006506 结果3 StackTrace: 结果3 消息: 结果3 StandardOutput: 结果3 StandardError: 结果 消息: Assert failed. Expected:<2> Actual:<4>
由于在测试时没有将各个断言分立进行,而是分成三个模块将断言分组进行,因此每个分组只能同期得到一个错误结果(即不会得到所有的错误结果,而是每一个测试程序内第一个失败的断言处),这尽管减少了测试用例的代码量,但降低了测试修改然后回归测试的效率(因为无法一次性定位所有错误),因此在实际测试中,可考虑尽量保证各个断言分立进行(这有助于定位错误,但相对应的,测试代码量和测试时间会变长)。
经过排查后发现,是成员函数IsValid
内部的错误:
//原来的 bool Vector2::IsValid() { return (isnan(x) || isnan(y) || isinf(x) || isinf(y)); } //正确的 bool Vector2::IsValid() { return !(isnan(x) || isnan(y) || isinf(x) || isinf(y)); }
将错误修改之后再次进行用例测试,结果正确:
组名称: UnitTestA02 分组依据: Hierarchy 组全名: UnitTestA02 持续时间: 0:00:00.0015827 0 个测试失败 0 个测试跳过 3 个测试通过 结果1 名称: TestConstruct 结果1 结果: 已通过 结果1 持续时间: 0:00:00.0007287 结果1 StackTrace: 结果1 消息: 结果1 StandardOutput: 结果1 StandardError: 结果2 名称: TestMemberFunc 结果2 结果: 已通过 结果2 持续时间: 0:00:00.0004756 结果2 StackTrace: 结果2 消息: 结果2 StandardOutput: 结果2 StandardError: 结果3 名称: TestOperation 结果3 结果: 已通过 结果3 持续时间: 0:00:00.0003784 结果3 StackTrace: 结果3 消息: 结果3 StandardOutput: 结果3 StandardError:
测试告一段落。
测试说明
这些测试用例尽管没能达到语句覆盖,但测试了各个功能能否正常工作,以及是否得到了预期的结果,并且对一部分极端的情况进行了考察,最终,测试通过。
由于Visual Studio 2017 Community中不支持代码覆盖率分析功能,因此这里无法提供关于代码覆盖率更加详细的相关信息。
Visual Studio的代码覆盖率分析功能是Enterprise版特有的。
此时,微软露出了和某Gabe一样令人胆寒的微笑
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签