如何用測試驅動出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 > 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 > 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/

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97