[学习笔记]《代码整洁之道》(八)

[学习笔记] 《代码整洁之道》— 第9章 单元测试

TDD 三定律

谁都知道TDD要求我们在编写生产代码之前先编写单元测试。

  • 定律一:在编写不能通过的测试单元前,不可以编写生产代码。
  • 定律二:只可编写刚好无法通过的单元测试,不能编译也算不通过。
  • 定律三:只可编写刚好足以通过当前失败测试的生产代码。

保持测试整洁

  • 脏测试等同于 — 如果不是坏于的话 — 没测试。
    • 测试必须随生产代码的演进而修改。
      • 测试越脏,就越难修改。
  • 测试代码和生产代码一样重要!
    • 单元测试让你的代码 可扩展、可维护、可复用
    • 覆盖了生产代码的自动化单元测试程序组能尽可能地保持设计和架构的整洁。
  • 测试越脏,代码就会变得越脏。最终,你丢失了测试,代码开始腐坏。

整洁的测试

  • 整洁的测试有三个要素:可读性可读性可读性

    • 在单元测试中,可读性甚至比生产代码还重要。
    • 明确简洁足够的表达力
  • 先看来自 FitNesse 的代码:

    public void testGetPageHieratchyAsXml() throws Exception{
        crawler.addPage(root, PathParser.parse("PageOne"));
        crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
        crawler.addPage(root, PathParser.parse("PageTwo"));
        
        request.setResource("root");
        request.addInput("type", "pages");
        Responder responder = new SerializedPageResponder();
        SimpleResponse reponse =
            (SimpleResponse) responder.makeResponse(
        		new FitNesseContext(root), request);
        String xml = response.getContent();
        
        assertEquals("text/xml", response.getContentType());
        assertSubString("<name>PageOne</name>", xml);
        assertSubString("<name>PageTwo</name>", xml);
        assertSubString("<name>ChildOne</name>", xml);
    }
    
    public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() 
    throws Exception{
        WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne"));
        crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
        crawler.addPage(root, PathParser.parse("PageTwo"));
        
        PageData data = pageOne.getData();
        WikiPageProperties properties = data.getProperties();
        WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME);
        symLinks.set("SymPage", "pageTwo");
        pageOne.commit(data);
        
        request.setResource("root");
        request.addInput("type", "pages");
        Responder responder = new SerializedPageResponder();
        SimpleResponse reponse =
            (SimpleResponse) responder.makeResponse(
        		new FitNesseContext(root), request);
        String xml = response.getContent();
        
        assertEquals("text/xml", response.getContentType());
        assertSubString("<name>PageOne</name>", xml);
        assertSubString("<name>PageTwo</name>", xml);
        assertSubString("<name>ChildOne</name>", xml);
        assertNotSubString("SymPage", xml);
    }
    
    public void testGetDataAsHtml() throws Exception{
        crawler.addPage(root, PathParser.parse("TestPageOne"), "test page");
        
        request.setResource("TestPageOne");
        request.addInput("type", "data");
        Responder responder = new SerializedPageResponder();
        SimpleResponse reponse =
            (SimpleResponse) responder.makeResponse(
        		new FitNesseContext(root), request);
        String xml = response.getContent();
        
        assertEquals("text/xml", response.getContentType());
        assertSubString("test page", xml);
        assertSubString("<Tset", xml);
    }
    

    重构为更整洁和有表达力的形式:

    public void testGetPageHieratchyAsXml() throws Exception{
        makePage("PageOne", "PageOne.ChildOne", "PageTwo");
        
        submitRequest("root", "type:pages");
        
        assertResponseIsXML();
        assertResponseContains(
        	"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
        );
    }
    
    public void testSymboLicLinksAreNotInXmlPageHierarchy() throws Exception{
        WikiPage page = makePage("PageOne");
        makePage("PageOne.ChildOne", "PageTwo");
        
        addLinkTo(page, "PageTwo", "SymPage");
        
        submitRequest("root", "type:pages");
        
        assertResponseIsXML();
        assertResponseContains(
        	"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
        );
        assertResponseDoesNotContain("SymPage");
    }
    
    public void testGetDataAsXml() throws Exception{
        makePageWidthContent("TestPageOne", "type:data");
        
        submitRequest("TestPageOne", "type:data");
        
        assertResponseIsXML();
        assertResponseContains("test page", "<Text");
    }
    

    这些测试显然呈现了构造-操作-检验 (BUILD-OPERATE-CHECK)模式。

    • 第一个环节构造测试数据;
    • 第二个环节操作测试数据;
    • 第三个环节检验操作是否得到期望的结果。
  • 双重标准:测试 API 中的代码与生产代码相比。的确有一套不同的工程标准。

    • 有些事你大概永远不会在生产环境中做,而在测试环境中却完全没问题。

    • 例:测试在“温度太低”时检验温度报警器、加热器和送风机是否全部打开。

      @Test
      public void turnOnLoTempAlarmAtThreashold() throws Exception{
          hw.setTemp(WAY_TOO_COLD);
          controller.tic();
          asserTure(hw.heaterState());
          asserTure(hw.blowerState());
          asserFalse(hw.coolerState());
          asserFalse(hw.hiTempState());
          asserTure(hw.loTempState());
      }
      

      重构后:

      @Test
      public void turnOnLoTempAlarmAtThreashold() throws Exception{
          wayTooCold();
          assertEquals("HbchL", hw.getState());
      }
      
      • 创建了一个 wayTooCold 函数,隐藏了 tic 函数的细节。
      • assertEquals 中那个奇怪的字符串:大写表示“打开”,小写表示“关闭”。
        • {heater, blower, cooler, hi-temp-alarm, lo-temp-alarm}

      扩展到更大范围:

      @Test
      public void turnOnCoolerAndBlowerIfTooHot() throws Exception{
          tooHot();
          assertEquals("hBChl", hw.getState());
      }
      
      @Test
      public void turnOnHeaterAndBlowerIfTooClod() throws Exception{
          tooHot();
          assertEquals("HBchl", hw.getState());
      }
      
      @Test
      public void turnOnHiTempAlarmAtThershold() throws Exception{
          wayTooHot();
          assertEquals("hBCHl", hw.getState());
      }
      
      @Test
      public void turnOnLoTempAlarmAtThershold() throws Exception{
          wayTooCold();
          assertEquals("HBchL", hw.getState());
      }
      
      public String getState(){
          String state = "";
          state += heater ? "H" : "h";
          state += blower ? "B" : "b";
          state += cooler ? "C" : "c";
          state += hiTempAlarm ? "H" : "h";
          state += loTempAlarm ? "L" : "l";
          return state;
      }
      
  • 尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。

  • F.I.R.S.T.

    • 快速(Fast):测试应该足够快,应该能快速运行。
    • 独立(Independent):测试应该相互独立。
      • 某个测试不应为下一个测试设定条件。
      • 可以单独运行每个测试,及以任何顺序运行测试。
    • 可重复(Repeatable):测试应该可在任何环境中重复使用。
    • 自足验证(Self-Validating):测试应该有布尔值输出。
      • 不应该查看日志文件来确认测试是否通过。
      • 不应该手工对比两个不同文本文件来测试是否通知。
    • 及时(Timely):测试应及时编写。

参考文献

[1] Robert C. Martin 著,韩磊 译,《代码整洁之道》,北京:人民邮电出版社,2010.1(2018.9 重印), ISBN 978-7-115-21687-8。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值