11月阅读周·编写可测试的JavaScript代码:代码覆盖率之覆盖率基础理论

举报
叶一一 发表于 2024/11/22 11:30:19 2024/11/22
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》、《JavaScript异步编程设计快速响应的网络应用》

当前阅读周书籍编写可测试的JavaScript代码

覆盖率基础理论

尽管代码覆盖率度量可能会产生误导,但它们仍然至关重要。代码覆盖率通常与单元测试关联在一起,但同样也可以很容易地从集成测试生成代码覆盖率。很容易就可以将多个代码覆盖率报告合并成一个单独的报告,该报告包括所有的单元测试和集成测试,从而全面了解完整的测试套件都覆盖了哪些代码。

无论使用哪种覆盖率工具,其流程都是类似的:为代码覆盖率信息构建相应的JavaScript文件,部署或练习这些文件,并把覆盖率结果推送并持久化到一个本地文件中,也可以将不同测试的覆盖率结果组合在一起,生成漂亮的HTML输出,或者仅仅为上游工具或报告获取相应的覆盖率数字和百分比。

是否要进行代码覆盖率计算?如果是,一行代码会执行多少次?这对衡量测试代码的有效性非常有用。理论上讲,“覆盖”的代码行数越多,测试就越完整。然而,代码覆盖率和测试完整性之间的联系是很脆弱的。

这是一个简单的Node.js函数,用于返回给定股票代码的当前价格:

/**
 * Return current stock price for given symbol
 * in the callback
 * @method getPrice
 * @param symbol <String> the ticker symbol
 * @param cb <Function> callback with results cb(error, value)
 * @param httpObj <HTTP> Optional HTTP object for injection
 * @return nothing
 **/
function getPrice(symbol, cb, httpObj) {
  var http = httpObj || require('http'),
    options = {
      host: download.finance.yahoo.com, // thanks yahoo!
      path: '/d/quotes.csv?s' + symbol + '&f=l1',
    };

  http.get(options, function (res) {
    res
      .on('data', function (d) {
        cb(null, d);
      })
      .on('error', function (e) {
        cb(e.message);
      });
  });
}

传入股票代号和回调,该函数就会获取该股票的当前价格。它遵循了标准的回调约定,如果出现错误,会将错误信息作为第一个参数传递给回调函数。还要注意,此函数允许注入http对象,以方便进行测试。这种做法,该函数和http对象之间是松耦合的,大大提高了可测试性(也可以使用Mockery)。在普通生产环境下,该值默认为系统提供的http对象,但同时允许stub或mock一个http对象进行测试。当函数和外部对象有紧耦合关系时,对于可测试性和松耦合来说,允许注入对象通常是值得的。

这里是依赖注入让测试更容易的另外一个例子。除了可测试性,如果主机需要HTTPS来提供股票价格服务呢?甚至有一些股票使用HTTPS,其他的还是使用HTTP呢?通过注入提供的灵活性是一件好事,我们可以很容易地利用这一点。

为最大代码覆盖率编写测试用例几乎是最重要的过程了。首先,我们stub一个http对象:

/**
 * A simple stub for http object
 **/
var events = require('events').eventemitter,
  util = require('util'),
  myhttp = function () {
    // dummy up nodejs's 'http' object
    var _this = this;
    events.call(this);
    this.get = function (options, cb) {
      cb(_this);
      return this;
    };
  };
util.inherits(myhttp, events);

重要的是要认识到,我们并不测试Node.js提供的http模块(我们也不想测试)。是的,可能会有bug,但我们的测试并不尝试去查找这些Bug。我们只是想测试该函数中的代码,而不是其他任何外部对象(不管谁写的或者是来自哪里)。在代码和其依赖项交互时,集成测试可能会发现一些Bug(希望如此)。

stub出的http对象将让测试变为可能。如下是我们如何使用YUI Test异步测试方法进行测试的代码(省略了YUI Test样板代码):

testPrice = function () {
  var symbol = 'YHOO',
    stockPrice = 50, // Wishful thinkinking??
    _this = this,
    http = new myhttp();
  getPrice(symbol, function (err, price) {
    _this.resume(function () {
      y.Assert.areEqual(stockPrice, price, 'prices not equal!');
    }, http); // Inject our 'http' object
  });
  http.fire('data', stockPrice); // Our mock data
  this.wait(1000);
};

上述测试是一个基本的成功测试用例。测试失败情况的话,我们只需在http对象上触发error事件即可:

testPrice = function () {
  var symbol = 'YHOO',
    _this = this,
    http = new myhttp();
  getPrice(symbol, function (err, price) {
    _this.resume(function () {
      y.Assert.areEqual(err, 'an error', 'Did not get error!');
    }, http);
  });
  http.fire('error', { message: 'an error' });
  this.wait(1000);
};

有了这两个测试,代码覆盖率数据就会显示100%的代码覆盖率——神奇!这意味着,不用写更多的测试,getPrice函数就能完全按照预期进行工作,是吗?当然不是。这就是为什么高代码覆盖率可能会误导人的原因。这个函数并没有被完全测试,但覆盖率数据却显示成完全测试了。

同样有趣的是测试代码的数量和被测代码的数量的对比。我们编写了近三倍的测试代码来测试该函数——如果算上不在这里列出的YUI Test样板代码的话,可能更多。虽然做到了100%的代码覆盖率,但我们还不能信誓旦旦地说,有了这些测试,getPrice函数就100%可靠。这是有时对单元测试投诉的关键:它阻碍并降低了开发速度。遗憾的是,随着时间的推移,Web应用程序越来越重要,单元测试——常规测试——通常会遭到冷遇。最好的情况,也是在交付代码之后才创建单元测试,而最坏的情况,则是永远都不创建单元测试。这是很有问题的,因为后测试既不会带来益处,也不会像在开发过程中(或之前)测试那么高效。预防为主,治疗为辅。

总结

高覆盖率百分比会误导,但低覆盖率不会。即使大多数(甚至所有)行的代码都被一个测试或一组测试执行了,也并不一定意味着所有边界测试都测试了,甚至也不意味着一般测试被测试完了。然而,低代码覆盖率百分比则可以清晰地显示哪些行代码没被练习或测试。这是明确下一个测试目标的强有力指示。


作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。