成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

撰寫可測試的 JavaScript

Eminjannn / 2629人閱讀

摘要:我們已經(jīng)準(zhǔn)備好經(jīng)歷一段痛苦的撰寫單元測試的過程了,但最終我們能夠撰寫可測試的。這種代碼是很容易進(jìn)行集成測試的,但幾乎不可能針對功能單元進(jìn)行多帶帶的測試。我們絕對可以寫出集成測試的代碼,但我們應(yīng)該很難寫出單元測試了。

轉(zhuǎn)自 勾三股四 - 撰寫可測試的 JavaScript

這篇文章算是 A List Apart 系列文章中,包括滑動門在內(nèi),令我印象最深刻的文章之一。最近有時間翻譯了一下,分享給更多人,希望對大家有所幫助!

我們已經(jīng)面對到了這一窘境:一開始我們寫的 JavaScript 只有區(qū)區(qū)幾行代碼,但是它的代碼量一直在增長,我們不斷的加參數(shù)、加條件。最后,粗 bug 了…… 我們才不得不收拾這個爛攤子。

如上所述,今天的客戶端代碼確實(shí)承載了更多的責(zé)任,瀏覽器里的整個應(yīng)用都越變越復(fù)雜。我們發(fā)現(xiàn)兩個明顯的趨勢:1、我們沒法通過單純的鼠標(biāo)定位和點(diǎn)擊來檢驗(yàn)代碼是否正常工作,自動化的測試才會真正讓我們放心;2、我們也許應(yīng)該在撰寫代碼的時候就考慮到,讓它變得可測試。

神馬?我們需要改變自己的編碼方式?是的。因?yàn)榧词刮覀円庾R到自動化測試的好,大部分人可能只是寫寫集成測試(integration tests)罷了。集成測試的側(cè)重點(diǎn)是讓整個系統(tǒng)的每一部分和諧共存,但是這并沒有告訴我們每個獨(dú)立的功能單元運(yùn)轉(zhuǎn)起來是否都和我們預(yù)期的一樣。

這就是為什么我們要引入單元測試。我們已經(jīng)準(zhǔn)備好經(jīng)歷一段痛苦的撰寫單元測試的過程了,但最終我們能夠撰寫可測試的 JavaScript。

單元與集成:有什么不同?

撰寫集成測試通常是相當(dāng)直接的:我們單純的撰寫代碼,描述用戶如何和這個應(yīng)用進(jìn)行交互、會得到怎樣的結(jié)果就好。Selenium 是這類瀏覽器自動化工具中的佼佼者。而 Capybara 可以便于 Ruby 和 Selenium 取得聯(lián)系。在其它語言中,這類工具也舉不勝舉。

下面就是搜索應(yīng)用的一部分集成測試:

def test_search
    fill_in("q", :with => "cat")
    find(".btn").click
    assert( find("#results li").has_content?("cat"), "Search results are shown" )
    assert( page.has_no_selector?("#results li.no-results"), "No results is not shown" )
end

集成測試對用戶的交互行為感興趣,而單元測試往往僅專注于一小段代碼:

  

當(dāng)我伴隨特定的輸入調(diào)用一個函數(shù)的時候,我是否收到了我預(yù)期中的結(jié)果?

我們按照傳統(tǒng)思路撰寫的程序是很難進(jìn)行單元測試的,同時也很難維護(hù)、調(diào)試和擴(kuò)展。但是如果我們在撰寫代碼的時候就考慮到我將來要做單元測試,那么這樣的思路不僅會讓我們發(fā)現(xiàn)測試代碼寫起來很直接,也會讓我們真正寫出更優(yōu)質(zhì)的代碼。

我們通過一個簡單的搜索應(yīng)用的例子來做個示范:

當(dāng)用戶搜索時,該應(yīng)用會向服務(wù)器發(fā)送一個 XHR (Ajax 請求) 取得相應(yīng)的搜索結(jié)果。并當(dāng)服務(wù)器以 JSON 格式返回數(shù)據(jù)之后,通過前端模板把結(jié)果顯示在頁面中。用戶在搜索結(jié)果中點(diǎn)“贊”,這個人的名字就會出現(xiàn)在右側(cè)的點(diǎn)“贊”列表里。

