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. int main( int argc, char* argv[] )
  9. {
  10. // Create the event manager and test controller
  11. CPPUNIT_NS::TestResult controller;
  12.  
  13. // Add a listener that colllects test result
  14. CPPUNIT_NS::TestResultCollector result;
  15. controller.addListener( &result );
  16.  
  17. // Add a listener that print dots as test run.
  18. CPPUNIT_NS::BriefTestProgressListener progress;
  19. controller.addListener( &progress );
  20.  
  21. // Add the top suite to the test runner
  22. CPPUNIT_NS::TestRunner runner;
  23. runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
  24. runner.run( controller );
  25.  
  26. // Print test in a compiler compatible format.
  27. CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
  28. outputter.write();
  29. system("pause");
  30. return result.wasSuccessful() ? : ;
  31. }

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. #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( == );
  10. CPPUNIT_ASSERT( == );
  11. }
  12.  
  13. void ExampleTestCase::anotherExample()
  14. {
  15. CPPUNIT_ASSERT ( == );
  16. }
  17.  
  18. void ExampleTestCase::setUp()
  19. {
  20. m_value1 = 2.0;
  21. m_value2 = 3.0;
  22. }
  23.  
  24. void ExampleTestCase::testAdd()
  25. {
  26. double result = m_value1 + m_value2;
  27. CPPUNIT_ASSERT( result == 6.0 );
  28. }
  29.  
  30. void ExampleTestCase::testEquals()
  31. {
  32. long* l1 = new long();
  33. long* l2 = new long();
  34.  
  35. CPPUNIT_ASSERT_EQUAL( , );
  36. CPPUNIT_ASSERT_EQUAL( 12L, 12L );
  37. CPPUNIT_ASSERT_EQUAL( *l1, *l2 );
  38.  
  39. delete l1;
  40. delete l2;
  41.  
  42. CPPUNIT_ASSERT( 12L == 12L );
  43. CPPUNIT_ASSERT_EQUAL( , );
  44. CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 );
  45. }

虽然我对该框架一点不熟悉我们发现了以下代码

  1. CPPUNIT_ASSERT( 12L == 12L );//两个长整形12的大小,为真
  2.  
  3. CPPUNIT_ASSERT_EQUAL( , );//比较12和13是否相等,不相等不通过测试
  4.  
  5. CPPUNIT_ASSERT_DOUBLES_EQUAL( 12.0, 11.99, 0.5 )//比较两个double的值是否相差大于0.5.

ExampleTestCase.h文件代码写的是各测试函数的声明。

main函数写的是测试相关代码,发现跟ExampleTestCase中的各函数一点关系都没有,可以安心通过更改部分代码来实现想要进行的但与测试。

三,cppunit的简单实践

本次测试是要测试四则运算程序中关于具体实现运算的两个函数,分别是convert2RPN()和calculateRPN(),二者功能是把算式转化为后缀表达式,并且计算。

以下为测试用例。

  1. test1:+       
  2.  
  3. test2:-       
  4.  
  5. test3:*       
  6.  
  7. test4:/       
  8.  
  9. test5:/       
  10.  
  11. test6:+*-
  12.  
  13. test7:+*(-)
  14.  
  15. test8:÷((+)÷)
  16.  
  17. test9:(/++) 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. #endif

ExampleTestCase.cpp

