单元测试的难题

173 阅读3分钟

测试应该是为了验证你的应用程序是否正常工作。如果你做了一些重构,而你的应用程序仍然工作,但你的测试却失败了,那么你真的是在测试正确的东西吗?

我最近在工作中遇到了这个问题。我在那里的大部分时间都在做React/Redux/Typescript的前端工作。我注意到,较低级别的组件有一些令人头疼的条件,以确定一个编号方案。有10个这样的组件,它们各自根据状态有条件地渲染,而且它们必须保持连续的编号。例如,下面的表格代表了每个组件的显示状态和编号方案的例子。

组件显示?编号
A1
B2
C错误
D3
E错误
F错误
G虚假
H4
I错误
J错误

这个重构似乎很简单--我将创建一个选择器,将状态作为一个参数,并输出一个以组件名称为键、以编号为值的对象。下面是一个简化的版本,输出上述信息,但这个函数显然有更多的逻辑在里面。

const getNumbers = (state) => {
  return {
    A: 1,
    B: 2,
    D: 3,
    H: 4,
  };
};

所以,如果我把这个选择器映射到我的低级组件中,我就会一直有正确的编号,而不会有一堆多余的逻辑。

const ComponentA = (props) => {
  return (
    <>
      <h1>{props.number}. Some Title</h1>
      <p>Some content</p>
    </>
  );
};

const mapStateToProps = (state) => ({
  number: getNumbers(state).A,
});

export default connect(mapStateToProps)(ComponentA);

这真是太好了我很兴奋地运行我的测试。关于测试的最好的事情之一是你可以重构东西,并且相当有信心你没有破坏任何东西,因为你的测试仍然通过。

测试并没有通过

正如你可能已经猜到的,我的测试没有通过。我有一堆由Storybook生成的失败的快照--我所有的数字现在都显示为undefined!

事实证明,我有一堆测试在组件级别,没有连接到测试中的Redux商店。这意味着我在测试中使用了<ComponentA /> ,但没有把number 这个道具传给它,因此numberundefined

测试现实的东西

未连接的组件从未在生产中使用过,它也不是用户所看到的真实的代表。所以这里的一个启示是,我们应该测试现实的东西。如果这些低级别的断言对我们的用户没有任何影响,我们是否真的关心这些断言是否通过?

另外,有一些伟大的人建议主要写集成测试。我绝对理解为什么,特别是当我在低级测试中失败的原因实际上不会在用户面前表现出来时。

单元测试的难题

为什么我一开始就写单元测试?

嗯,我这样做是因为我非常喜欢单元测试驱动开发(TDD)。在集成或端到端测试之前,需要大量的 "在黑暗中飞行",以确保事情在一起运作良好。通过逐步测试单元,我觉得我有一个更好的机会让它们一起工作得很好。

所以,如果在开发过程中写单元测试很方便,但最后进行集成和端到端测试更有价值,我们该怎么做?

我肯定会在开发过程中继续写单元测试;这是一个在开发过程中对我很有用的工具。但是,我将努力使它们保持相对最小和现实。就我前面提到的组件而言,我至少应该在其redux连接的形式下测试该组件,而不是完全孤立地测试。

我还将投入更多的时间来编写集成和端到端的测试。如果我破坏了这些测试,这将更能说明一个真正的、面向用户的问题。