• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
  • 如何用測試驅動出100%測試覆蓋率的代碼

    發表于:2016-06-29來源:Phodal全棧工程師作者:Phodal Huang點擊數: 標簽:測試驅動
    本文以 DDM 為例,簡單地介紹一下如何用測試驅動開發(TDD, Test-Driven Development)的方法來驅動出這個函數庫。

    本文以DDM為例,簡單地介紹一下如何用測試驅動開發(TDD, Test-Driven Development)的方法來驅動出這個函數庫。

    DDM簡介

    DDM是一個簡潔的前端領域模型庫,如我在《DDM: 一個簡潔的前端領域模型庫》一文中所說,它是我對于DDD在前端領域中使用的一個探索。

    簡單地來說,這個庫就是對一個數據模型的操作——增、刪 、改,然后生成另外一個數據模型。

    DDM

    如以Blog模型,刪除Author,我們就可以得到一個新的模型。而實現上是因為我們需要RSS模型,我們才需要對原有的模型進行修改。

    預先式設計

    如果你對TDD有點了解的話,那么你可能會預先式設計有點疑問。

    等等,什么是測試驅動開發?

    > 測試驅動開發,英文全稱Test-Driven Development,簡稱TDD,是一種不同于傳統軟件開發流程的新型的開發方法。它要求在編寫某個功能的代碼之前先編寫測試代碼,然后只編寫使測試通過的功能代碼,通過測試來推動整個開發的進行。這有助于編寫簡潔可用和高質量的代碼,并加速開發過程。

    流程大概就是這樣的,先寫個測試 -> 然后運行測試,測試失敗 -> 讓測試通過 -> 重構。 enter image description here

    換句簡單的話來說,就是 紅 -> 綠 -> 重構。

    在DDM項目里,就是一個比較適合TDD的場景。我們知道我們所有的功能,我們也知道我們需要對什么內容進行測試,并且它很簡單。因為這個場景下,我們已經知道了我們所需要的功能,所以我們就可以直接設計主要的函數:

    export class DDM {
      constructor() {}
      from() {};
      get(array) {};
      to() {};
      handle() {};
      add() {};
      remove(field) {};
    }
    

    上面的就是我們的需要函數,不過在后來因為需要就添加了replacereplaceWithHandle方法。

    然后,我們就可以編寫我們的第一個測試了。

    第一個驅動開發的測試

    我們的第一個測試,比較簡單,但是也比較麻煩——我們需要構建出基本的輪廓。我們的第一個測試就是要測試我們可以從原來的對象中取出title的值:

    let ddm = new DDM();
    var originObject = {
      title: 'hello',
      blog: 'fdsf asdf fadsf ',
      author: 'phodal'
    };
    
    var newObject = {};
    ddm
      .get(['title'])
      .from(originObject)
      .to(newObject);
    
    expect(newObject.title).toBe("hello");
    

    對應的,為了實現這個需要基本的功能,我們就可以寫一個簡單的return來通過測試。

      from(originObject) {
        return this;
      };
    
      get(array) {
        return this;
      };
    
      to(newObject) {
        newObject.title = 'hello';
        return this;
      };
    

    但是這個功能在我們寫下一個測試的時候,它就會出錯。

    ddm
      .get(['title', 'phodal'])
      .from(originObject)
      .to(newObject);
    
    expect(newObject.title).toBe("hello");
    expect(newObject.author).toBe("phodal");
    

    但是這也是我們實現功能要做的一步,下一步我們就可以實現真正的功能:

    • 在from函數里,復制originObject
    • 在get函數里,獲取新的對象所需要的key
    • 最后,在to函數里,進行復制處理
      from(originObject) {
        this.originObject = originObject;
        return this;
      };
    
      get(array) {
        this.newObjectKey = array;
        return this;
      };
    
      to(newObject) {
        for (var key of this.newObjectKey) {
          newObject[key] = this.originObject[key];
        }
        return this;
      };
    

    現在,我們已經完成了基本的功能。

    一個意外的情況

    在我實現的過程中,我發現如果我傳給get函數的array如果是空的話,那么就不work了。于是,就針對這個情況寫了個測試,然后實現了這個功能:

      get(keyArray) {
        if(keyArray) {
          this.newObjectKey = keyArray;
        } else {
          this.newObjectKey = [];
        }
        return this;
      };
    
      to(newObject) {
        if(this.newObjectKey.length &gt; 0){
          for (var key of this.newObjectKey) {
            newObject[key] = this.originObject[key];
          }
        } else {
          // Clone each property.
          for (var prop in this.originObject) {
            newObject[prop] = clone(this.originObject[prop]);
          }
        }
        return this;
      };
    

    在這個過程中,我還找到了一個clone函數,來替換from中的"="。

      from(originObject) {
        this.originObject = clone(originObject);
        return this;
      };
    
    第三個驅動開發的測試
    ---
    
    因為有了第一個測試的基礎,我們要寫下一測試變得非常簡單:
    
    ```javascript
    dlm.get(['title'])
      .from(originObject)
      .add('tag', 'hello,world,linux')
      .to(newObject);
    
    expect(newObject.tag).toBe("hello,world,linux");
    expect(newObject.title).toBe("hello");
    expect(newObject.author).toBe(undefined);
    

    在實現的過程中,我又投機取巧了,我創建了一個對象來存儲新的對象的key和value:

      add(field, value) {
        this.objectForAddRemove[field] = value;
        return this;
      };
    

    同樣的,在to方法里,對其進行處理:

      to(newObject) {
        function cloneObjectForAddRemove() {
          for (var prop in this.objectForAddRemove) {
            newObject[prop] = this.objectForAddRemove[prop];
          }
        }
    
        function cloneToNewObjectByKey() {
          for (var key of this.newObjectKey) {
            newObject[key] = this.originObject[key];
          }
        }
    
        function deepCloneObject() {
          // Clone each property.
          for (var prop in this.originObject) {
            newObject[prop] = clone(this.originObject[prop]);
          }
        }
    
        cloneObjectForAddRemove.call(this);
        if (this.newObjectKey.length &gt; 0) {
          cloneToNewObjectByKey.call(this);
        } else {
          deepCloneObject.call(this);
        }
        return this;
      };
    

    在這個函數里,我們用cloneObjectForAddRemove函數來復制將要添加的key和value到新的對象里。

    remove和handle函數

    對于剩下的remove和handle來說,他們實現起來都是類似的:

    • 存儲相應的對象操作
    • 然后在to函數里進行處理

    編寫測試:

        function handler(blog) {
          return blog[0];
        }
    
        ddm.get(['title', 'blog', 'author'])
          .from(originObject)
          .handle("blog", handler)
          .to(newObject);
        expect(newObject.blog).toBe('A');
    

    然后實現功能:

      remove(field) {
        this.objectKeyForRemove.push(field);
        return this;
      };
    
      handle(field, handle) {
        this.handleFunction.push({
          field: field,
          handle: handle
        });
        return this;
      }
    

    這一切看上去都很自然,然后我們就可以對其進行重構了。

    100%的測試覆蓋率

    由于,我們先編寫了測試,再實現代碼,所以我們編寫的代碼都有對應的測試。因此,我們可以輕松實現相當高的測試覆蓋率。

    在這個Case下,由于業務場景比較簡單,要實現100%的測試覆蓋率就是一件很簡單的事。

    (PS: 我不是TDD的死忠,只是有時候它真的很美。)

    原文轉自:https://www.phodal.com/blog/use-tdd-drive-100-percent-test-coverage/

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>