zoukankan      html  css  js  c++  java
  • 《编写可读代码的艺术》第14章 测试与可读性

    1. 使测试易于阅读和维护

        测试代码的可读性和被测试代码同样重要。很多程序员会把测试代码看做非正式文档,它记录了代码如何工作及如何使用。

        当测试代码多得让人望而却步,程序员不敢修改真实代码,不会再增加新的测试。从而对测试代码丧失信心。

    2. 有问题的测试代码示例,后面要一一对它们进行修改。

     1 void Test1() {
     2     vector<ScoredDocument> docs;
     3     docs.resize(5);
     4     docs[0].url = "http://example.com";
     5     docs[0].score = -5.0;
     6     docs[1].url = "http://example.com";
     7     docs[1].score = 1;
     8     docs[2].url = "http://example.com";
     9     docs[2].score = 4;
    10     docs[3].url = "http://example.com";
    11     docs[3].score = -99998.7;
    12     docs[4].url = "http://example.com";
    13     docs[4].score = 3.0;
    14 
    15     SortAndFilterDocs(&docs);
    16     
    17     assert(docs.size() == 3);
    18     assert(docs[0].score == 4);
    19     assert(docs[1].score == 3.0);
    20     assert(docs[2].score == 1);
    21 }

      3. 使这个测试更可读

        对使用者隐去不重要的细节,以便更重要的细节能突出。这样测试代码变得紧凑一点了。

     1 void MakeScoredDoc(ScoredDocument* sd, double score, string url) {
     2     sd->score = score;
     3     sd->url = url;
     4 }
     5 
     6 void Test1() {
     7     vector<ScoredDocument> docs;
     8     docs.resize(5);
     9     MakeScoredDoc(&docs[0], -5.0, "http://example.com");
    10     MakeScoredDoc(&docs[1], 1, "http://example.com");
    11     MakeScoredDoc(&docs[2], 4, "http://example.com");
    12     MakeScoredDoc(&docs[3], -99998.7, "http://example.com");
    13     ...
    14 }

        进一步隐藏细节

     1 void AddScoredDoc(vector<ScoredDocument>& docs, double score) {
     2     ScoredDocument sd;
     3     sd.score = score;
     4     sd.url = "http://example.com";
     5     docs.push_back(sd);
     6 }
     7 
     8 void Test1() {
     9     vector<ScoredDocument> docs;
    10     AddScoredDoc(docs, -5.0);
    11     AddScoredDoc(docs, 1);
    12     AddScoredDoc(docs, 4);
    13     AddScoredDoc(docs, -99998.7);
    14     ...
    15 }

    4. 创建最小的测试声明

        CheckScoresBeforeAfter("-5, 1, 4, -99998.7, 3", "4, 3, 1");

        我们可以把测试的基本内容精简为一行代码来描述,即对于给定的输入,期望的输出是什么。

    5. 实现定制的“微语言”

        在较新版本的C++中,可以这样传入数组:

        CheckScoresBeforeAfter({-5, 1, 4, -99998.7, 3}, {4, 3, 1});

        然而在之前,需要对字符串进行解析:

     1 vector<ScoredDocument> ScoredDocsFromString(string scores) {
     2     vector<ScoredDocument> docs;
     3     replace(scores.begin(), scores.end(), ',', ' ');
     4     // Populate 'docs' from a string of space-separated scores.
     5     istringstream stream(scores);
     6     double score;
     7     while (stream >> score) {
     8         AddScoredDoc(docs, score);
     9     }
    10     return docs;
    11 }
    12 
    13 string ScoredDocsToString(vector<ScoredDocument> docs) {
    14     ostringstream stream;
    15     for (int i = 0; i < docs.size(); i++) {
    16         if (i > 0) {
    17             stream << ", ";
    18             stream << docs[i].score;
    19         }
    20     }
    21     return stream.str();
    22 }
    23 
    24 void CheckScoresBeforeAfter(string input, string expected_output) {
    25     vector<ScoredDocument> docs = ScoredDocsFromString(input);
    26     SortAndFilterDocs(&docs);
    27     string output = ScoredDocsToString(docs);
    28     assert(output == expected_output);
    29 }

        乍一看有很多的代码,但是实际代码的能力更强了,你可以只调用。

        CheckScoresBeforeAfter一次就写出了整个测试,你将倾向于增加更多的测试代码。

    6. 让错误消息具有可读性

      assert(output == expected_output);

        Assertion failed: (output == expected_output),

       function CheckScoresBeforeAfter, file test.cc, line 37.

        大部分语言都有高级版本的assert()可用。

        BOOST_REQUIRE_EQUAL(output, expected_output)

      test.cc(37): fatal error in "CheckScoresBeforeAfter": critical check
        output == expected_output failed ["1, 3, 4" != "4, 3, 1"]

       手工打造错误消息。理想的错误消息可以像这样:

      CheckScoresBeforeAfter() failed,
        Input:
        "-5, 1, 4, -99998.7, 3"
        Expected Output: "4, 3, 1"
        Actual Output: "1, 3, 4"

     1 void CheckScoresBeforeAfter(...) {
     2     //...
     3     if (output != expected_output) {
     4         cerr << "CheckScoresBeforeAfter() failed," << endl;
     5         cerr << "Input:
     6         "" << input << """ << endl;
     7         cerr << "Expected Output: "" << expected_output << """ << endl;
     8         cerr << "Actual Output: "" << output << """ << endl;
     9         abort();
    10     }
    11 }

    7. 选择好的测试输入

        应当选择一组最简单的输入,它能完整地使用被测代码。

    1 CheckScoresBeforeAfter("1, 2, 3", "3, 2, 1"); //未测试负分的情况
    2 CheckScoresBeforeAfter("123014, -1082342, 823423, 234205, -235235",
    3     "823423, 234205, 123014"); // 无必要的复杂

         简化输入值

    CheckScoresBeforeAfter("-5, 1, 4, -99998.7, 3", "4, 3, 1");
    
    // -99998.7, 3只是想表示很大的负数,用-1e100这样的值更好
    // 实际上只需要一个负数来测试负数会被移除就可以了:
    CheckScoresBeforeAfter("1, 2, -1, 3", "3, 2, 1");
    
    //你可能会想包含这样一个测试,
    //大型输入在发现bug方面很有用,但是这样的代码大多看上去很吓人,效果却不好
    CheckScoresBeforeAfter("100, 38, 19, -25, 4, 84, [lots of values] ...",
        "100, 99, 98, 97, 96, 95, 94, 93, ...");
    // 用编程的方法来生成大型输入(如100000个值)会有更好的效果。

        一个功能的多个小测试,更容易、更高效且更有可读性

        SortAndFilterDocs()的四个测试: 

    1 CheckScoresBeforeAfter("2, 1, 3", "3, 2, 1");   //Basic sorting
    2 CheckScoresBeforeAfter("0, -0.1, -10", "0");    // All values < 0 removed
    3 CheckScoresBeforeAfter("1, -2, 1, -2", "1, 1"); //Duplicates not a problem
    4 CheckScoresBeforeAfter("","");                  //Empty input O

     8. 为测试函数命名

        不要用Test1()这样的名字,可以用Test_<FunctionName>()这样的格式:

        void Test_SortAndFilterDocs()

        或者Test_<FunctionName>_<Situation>()这样的格式:

        void Test_SortAndFilterDocs_BasicSorting()
        void Test_SortAndFilterDocs_NegativeValues()

        在你的整个代码库中不会调用这些函数,因此避免使用长函数名的情形在这里不适用

    9. 对测试较好的开发方式

        对测试友好的东西往往自然地产生有良好组织的代码。

        尽量避免:1. 使用全局变量;2. 对外部组件有大量依赖的代码;3. 代码有不确定的行为。

        应当做到:1. 类中只有很少或者没有内部状态;2. 一个类/函数只做一件事;3. 每个类对别的类的依赖很少(低耦合);4. 函数的接口简单,定义明确。

        不要走得太远:1. 为了测试,牺牲代码可读性;2. 着迷于100%的测试覆盖率;3. 让测试成为产品开发的阻碍

     
  • 相关阅读:
    毕业考试
    相机标定
    深度相机
    怎么选工业相机
    Python Socket 编程
    Canoe 过滤Trace中报文
    Canoe 使用Replay Block CAN回放报文
    安装Jupyter Notebook
    Altium Designer PCB 画板框
    EMQX 取消匿名登录和添加、删除用户
  • 原文地址:https://www.cnblogs.com/yyqng/p/14286834.html
Copyright © 2011-2022 走看看