一個“傳統(tǒng)”的 JavaScript 實(shí)現(xiàn)大概是這個樣子的:

// 模板緩存,緩存的內(nèi)容均為 jqXHR 對象
var tmplCache = {};

/**
 * 載入模板
 * 從 "/templates/{name}" 載入模板,存入 tmplCache
 * @param  {string} name 模板名稱
 * @return {object}      模板請求的 jqXHR 對象
 */
function loadTemplate (name) {
  if (!tmplCache[name]) {
    tmplCache[name] = $.get("/templates/" + name);
  }
  return tmplCache[name];
}

/**
 * 頁面主要邏輯
 * 1. 支持搜索行為并展示結(jié)果
 * 2. 支持點(diǎn)“贊”,被贊過的人會出現(xiàn)在點(diǎn)“贊”列表里
 */
$(function () {

  var resultsList = $("#results");
  var liked = $("#liked");
  var pending = false; // 用來標(biāo)識之前的搜索是否尚未結(jié)束

  // 用戶搜索行為,表單提交事件
  $("#searchForm").on("submit", function (e) {
    // 屏蔽默認(rèn)表單事件
    e.preventDefault();

    // 如果之前的搜索尚未結(jié)束,則不開始新的搜索
    if (pending) { return; }

    // 得到要搜索的關(guān)鍵字
    var form = $(this);
    var query = $.trim( form.find("input[name="q"]").val() );

    // 如果搜索關(guān)鍵字為空則不進(jìn)行搜索
    if (!query) { return; }

    // 開始新的搜索
    pending = true;

    // 發(fā)送 XHR
    $.ajax("/data/search.json", {
      data : { q: query },
      dataType : "json",
      success : function (data) {
        // 得到 people-detailed 模板
        loadTemplate("people-detailed.tmpl").then(function (t) {
          var tmpl = _.template(t);

          // 通過模板渲染搜索結(jié)果
          resultsList.html( tmpl({ people : data.results }) );

          // 結(jié)束本次搜索
          pending = false;
        });
      }
    });

    // 在得到服務(wù)器響應(yīng)之前,清空搜索結(jié)果,并出現(xiàn)等待提示
    $("
  • ", { "class" : "pending", html : "Searching …" }).appendTo( resultsList.empty() ); }); // 綁定點(diǎn)“贊”的行為,鼠標(biāo)點(diǎn)擊事件 resultsList.on("click", ".like", function (e) { // 屏蔽默認(rèn)點(diǎn)擊事件 e.preventDefault(); // 找到當(dāng)前人的名字 var name = $(this).closest("li").find("h2").text(); // 清除點(diǎn)“贊”列表的占位元素 liked.find(".no-results").remove(); // 在點(diǎn)“贊”列表加入新的項(xiàng)目 $("
  • ", { text: name }).appendTo(liked); }); });
  • 我的朋友 Adam Sontag 稱之為“自己給自己挖坑”的代碼:展現(xiàn)、數(shù)據(jù)、用戶交互、應(yīng)用狀態(tài)全部分散在了每一行代碼里。這種代碼是很容易進(jìn)行集成測試的,但幾乎不可能針對功能單元進(jìn)行多帶帶的測試。

    單元測試為什么這么難?有四大罪魁禍?zhǔn)祝?/p>

    沒有清晰的結(jié)構(gòu)。幾乎所有的工作都是在 $(document).ready() 回調(diào)里進(jìn)行的,而這一切在一個匿名函數(shù)里,它在測試中無法暴露出任何接口。

    函數(shù)太復(fù)雜。如果一個函數(shù)超過了 10 行,比如提交表單的那個函數(shù),估計大家都覺得它太忙了,一口氣做了很多事。

    隱藏狀態(tài)還是共享狀態(tài)。比如,因?yàn)?pending 在一個閉包里,所以我們沒有辦法測試在每個步驟中這個狀態(tài)是否正確。

    強(qiáng)耦合。比如這里 $.ajax 成功的回調(diào)函數(shù)不應(yīng)該依賴 DOM 操作。

    組織我們的代碼

    首當(dāng)其沖的是把我們代碼的邏輯縷一縷,根據(jù)職責(zé)的不同把整段代碼分為幾個方面:

    展現(xiàn)和交互

    數(shù)據(jù)管理和保存

    應(yīng)用的狀態(tài)

    把上述代碼建立并串連起來

    在之前的“傳統(tǒng)”實(shí)現(xiàn)里,這四類代碼是混在一起的,前一行我們還在處理界面展現(xiàn),后兩行就在和服務(wù)器通信了。

    我們絕對可以寫出集成測試的代碼,但我們應(yīng)該很難寫出單元測試了。在功能測試?yán)?,我們可以做出諸如“當(dāng)用戶搜索東西的時候,他會看到相應(yīng)的搜索結(jié)果”的斷言,但是無法再具體下去了。如果里面出了什么問題,我們還是得追蹤進(jìn)去,找到確切的出錯位置。這樣的話功能測試其實(shí)也沒幫上什么忙。

    如果我們反思自己的代碼,那不妨從單元測試寫起,通過單元測試這個角度,更好的觀察,是哪里出了問題。這進(jìn)而會幫助我們改進(jìn)代碼,讓代碼變得更易于重用、易于維護(hù)、易于擴(kuò)展。

    我們的新版代碼遵循下面幾個原則:

    根據(jù)上述四類職責(zé),列出每個互不相干的行為,并分別用一個對象來表示。對象之前互不依賴,以避免不同的代碼混在一起。

    用可配置的內(nèi)容代替寫死的內(nèi)容,以避免我們?yōu)榱藴y試而復(fù)刻整個 HTML 環(huán)境。

    保持對象方法的簡單明了。這會把測試工作變得簡單易懂。

    通過構(gòu)造函數(shù)創(chuàng)建對象實(shí)例。這讓我們可以根據(jù)測試的需要復(fù)刻每一段代碼的內(nèi)容。

    作為起步,我們有必要搞清楚,該如何把應(yīng)用分解成不同的部分。我們有三塊展現(xiàn)和交互的內(nèi)容:搜索框、搜索結(jié)果和點(diǎn)“贊”列表。

    我們還有一塊內(nèi)容是從服務(wù)器獲取數(shù)據(jù)的、一塊內(nèi)容是把所有的內(nèi)容粘合在一起的。

    我們從整個應(yīng)用最簡單的一部分開始吧:點(diǎn)“贊”列表。在原版應(yīng)用中,這部分代碼的職責(zé)就是更新點(diǎn)“贊”列表:

    var liked = $("#liked");
    var resultsList = $("#results");
    
    // ...
    
    resultsList.on("click", ".like", function (e) {
      e.preventDefault();
      var name = $(this).closest("li").find("h2").text();
      liked.find( ".no-results" ).remove();
      $("
  • ", { text: name }).appendTo(liked); });
  • 搜索結(jié)果這部分是完全和點(diǎn)“贊”列表攪在一起的,并且需要很多 DOM 處理。更好的易于測試的寫法是創(chuàng)建一個點(diǎn)“贊”列表的對象,它的職責(zé)就是封裝點(diǎn)“贊”列表的 DOM 操作。

    var Likes = function (el) {
      this.el = $(el);
      return this;
    };
    
    Likes.prototype.add = function (name) {
      this.el.find(".no-results").remove();
      $("
  • ", { text: name }).appendTo(this.el); };
  • 這段代碼提供了創(chuàng)建一個點(diǎn)“贊”列表對象的構(gòu)造函數(shù)。它有 .add() 方法,可以在產(chǎn)生新的贊的時候使用。這樣我們就可以寫很多測試代碼來保障它的正常工作了:

    var ul;
    
    // 設(shè)置測試的初始狀態(tài):生成一個搜索結(jié)果列表
    setup(function(){
      ul = $("
    
    *");
    });
    
    test("測試構(gòu)造函數(shù)", function () {
      var l = new Likes(ul);
      // 斷言對象存在
      assert(l);
    });
    
    test("點(diǎn)一個“贊”", function () {
      var l = new Likes(ul);
      l.add("Brendan Eich");
    
      // 斷言列表長度為1
      assert.equal(ul.find("li").length, 1);
      // 斷言列表第一個元素的 HTML 代碼是 "Brendan Eich"
      assert.equal(ul.find("li").first().html(), "Brendan Eich");
      // 斷言占位元素已經(jīng)不存在了
      assert.equal(ul.find("li.no-results").length, 0);
    });
    

    怎么樣?并不難吧 :-) 我們這里用到了名為 Mocha 的測試框架,以及名為 Chai 的斷言庫。Mocha 提供了 testsetup 函數(shù);而 Chai 提供了 assert。測試框架和斷言庫的選擇還有很多,我們出于介紹的目的給大家展示這兩款。你可以找到屬于適合自己的項(xiàng)目——除了 Mocha 之外,QUnit 也比較流行。另外 Intern 也是一個測試框架,它運(yùn)用了大量的 promise 方式。

    我們的測試代碼是從點(diǎn)“贊”列表這一容器開始的。然后它運(yùn)行了兩個測試:一個是確定點(diǎn)“贊”列表是存在的;另一個是確保 .add() 方法達(dá)到了我們預(yù)期的效果。有這些測試做后盾,我們就可以放心重構(gòu)點(diǎn)“贊”列表這部分的代碼了,即使代碼被破壞了,我們也有信心把它修復(fù)好。

    我們新應(yīng)用的代碼現(xiàn)在看起來是這樣的:

    var liked = new Likes("#liked"); // 新的點(diǎn)“贊”列表對象
    var resultsList = $("#results");
    
    // ...
    
    resultsList.on("click", ".like", function (e) {
      e.preventDefault();
      var name = $(this).closest("li").find("h2").text();
      liked.add(name); // 新的點(diǎn)“贊”操作的封裝
    });
    

    搜索結(jié)果這部分比點(diǎn)“贊”列表更復(fù)雜一些,不過我們也該拿它開刀了。和我們?yōu)辄c(diǎn)“贊”列表創(chuàng)建一個 .add() 方法一樣,我們要創(chuàng)建一個與搜索結(jié)果有交互的方法。我們需要一個點(diǎn)“贊”的入口,向整個應(yīng)用“廣播”自己發(fā)生了什么變化——比如有人點(diǎn)了個“贊”。

    // 為每一條搜索結(jié)果的點(diǎn)“贊”按鈕綁定點(diǎn)擊事件
    var SearchResults = function (el) {
      this.el = $(el);
      this.el.on( "click", ".btn.like", _.bind(this._handleClick, this) );
    };
    
    // 展示搜索結(jié)果,獲取模板,然后渲染
    SearchResults.prototype.setResults = function (results) {
      var templateRequest = $.get("people-detailed.tmpl");
      templateRequest.then( _.bind(this._populate, this, results) );
    };
    
    // 處理點(diǎn)“贊”
    SearchResults.prototype._handleClick = function (evt) {
      var name = $(evt.target).closest("li.result").attr("data-name");
      $(document).trigger("like", [ name ]);
    };
    
    // 對模板渲染數(shù)據(jù)的封裝
    SearchResults.prototype._populate = function (results, tmpl) {
      var html = _.template(tmpl, { people: results });
      this.el.html(html);
    };
    

    現(xiàn)在我們舊版應(yīng)用中管理搜索結(jié)果和點(diǎn)“贊”列表之間交互的代碼如下:

    var liked = new Likes("#liked");
    var resultsList = new SearchResults("#results");
    
    // ...
    
    $(document).on("like", function (evt, name) {
      liked.add(name);
    })
    

    這就更簡單更清晰了,因?yàn)槲覀兺ㄟ^ document 在各個獨(dú)立的組件之間進(jìn)行消息傳遞,而組件之間是互不依賴的。(值得注意的是,在真正的應(yīng)用當(dāng)中,我們會使用一些諸如 Backbone 或 RSVP 庫來管理事件。我們出于讓例子盡量簡單的考慮,使用了 document 來觸發(fā)事件) 我們同時隱藏了很多臟活累活:比如在搜索結(jié)果對象里尋找被點(diǎn)“贊”的人,要比放在整個應(yīng)用的代碼里更好。更重要的是,我們現(xiàn)在可以寫出保障搜索結(jié)果對象正常工作的測試代碼了:

    var ul;
    var data = [ /* 填入假數(shù)據(jù) */ ];
    
    // 確保點(diǎn)“贊”列表存在
    setup(function () {
      ul = $("
    
    *");
    });
    
    test("測試構(gòu)造函數(shù)", function () {
      var sr = new SearchResults(ul);
      // 斷言對象存在
      assert(sr);
    });
    
    test("測試收到的搜索結(jié)果", function () {
      var sr = new SearchResults(ul);
      sr.setResults(data);
    
      // 斷言搜索結(jié)果占位元素已經(jīng)不存在
      assert.equal(ul.find(".no-results").length, 0);
      // 斷言搜索結(jié)果的子元素個數(shù)和搜索結(jié)果的個數(shù)相同
      assert.equal(ul.find("li.result").length, data.length);
      // 斷言搜索結(jié)果的第一個子元素的 "data-name" 的值和第一個搜索結(jié)果相同
      assert.equal(
        ul.find("li.result").first().attr("data-name"),
        data[0].name
      );
    });
    
    test("測試點(diǎn)“贊”按鈕", function() {
      var sr = new SearchResults(ul);
      var flag;
      var spy = function () {
        flag = [].slice.call(arguments);
      };
    
      sr.setResults(data);
      $(document).on("like", spy);
    
      ul.find("li").first().find(".like.btn").click();
    
      // 斷言 `document` 收到了點(diǎn)“贊”的消息
      assert(flag, "事件被收到了");
      // 斷言 `document` 收到的點(diǎn)“贊”消息,其中的名字是第一個搜索結(jié)果
      assert.equal(flag[1], data[0].name, "事件里的數(shù)據(jù)被收到了" );
    });
    

    和服務(wù)器直接的交互是另外一個有趣的話題。原版的代碼包括一個 $.ajax() 的請求,以及一個直接操作 DOM 的回調(diào)函數(shù):

    $.ajax("/data/search.json", {
      data : { q: query },
      dataType : "json",
      success : function( data ) {
        loadTemplate("people-detailed.tmpl").then(function(t) {
          var tmpl = _.template( t );
          resultsList.html( tmpl({ people : data.results }) );
          pending = false;
        });
      }
    });
    

    同樣,我們很難為這樣的代碼撰寫測試。因?yàn)楹芏嗖煌墓ぷ魍瑫r發(fā)生在這一小段代碼中。我們可以重新組織一下數(shù)據(jù)處理的部分:

    var SearchData = function () { };
    
    SearchData.prototype.fetch = function (query) {
      var dfd;
    
      // 如果搜索關(guān)鍵字為空,則不做任何事,立刻 `promise()`
      if (!query) {
        dfd = $.Deferred();
        dfd.resolve([]);
        return dfd.promise();
      }
    
      // 否則,向服務(wù)器請求搜索結(jié)果并把在得到結(jié)果之后對其數(shù)據(jù)進(jìn)行包裝
      return $.ajax( "/data/search.json", {
        data : { q: query },
        dataType : "json"
      }).pipe(function( resp ) {
        return resp.results;
      });
    };
    

    現(xiàn)在我們改變了獲得搜索結(jié)果這部分的代碼:

    var resultList = new SearchResults("#results");
    var searchData = new SearchData();
    
    // ...
    
    searchData.fetch(query).then(resultList.setResults);
    

    我們再一次簡化了代碼,并通過 SearchData 對象拋棄了之前應(yīng)用程序主函數(shù)里雜亂的代碼。同時我們已經(jīng)讓搜索接口變得可測試了,盡管現(xiàn)在和服務(wù)器通信這里還有事情要做。

    首先我們不是真的要跟服務(wù)器通信——不然這又變成集成測試了:諸如我們是有責(zé)任感的開發(fā)者,我們已經(jīng)確保服務(wù)器一定不會犯錯等等,是這樣嗎?為了替代這些東西,我們應(yīng)該“mock”(偽造) 與服務(wù)器之間的通信。Sinon 這個庫就可以做這件事。第二個障礙是我們的測試應(yīng)該覆蓋非理想環(huán)境,比如關(guān)鍵字為空。

    test("測試構(gòu)造函數(shù)", function () {
      var sd = new SearchData();
      assert(sd);
    });
    
    suite("取數(shù)據(jù)", function () {
      var xhr, requests;
    
      setup(function () {
        requests = [];
        xhr = sinon.useFakeXMLHttpRequest();
        xhr.onCreate = function (req) {
          requests.push(req);
        };
      });
    
      teardown(function () {
        xhr.restore();
      });
    
      test("通過正確的 URL 獲取數(shù)據(jù)", function () {
        var sd = new SearchData();
        sd.fetch("cat");
    
        assert.equal(requests[0].url, "/data/search.json?q=cat");
      });
    
      test("返回一個 promise", function () {
        var sd = new SearchData();
        var req = sd.fetch("cat");
    
        assert.isFunction(req.then);
      });
    
      test("如果關(guān)鍵字為空則不查詢", function () {
        var sd = new SearchData();
        var req = sd.fetch();
        assert.equal(requests.length, 0);
      });
    
      test("如果關(guān)鍵字為空也會有 promise", function () {
        var sd = new SearchData();
        var req = sd.fetch();
    
        assert.isFunction( req.then );
      });
    
      test("關(guān)鍵字為空的 promise 會返回一個空數(shù)組", function () {
        var sd = new SearchData();
        var req = sd.fetch();
        var spy = sinon.spy();
    
        req.then(spy);
    
        assert.deepEqual(spy.args[0][0], []);
      });
    
      test("返回與搜索結(jié)果相對應(yīng)的對象", function () {
        var sd = new SearchData();
        var req = sd.fetch("cat");
        var spy = sinon.spy();
    
        requests[0].respond(
          200, { "Content-type": "text/json" },
          JSON.stringify({ results: [ 1, 2, 3 ] })
        );
    
        req.then(spy);
    
        assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]);
      });
    });
    

    出于篇幅的考慮,這里對搜索框的重構(gòu)及其相關(guān)的單元測試就不一一介紹了。完整的代碼可以移步至此查閱。

    當(dāng)我們按照可測試的 JavaScript 的思路重構(gòu)代碼之后,我們最后用下面這段代碼開啟程序:

    $(function() {
      var pending = false;
    
      var searchForm = new SearchForm("#searchForm");
      var searchResults = new SearchResults("#results");
      var likes = new Likes("#liked");
      var searchData = new SearchData();
    
      $(document).on("search", function (event, query) {
        if (pending) { return; }
    
        pending = true;
    
        searchData.fetch(query).then(function (results) {
          searchResults.setResults(results);
          pending = false;
        });
    
        searchResults.pending();
      });
    
      $(document).on("like", function (evt, name) {
        likes.add(name);
      });
    });
    

    比干凈整潔的代碼更重要的,是我們的代碼擁有了更健壯的測試基礎(chǔ)作為后盾。這也意味著我們可以放心的重構(gòu)任意部分的代碼而不必?fù)?dān)心程序遭到破壞。我們還可以繼續(xù)為新功能撰寫新的測試代碼,并確保新的程序可以通過所有的測試。

    測試會在宏觀上讓你變輕松

    看完這些的長篇大論你一定會說:“納尼?我多寫了這么多代碼,結(jié)果還是做了這么一點(diǎn)事情?”

    關(guān)鍵在于,你做的東西早晚要放到網(wǎng)上的。同樣是花時間解決問題,你會選擇在瀏覽器里點(diǎn)來點(diǎn)去?還是自動化測試?還是直接在線上讓你的用戶做你的小白鼠?無論你寫了多少測試,你寫好代碼,別人一用,多少會發(fā)現(xiàn)點(diǎn) bug。

    至于測試,它可能會花掉你一些額外的時間,但是它到最后真的是為你省下了時間。寫測試代碼測出一個問題,總比你發(fā)布到線上之后才發(fā)現(xiàn)有問題要好。如果有一個系統(tǒng)能讓你意識到它真的能避免一個 bug 的流出,你一定會心存感激。

    額外的資源

    這篇文章只能算是 JavaScript 測試的一點(diǎn)皮毛,但是如果你對此抱有興趣,那么可以繼續(xù)移步至:

    幻燈演示 2012 Full Frontal conference in Brighton, UK

    Grunt 一個可以進(jìn)行自動化測試等諸多事情的工具

    測試驅(qū)動的 JavaScript 開發(fā) 及其 中文版

    文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85502.html

    相關(guān)文章

    • 編寫可測試的Go代碼

      摘要:功能測試函數(shù)功能測試函數(shù)需要接收類型的單一參數(shù),類型用來管理測試狀態(tài)和支持格式化的測試日志。測試函數(shù)的相關(guān)說明,可以通過來查看幫助文檔。下面是一個例子被測試的進(jìn)程退出函數(shù)測試進(jìn)程退出函數(shù)的測試函數(shù)參考資料原文鏈接 原文鏈接:http://tabalt.net/blog/golang... Golang作為一門標(biāo)榜工程化的語言,提供了非常簡便、實(shí)用的編寫單元測試的能力。本文通過Golan...

      khlbat 評論0 收藏0
    • MVC

      摘要:模式的目的是實(shí)現(xiàn)一種動態(tài)的程序設(shè)計,使后續(xù)對程序的修改和擴(kuò)展簡化,并且使程序某一部分的重復(fù)利用成為可能。它處理事件并作出響應(yīng)。事件包括用戶的行為和數(shù)據(jù)上的改變。此外,提高了應(yīng)用程序的靈活性和可配置性。 我的博客地址 → MVC | The story of Captain,轉(zhuǎn)載請注明出處。 MVC模式 (Model–View–Controller)是軟件工程中的一種軟件架構(gòu)模式,把軟...

      luck 評論0 收藏0
    • 全棧工程師的武器——MEAN

      摘要:自年發(fā)布以來,走過了漫長的道路。一下子,工程師認(rèn)為自己不只是前端開發(fā)者了。這種趨勢被稱為全棧的或純的解決方案??梢哉J(rèn)為它是文檔結(jié)構(gòu)的數(shù)據(jù)庫,而不是由行列表組成的數(shù)據(jù)庫。也是高度可測試的,這是很重要的。 JavaScript自1995年發(fā)布以來,走過了漫長的道路。已經(jīng)有了幾個主要版本的ECMAScript規(guī)范,單頁Web應(yīng)用程序也慢慢興起,還有支持客戶端的JavaScript框架。作為一...

      chanjarster 評論0 收藏0
    • [ 學(xué)習(xí)路線 ] 2015 前端(JS)工程師必知必會 (2)

      摘要:轉(zhuǎn)自前端外刊評論非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下上次我寫前端工程師必知必會已經(jīng)是三年前了,那是我寫過最火的文章了。測試的第二大障礙是工具。 轉(zhuǎn)自:前端外刊評論 非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下........ 上次我寫《前端工程師必知必會》已經(jīng)是三年前了,那是我寫過最火的文章了。三年了,我仍然會在Twitter上...

      stefan 評論0 收藏0
    • [ 學(xué)習(xí)路線 ] 2015 前端(JS)工程師必知必會 (2)

      摘要:轉(zhuǎn)自前端外刊評論非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下上次我寫前端工程師必知必會已經(jīng)是三年前了,那是我寫過最火的文章了。測試的第二大障礙是工具。 轉(zhuǎn)自:前端外刊評論 非常感謝,翻譯的很好,受益很多,轉(zhuǎn)到此處讓前端小伙伴們也驚呆下........ 上次我寫《前端工程師必知必會》已經(jīng)是三年前了,那是我寫過最火的文章了。三年了,我仍然會在Twitter上...

      liaorio 評論0 收藏0

    發(fā)表評論

    0條評論

    最新活動
    閱讀需要支付1元查看
    <