【第三周】【】cppunit!
coding.net地址:https://coding.net/u/Boxer_
ssh:git@git.coding.net:Boxer_/homework.git
https://coding.net/user
本次作业过程还是比较坎坷的,主要是前期准备工作比较复杂,这次工作是了解,熟悉并能使用单元测试框架,主要包括junit,cppunit,cunit。这是上次上课老师留下来的,在课堂上老师介绍了一些这些东西,我努力认真听了一下,然后只记下了名字。由于是用的c++和c编写的代码,我理所应当选用cppunit。
一,cppunit的了解:
当着手这个任务开始,已经是周一下午了,算起来距离deadline已经不远了,倒不是我懒也不是拖延(至少不是主要原因),而是由于其他不可抗事务。一般有这样的规律:距离deadline越近,效率越高。所以从这种意义上来讲,我也是因祸得福,节约了不必要的时间的浪费,毕竟考试前一晚上的复习效率是最高的嘛,而我居然拿出了比一学期考试复习还认真的态度应对一周普通软件工程的作业。还有关于拖延的相关信息大家可以看一下这个视频http://open.163.com/movie/2016/3/Q/E/MBHQSM52F_MBI15O7QE.html
关于cppunit单元测试,我之前不怎么了解 ,选择了百度一下,而结果让我感到不舒服,因为教程基本都是十年前的,推荐的ide还是vc++6.0。这导致了我走了很多弯路,虽然教程是很早之前的,而教程里留下的sourceforge代码托管网站的地址居然隔了十几年还能用,非常靠谱,反观百度云分享链接几个月就应该损失殆尽了。在SourceForge上cppunit的代码一直在不断更新,这让我通过教程给的十年前的地址下载下来了最新的官方项目。但很遗憾的是,无法使用。
可以看到这里面所有的扩展名后面又增加了,v的后缀,不知道什么原因,可能这是服务器版本。导致所有文件无法正常执行了,因为被改了后缀的子文件已经忘记了自己的打开方式,就犹如一个个孤岛,孤立无援。所以这是一个大坑,不知情况者要在这里浪费不少时间。
之后我就一直在网上寻找教程,但是符合自己口味的教程实在太少,前几页的结果也看了很多了,都是零几年写的了,很难理解。就算才开始能跟上作者的步骤,但是作者可能下一步就很跨越,再也理解不能。我厌烦了,我就问老师,老师老师这cppunit是不是已
经淘汰不用了,能不能写个教程。
(部分聊天记录)
之后我明白了,我不会,找不到一定是我还不够努力。既然这是个成熟的软件或者是框架,就一定能用并且也有人用过。然后我想距离现在越短的教程大概越靠谱,百度应该能添加搜所条件。我找了一下果然可以搜最近时间的结果。
我看了下最近一年的文章,搜到了其他人在博客园的教程,比之前我找的教程靠谱233倍。
虽然事后我感觉也是个坑,因为我找他的步骤详细做了,最后几步过不了他提供的github上提供的代码。我看到了他的编译文件,少了几个文件,相当于少了很多类。而我程序报错的代码恰好是因为几个类的定义找不到,当然这是后面才知道的了。然而虽然没按照作者所说的路线走下去,在按照作者步骤做的过程中,我成功的编译了部分文件,得到了部分结果,了解了这个框架的部分构成,更重要的是得到了继续寻找的勇气。
所以个人的经历不是简单的用值不值,简单的得失来衡量的。急于到达目的地,不光跑错了方向,并且也失去了一路的风景。后来我刻意找了一下老版本的cppunit,发现是正常的,之后我用之前教程里得到的经验编译了一下。看了下示例项目,初步了解了下该框架。
鉴于本人学习该框架的痛苦经历,加上有些同学向我对该问题进行交流。我决定说一下流程。算是个简单的教程。
1,你要确保自己有个c++编译器,最好是ide,这里推荐下vs2008。 vs2013或者更高编译出来的东西有问题,我的win10+vs2013环境编译出来的东西无法运行,而实验室的win7+vs2008可以。
2,在http://downloads.sourceforge.net/cppunit/cppunit-1.12.1.tar.gz下载软件,
cppunit版本是cppunit-1.12.1。这个网址打开后过一会会自动下载。
3,解压后是这样的。
4,进入cppunit目录下src文件夹,用VS打开CppUnitLibraries文件,提示需要转换,选择确定。
5,之后迁移项目要花点时间,然后点击生成-批生成-全部生成。之后编译过程比较长,弹出对话框点确定就行了。
6,编译完之后,我们就得到了以下文件,打开testpluginrunner还有图形界面。
7,我们不使用图形界面,退回到一级目录,然后打开example,点击examples.dsw,看到有几个项目。左边的是加载到vs里面的,右边的是文件里的。对比我们可以发现,右边文件夹名字在左边出现的,应该是示例项目。
8,有个项目名字为simple,应该是简单的示例代码,我们点开simple。simple有四个文件,除了我们熟悉的.cpp,.h外还有没见过的makefile文件。
在mian函数的return前面添加system("pause");然后右键项目,生成,然后我们找example/simple/debug下面的simple.exe,看到以下结果。
二,cppunit的进一步了解
再让我们看一下代码
main文件代码
1 #include <cppunit/BriefTestProgressListener.h> 2 #include <cppunit/CompilerOutputter.h> 3 #include <cppunit/extensions/TestFactoryRegistry.h> 4 #include <cppunit/TestResult.h> 5 #include <cppunit/TestResultCollector.h> 6 #include <cppunit/TestRunner.h> 7 8 9 int main( int argc, char* argv[] ) 10 { 11 // Create the event manager and test controller 12 CPPUNIT_NS::TestResult controller; 13 14 // Add a listener that colllects test result 15 CPPUNIT_NS::TestResultCollector result; 16 controller.addListener( &result ); 17 18 // Add a listener that print dots as test run. 19 CPPUNIT_NS::BriefTestProgressListener progress; 20 controller.addListener( &progress ); 21 22 // Add the top suite to the test runner 23 CPPUNIT_NS::TestRunner runner; 24 runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() ); 25 runner.run( controller ); 26 27 // Print test in a compiler compatible format. 28 CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() ); 29 outputter.write(); 30 system("pause"); 31 return result.wasSuccessful() ? 0 : 1; 32 }
ExampleTestCase.h文件代码
1 #ifndef CPP_UNIT_EXAMPLETESTCASE_H 2 #define CPP_UNIT_EXAMPLETESTCASE_H 3 4 #include <cppunit/extensions/HelperMacros.h> 5 6 /* 7 * A test case that is designed to produce 8 * example errors and failures 9 * 10 */ 11 12 class ExampleTestCase : public CPPUNIT_NS::TestFixture 13 { 14 CPPUNIT_TEST_SUITE( ExampleTestCase ); 15 CPPUNIT_TEST( example ); 16 CPPUNIT_TEST( anotherExample ); 17 CPPUNIT_TEST( testAdd ); 18 CPPUNIT_TEST( testEquals ); 19 CPPUNIT_TEST_SUITE_END(); 20 21 protected: 22 double m_value1; 23 double m_value2; 24 25 public: 26 void setUp(); 27 28 protected: 29 void example(); 30 void anotherExample(); 31 void testAdd(); 32 void testEquals(); 33 }; 34 35 36 #endif
ExampleTestCase.cpp文件代码
1 #include <cppunit/config/SourcePrefix.h> 2 #include "ExampleTestCase.h" 3 4 CPPUNIT_TEST_SUITE_REGISTRATION( ExampleTestCase ); 5 6 void ExampleTestCase::example() 7 { 8 CPPUNIT_ASSERT_DOUBLES_EQUAL( 1.0, 1.1, 0.05 ); 9 CPPUNIT_ASSERT( 1 == 0 ); 10 CPPUNIT_ASSERT( 1 == 1 ); 11 } 12 13 14 void ExampleTestCase::anotherExample() 15 { 16 CPPUNIT_ASSERT (1 == 2); 17 } 18 19 void ExampleTestCase::setUp() 20 { 21 m_value1 = 2.0; 22 m_value2 = 3.0; 23 } 24 25 void ExampleTestCase::testAdd() 26 { 27 double result = m_value1 + m_value2; 28 CPPUNIT_ASSERT( result == 6.0 ); 29 } 30 31 32 void ExampleTestCase::testEquals() 33 { 34 long* l1 = new long(12); 35 long* l2 = new long(12); 36 37 CPPUNIT_ASSERT_EQUAL( 12, 12 ); 38 CPPUNIT_ASSERT_EQUAL( 12L, 12L ); 39 CPPUNIT_ASSERT_EQUAL( *l1, *l2 ); 40 41 delete l1; 42 delete l2; 43 44 CPPUNIT_ASSERT( 12L == 12L ); 45 CPPUNIT_ASSERT_EQUAL( 12, 13 ); 46 CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 ); 47 }
虽然我对该框架一点不熟悉我们发现了以下代码
1 CPPUNIT_ASSERT( 12L == 12L );//两个长整形12的大小,为真 2 3 CPPUNIT_ASSERT_EQUAL( 12, 13 );//比较12和13是否相等,不相等不通过测试 4 5 CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 )//比较两个double的值是否相差大于0.5.
ExampleTestCase.h文件代码写的是各测试函数的声明。
main函数写的是测试相关代码,发现跟ExampleTestCase中的各函数一点关系都没有,可以安心通过更改部分代码来实现想要进行的但与测试。
三,cppunit的简单实践
本次测试是要测试四则运算程序中关于具体实现运算的两个函数,分别是convert2RPN()和calculateRPN(),二者功能是把算式转化为后缀表达式,并且计算。
以下为测试用例。
test1:1+1 2 test2:55-5 50 test3:33*3 99 test4:50/5 9 test5:1/0 0 test6:21+23*12-6 291 test7:21+23*(12-6) 159 test8:8÷((49+5)÷27) 4 test9:(88/17+31+4) 40.1756
以下为测试代码
ExampleTestCase.h
1 #ifndef CPP_UNIT_EXAMPLETESTCASE_H 2 #define CPP_UNIT_EXAMPLETESTCASE_H 3 #include<string> 4 #include<iostream> 5 #include <cppunit/extensions/HelperMacros.h> 6 using namespace std; 7 class ExampleTestCase : public CPPUNIT_NS::TestFixture 8 { 9 CPPUNIT_TEST_SUITE( ExampleTestCase ); 10 CPPUNIT_TEST( test1 ); 11 CPPUNIT_TEST( test2 ); 12 CPPUNIT_TEST( test3 ); 13 CPPUNIT_TEST( test4 ); 14 CPPUNIT_TEST( test5 ); 15 CPPUNIT_TEST( test6 ); 16 CPPUNIT_TEST( test7 ); 17 CPPUNIT_TEST( test8 ); 18 CPPUNIT_TEST( test9 ); 19 CPPUNIT_TEST_SUITE_END(); 20 public: 21 double res; 22 string s; 23 void test1(); 24 void test2(); 25 void test3(); 26 void test4(); 27 void test5(); 28 void test6(); 29 void test7(); 30 void test8(); 31 void test9(); 32 33 }; 34 35 36 #endif
ExampleTestCase.cpp
之前的代码就不占篇幅了,折叠了先。
1 void convert2RPN(string &s) { 2 stringstream ss; 3 stack<char> stk; 4 for (size_t i = 0; i < s.length(); i++) { 5 if (isdigit(s.at(i))) { 6 ss << s.at(i); 7 if ((i < s.length() - 1 && !isdigit(s.at(i + 1))) 8 || i == s.length() - 1) { 9 ss << ' '; 10 } 11 } 12 else { 13 if (stk.empty()) { 14 stk.push(s.at(i)); 15 } 16 else { 17 switch (s.at(i)) { 18 case '(': 19 stk.push(s.at(i)); 20 break; 21 case ')': 22 while (stk.top() != '(') { 23 ss << stk.top(); 24 stk.pop(); 25 } 26 stk.pop(); 27 break; 28 case '+': 29 case '-': 30 31 while (!stk.empty() && stk.top() != '(') { 32 ss << stk.top(); 33 stk.pop(); 34 } 35 stk.push(s.at(i)); 36 break; 37 case '*': 38 case '/': 39 while (!stk.empty() && (stk.top() == '*' || stk.top() == '/')) { 40 ss << stk.top(); 41 stk.pop(); 42 } 43 stk.push(s.at(i)); 44 break; 45 } 46 } 47 } 48 } 49 while (!stk.empty()) { 50 ss << stk.top(); 51 stk.pop(); 52 } 53 s = ss.str(); 54 }
1 double calculateRPN(const string &s) { 2 stack<float> stk; 3 for (size_t i = 0; i < s.length(); i++) { 4 if (isdigit(s.at(i))) { 5 int e = atoi(&s.at(i)); 6 int t = e / 10; 7 while (t > 0) { 8 i++; 9 t /= 10; 10 } 11 i++; 12 stk.push(e); 13 } 14 else { 15 float r = stk.top(); 16 stk.pop(); 17 float l = stk.top(); 18 stk.pop(); 19 float result; 20 switch (s.at(i)) { 21 case '+': 22 result = l + r; 23 break; 24 case '-': 25 result = l - r; 26 break; 27 case '*': 28 result = l * r; 29 break; 30 case '/': 31 32 result = l / r; 33 break; 34 } 35 stk.push(result); 36 } 37 } 38 39 return stk.top(); 40 }
1 void ExampleTestCase::test1() 2 { 3 string s ="1+1"; 4 convert2RPN(s); 5 double res1 = calculateRPN(s); 6 double res2 = 2; 7 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 8 } 9 10 void ExampleTestCase::test2() 11 { 12 string s= "55-5"; 13 double res2 = 50; 14 convert2RPN(s); 15 double res1 = calculateRPN(s); 16 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 17 } 18 void ExampleTestCase::test3() 19 { 20 string s= "33*3"; 21 double res2 = 99; 22 convert2RPN(s); 23 double res1 = calculateRPN(s); 24 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 25 } 26 void ExampleTestCase::test4() 27 { 28 string s= "50/5"; 29 double res2 = 10; 30 convert2RPN(s); 31 double res1 = calculateRPN(s); 32 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 33 } 34 void ExampleTestCase::test5() 35 { 36 string s= "1/1"; 37 double res2 = 1; 38 convert2RPN(s); 39 double res1 = calculateRPN(s); 40 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 41 } 42 43 void ExampleTestCase::test6() 44 { 45 string s= "21+23*12-6"; 46 double res2 = 291; 47 convert2RPN(s); 48 double res1 = calculateRPN(s); 49 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 50 } 51 52 void ExampleTestCase::test7() 53 { 54 string s= "21+23*(12-6)"; 55 double res2 = 159; 56 convert2RPN(s); 57 double res1 = calculateRPN(s); 58 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 59 } 60 void ExampleTestCase::test8() 61 { 62 string s= "8/((49+5)/27)"; 63 double res2 = 4; 64 convert2RPN(s); 65 double res1 = calculateRPN(s); 66 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 67 } 68 69 void ExampleTestCase::test9() 70 { 71 72 string s= "(88/17+31+4)"; 73 double res2 = 40.1756; 74 convert2RPN(s); 75 double res1 = calculateRPN(s); 76 CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 ); 77 }
考虑到比较的结果是浮点数所以用了 CPPUNIT_ASSERT_DOUBLES_EQUAL,一二参数是要对比大小的参数,第三个参数是前二者的相差的阀值,一二参数若大于这个阀值,测试样例便不能通过。
main函数未做更改。
调试过程中出现过几次错误(其余n多艰难调试过程省略,)。
主要是因为再输入测试用例的过程中夹杂了汉语的括号,导致程序崩溃,虽然可以过编译,但无法正常执行。
最后改正了测试用例后的结果:
测试点 4和5 没用通过,我们再回顾一下测试用例。
test1:1+1 2 test2:55-5 50 test3:33*3 99 test4:50/5 9 test5:1/0 0 test6:21+23*12-6 291 test7:21+23*(12-6 159 test8:8÷((49+5)÷27) 4 test9:(88/17+31+4) 40.1756
可以看到第四个测试用例答案是错的,第五个出现了除零错误,结果是无限大的数。
可以看这两个函数还是比较健壮的。除零错误在代码中可以过滤掉,不进入计算。
修改了测试用例的答案后(一般不是修改程序吗?),程序顺利的通过了测试点。
没有错误的测试界面是简洁很多的。
类别 |
内容 |
开始时间 |
结束时间 |
间断时间 |
净时间 |
搜集情报 |
对有关c++单元测试的情报进行搜集 |
周一 12:29 周二 10:31 |
周一 24:00 周二 22:00 |
255min 200min |
406min 489min |
研究熟悉框架 |
主要是看example研究代码 |
周二 22:00 周三 11:55 周三 15:05 |
周三00:40 周三 13:20 周三 18:00 |
5min 20min 30min |
155min 85min 145min |
写博客 |
其实与前面穿插进行,做一部分写一部分。 |
周三 20:50 |
周三23:30 |
20min |
140min |
体会:
本次作业完成感觉是最累的一次,特别是找相关资料的时候,那种花费了大量时间一次又一次一无所获的感觉,那种面对多年前文献的无力感,因为看不懂,他所说的东西已经不好下载了。现在的环境都是win7和win10跟以前也大有不同了。但好在最后有一些小小的收获。刚开始可以编译运行示例代码的,心情是开心的,但是那些示例工程的代码由于是自己的框架,相关函数和类很难看懂,我那一瞬间感觉自己已经感觉不会编程了,后来慢慢地尝试去看,发现其实并不难,或者自己找到了可以自己提升的途径。还是比较简单的方法,既然原示例工程可以运行,那么我就可以每一次做一点修改,不断观察编译以及运行结果,靠几个简单的函数让我的代码做一下简单的测试。由于时间有限,我打算看后面较复杂工程的代码还没看,我觉得通过这个方法,我可以逐渐更熟悉这个框架,并且提高自己的工程能力,这次这个经历锻炼了我面对工程的综合解决路线。代码不是工程的全部,想要做好一个东西包括收集资料,下载软件代码,不断自己尝试,包括后面的单元测试,真正算是一项工程,包括了很多方面。总之,通过本次作业,我体验到了以前没有过的经历,感觉自己的能力更加全面了。