💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
现在,我将继续添加更多测试。我遵循的风格是:观察被测试类应该做的所有事情,然后对这个类的每个行为进行测试,包括各种可能使它发生异常的边界条件。这不同于某些程序员提倡的“测试所有`public`函数”的风格。记住,测试应该是一种风险驱动的行为,我测试的目标是希望找出现在或未来可能出现的bug。所以我不会去测试那些仅仅读或写一个字段的访问函数,因为它们太简单了,不太可能出错。 这一点很重要,因为如果尝试撰写过多测试,结果往往反而导致测试不充分。事实上,即使我只做一点点测试,也从中获益良多。测试的重点应该是那些我最担心出错的部分,这样就能从测试工作中得到最大利益。 接下来,我的目光落到了代码的另一个主要输出上,也就是总利润的计算。我同样可以在一开始的测试夹具上,对总利润做一个基本的测试。 > ![](https://box.kancloud.cn/9cf522e33e311401bf0d755d003df8ea_19x20.jpeg) 编写未臻完善的测试并经常运行,好过对完美测试的无尽等待。 ``` describe('province', function() {  it('shortfall', function() {   const asia = new Province(sampleProvinceData());   expect(asia.shortfall).equal(5);  });  it('profit', function() {   const asia = new Province(sampleProvinceData());   expect(asia.profit).equal(230);  }); }); ``` 这是最终写出来的测试,但我是怎么写出它来的呢?首先我随便给测试的期望值写了一个数,然后运行测试,将程序产生的实际值(`230`)填回去。当然,我也可以自己手动计算,不过,既然现在的代码是能正常运行的,我就选择暂时相信它。测试可以正常工作后,我又故技重施,在利润的计算过程插入一个假的乘以2逻辑来破坏测试。如我所料,测试会失败,这时我才满意地将插入的假逻辑恢复过来。这个模式是我为既有代码添加测试时最常用的方法:先随便填写一个期望值,再用程序产生的真实值来替换它,然后引入一个错误,最后恢复错误。 这个测试随即产生了一些重复代码——它们都在第一行里初始化了同一个测试夹具。正如我对一般的重复代码抱持怀疑,测试代码中的重复同样令我心生疑惑,因此我要试着将它们提到一处公共的地方,以此来消灭重复。一种方案就是把常量提取到外层作用域里。 ``` describe('province', function() {  const asia = new Province(sampleProvinceData()); // DON'T DO THIS  it('shortfall', function() {   expect(asia.shortfall).equal(5);  });  it('profit', function() {   expect(asia.profit).equal(230);  }); }); ``` 但正如代码注释所说的,我从不这样做。这样做的确能解决一时的问题,但共享测试夹具会使测试间产生交互,这是滋生bug的温床——还是你写测试时能遇见的最恶心的bug之一。使用了JavaScript中的`const`关键字只表明`asia`的引用不可修改,不表明对象的内容也不可修改。如果未来有一个测试改变了这个共享对象,测试就可能时不时失败,因为测试之间会通过共享夹具产生交互,而测试的结果就会受测试运行次序的影响。测试结果的这种不确定性,往往使你陷入漫长而又艰难的调试,严重时甚至可能令你对测试体系的信心产生动摇。因此,我比较推荐采取下面的做法: ``` describe('province', function() {  let asia;  beforeEach(function() {   asia = new Province(sampleProvinceData());  });  it('shortfall', function() {   expect(asia.shortfall).equal(5);  });  it('profit', function() {   expect(asia.profit).equal(230);  }); }); ``` `beforeEach`子句会在每个测试之前运行一遍,将`asia`变量清空,每次都给它赋一个新的值。这样我就能在每个测试开始前,为它们各自构建一套新的测试夹具,这保证了测试的独立性,避免了可能带来麻烦的不确定性。 对于这样的建议,有人可能会担心,每次创建一个崭新的测试夹具会拖慢测试的运行速度。大多数时候,时间上的差别几乎无法察觉。如果运行速度真的成为问题,我也可以考虑共享测试夹具,但这样我就得非常小心,确保没有测试会去更改它。如果我能够确定测试夹具是百分之百不可变的,那么也可以共享它。但我的本能反应还是要使用独立的测试夹具,可能因为我过去尝过了太多共享测试夹具带来的苦果。 既然我在`beforeEach`里运行的代码会对每个测试生效,那么为何不直接把它挪到每个`it`块里呢?让所有测试共享一段测试夹具代码的原因,是为了使我对公用的夹具代码感到熟悉,从而将眼光聚焦于每个测试的不同之处。`beforeEach`块旨在告诉读者,我使用了同一套标准夹具。你可以接着阅读`describe`块里的所有测试,并知道它们都是基于同样的数据展开测试的。