之前的代码就不占篇幅了,折叠了先。

  1. void convert2RPN(string &s) {
  2. stringstream ss;
  3. stack<char> stk;
  4. for (size_t i = ; i < s.length(); i++) {
  5. if (isdigit(s.at(i))) {
  6. ss << s.at(i);
  7. if ((i < s.length() - && !isdigit(s.at(i + )))
  8. || i == s.length() - ) {
  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. }

convert2RPN

  1. double calculateRPN(const string &s) {
  2. stack<float> stk;
  3. for (size_t i = ; i < s.length(); i++) {
  4. if (isdigit(s.at(i))) {
  5. int e = atoi(&s.at(i));
  6. int t = e / ;
  7. while (t > ) {
  8. i++;
  9. t /= ;
  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. }

calculateRPN

  1. void ExampleTestCase::test1()
  2. {
  3. string s ="1+1";
  4. convert2RPN(s);
  5. double res1 = calculateRPN(s);
  6. double res2 = ;
  7. CPPUNIT_ASSERT_DOUBLES_EQUAL( res1, res2, 0.05 );
  8. }
  9.  
  10. void ExampleTestCase::test2()
  11. {
  12. string s= "55-5";
  13. double res2 = ;
  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 = ;
  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 = ;
  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 = ;
  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 = ;
  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 = ;
  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 = ;
  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 没用通过,我们再回顾一下测试用例。

  1. test1:+
  2.  
  3. test2:-
  4.  
  5. test3:*
  6.  
  7. test4:/
  8.  
  9. test5:/
  10.  
  11. test6:+*-
  12.  
  13. test7:+*(-
  14.  
  15. test8:÷((+)÷)
  16.  
  17. test9:(/++) 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跟以前也大有不同了。但好在最后有一些小小的收获。刚开始可以编译运行示例代码的,心情是开心的,但是那些示例工程的代码由于是自己的框架,相关函数和类很难看懂,我那一瞬间感觉自己已经感觉不会编程了,后来慢慢地尝试去看,发现其实并不难,或者自己找到了可以自己提升的途径。还是比较简单的方法,既然原示例工程可以运行,那么我就可以每一次做一点修改,不断观察编译以及运行结果,靠几个简单的函数让我的代码做一下简单的测试。由于时间有限,我打算看后面较复杂工程的代码还没看,我觉得通过这个方法,我可以逐渐更熟悉这个框架,并且提高自己的工程能力,这次这个经历锻炼了我面对工程的综合解决路线。代码不是工程的全部,想要做好一个东西包括收集资料,下载软件代码,不断自己尝试,包括后面的单元测试,真正算是一项工程,包括了很多方面。总之,通过本次作业,我体验到了以前没有过的经历,感觉自己的能力更加全面了。

【第三周】【】cppunit!的更多相关文章

  1. 三周,用长轮询实现Chat并迁移到Azure测试

    公司的OA从零开始进行开发,继简单的单点登陆.角色与权限.消息中间件之后,轮到在线即时通信的模块需要我独立去完成.这三周除了逛网店见爱*看动漫接兼职,基本上都花在这上面了.简单地说就是用MVC4基于长 ...

  2. Coursera系列-R Programming第三周-词法作用域

    完成R Programming第三周 这周作业有点绕,更多地是通过一个缓存逆矩阵的案例,向我们示范[词法作用域 Lexical Scopping]的功效.但是作业里给出的函数有点绕口,花费了我们蛮多心 ...

  3. JAVA第三周作业(从键盘输入若干数求和)

    JAVA第三周作业(从键盘输入若干数求和) 在新的一周,我学习了JAVA的IO编程.下面的代码实现了从键盘输入若干数求和的目标.import java.util.Scanner; public cla ...

  4. 20145213《Java程序设计》第三周学习总结

    20145213<Java程序设计>第三周学习总结 教材学习内容总结 正所谓距离产生美,上周我还倾心于Java表面的基础语法.其简单的流程结构,屈指可数的基本类型分类,早已烂熟于心的运算符 ...

  5. 20145304 Java第三周学习报告

    20145304 <Java程序设计>第三周学习总结 教材学习内容总结 1.定义类: 类定义时使用class关键词,建立实例要使用new关键词. 代码如下: /*定义类 书上例子 衣服的型 ...

  6. 20145330《Java程序设计》第三周学习总结

    20145330 <Java程序设计>第三周学习总结 第三周知识的难度已经逐步上升,并且一周学习两章学习压力也逐渐加大,需要更高效率的来完成学习内容,合理安排时间. 类与对象 对象(Obj ...

  7. 20145337《Java程序设计》第三周学习总结

    20145337 <Java程序设计>第三周学习总结 教材学习内容总结 类与对象 类与对象的关系:要产生对象必须先定义类,类是对象的设计图,对象是类的实例.我觉得在视频中对类与对象关系的描 ...

  8. Linux内核设计第三周——构造一个简单的Linux系统

    Linux内核设计第三周 ——构造一个简单的Linux系统 一.知识点总结 计算机三个法宝: 存储程序计算机 函数调用堆栈 中断 操作系统两把宝剑: 中断上下文的切换 进程上下文的切换 linux内核 ...

  9. 20145218 《Java程序设计》第三周学习总结

    20145218 <Java程序设计>第三周学习总结 教材学习内容总结 定义类 编写程序要产生对象就要先定义类.类是对象的设计图,对象是类的实例.类定义时使用class关键词,建立实例时, ...

随机推荐

  1. VS.NET2013发布网站的时候去掉.cs文件(预编译)(转)

      在要发布的网站上右键,选择"发布网站".   在发布窗口中,会让你选择一个发布配置文件,没有的话点击下拉菜单在里面选择新建一个. NEXT.   好,现在发布一下网站.发布出来 ...

  2. .Net 两个对像之间的映射 (一 )

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  3. 20155202 《Java程序设计》实验二(面向对象程序设计)实验报告

    20155202 <Java程序设计>实验二(面向对象程序设计)实验报告 代码托管 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉 ...

  4. 人脸识别引擎SeetaFaceEngine简介及在windows7 vs2013下的编译

    SeetaFaceEngine是开源的C++人脸识别引擎,无需第三方库,它是由中科院计算所山世光老师团队研发.它的License是BSD-2. SeetaFaceEngine库包括三个模块:人脸检测( ...

  5. AngularJS中Directive指令系列 - 基本用法

    参考: https://docs.angularjs.org/api/ng/service/$compile http://www.zouyesheng.com/angular.html Direct ...

  6. InTelliJ 字体调整

    Java IDE 工具InTelliJ 调整字体大小 1.File -> Settings 2.左上的搜索框中输入 font. 等待自动查找结果. 3.修改size 大小

  7. 关闭会声会影2018提示UEIP.dll找不到指定模块

    最近有一些会声会影2018用户反映在关闭后弹出UEIP.dll错误,不知道该怎么办才好,针对这个问题,小编下面为大家介绍下解决方法. 原因分析 出现这个错误跟会声会影安装路径有中文字符是密切相关的,导 ...

  8. nginx main函数

    源代码: int ngx_cdecl main(int argc, char *const *argv) { ngx_int_t i; ngx_log_t *log; ngx_cycle_t *cyc ...

  9. 高可用Kubernetes集群-11. 部署kube-dns

    参考文档: Github介绍:https://github.com/kubernetes/dns Github yaml文件:https://github.com/kubernetes/kuberne ...

  10. js页面跳转,url带url参数解决方案

    今天,在做一个项目的时候,向后端发送了一个Ajax请求,后端返回了一个字符串,告诉我未登录.那么我需要跳转到登录页面,同时告诉登录页面,登录成功后,需要跳回的url.也就是标题所说,url中的一个参数 ...