原文摘自:https://dmitripavlutin.com/7-architectural-attributes-of-a-reliable-react-component/#6testableandtested
一个被验证过针对给定的输入会渲染出符合期望的输出的组件,称为 测试过的(tested) 组件; 一个 可测试的(testable) 组件意味着其易于测试
如何确保一个组件如期望的工作呢?你可以说:“我都自己手动试过的呀。”
如果你打算对每个组件的每个改动都手动验证的话,或早或晚的,你就会跳过这项苦差事了,而小的瑕疵早晚也会出现。
这就是对组件的自动化验证,也就是单元测试(unit test),为何重要的原因。单元测试保证了每次对组件做出的更改后,组件都能正确工作。
单元测试并不只与早期发现 bug 有关。另一个重要的方面是用其检验组件架构化水平优劣的能力。
我觉得这句话格外的重要:
一个 无法测试 或 难以测试 的组件,基本上就等同于 设计得很拙劣 的组件.
组件之所以难以测试时因为其有太多的 props、依赖、引用的模型和对全局变量的访问 -- 这都是不良设计的标志。
一个架构设计羸弱的组件,就会变成无法测试的,进而你就会简单的跳过单元测试,又导致了其保持未测试状态,这是一个恶性循环。
总之,许多应用为何是未测试状态的原因就是不良的组件设计。即便你想动手测试,也无处下手。
之前的文章中提及过 <Controls>
组件。
下面的代码测试了高度依赖于父组件结构的 <Controls>
版本:
import assert from 'assert';
import { shallow } from 'enzyme';
class Controls extends Component {
render() {
return (
<div className="controls">
<button onClick={() => this.updateNumber(+1)}>
Increase
</button>
<button onClick={() => this.updateNumber(-1)}>
Decrease
</button>
</div>
);
}
updateNumber(toAdd) {
this.props.parent.setState(prevState => ({
number: prevState.number + toAdd
}));
}
}
class Temp extends Component {
constructor(props) {
super(props);
this.state = { number: 0 };
}
render() {
return null;
}
}
describe('<Controls />', function() {
it('should update parent state', function() {
const parent = shallow(<Temp/>);
const wrapper = shallow(<Controls parent={parent} />);
assert(parent.state('number') === 0);
wrapper.find('button').at(0).simulate('click');
assert(parent.state('number') === 1);
wrapper.find('button').at(1).simulate('click');
assert(parent.state('number') === 0);
});
});
<Controls>
测试起来非常复杂,因为它关联了父组件的实现细节。
测试场景中需要一个额外的 <Temp>
组件,用来模拟父组件,检验 <Controls>
是否正确修改了父组件的状态。
当 <Controls>
独立于父组件的细节时,测试就简单了。让我们测试一下合理封装版本的 <Controls>
组件:
import assert from 'assert';
import { shallow } from 'enzyme';
import { spy } from 'sinon';
function Controls({ onIncrease, onDecrease }) {
return (
<div className="controls">
<button onClick={onIncrease}>Increase</button>
<button onClick={onDecrease}>Decrease</button>
</div>
);
}
describe('<Controls />', function() {
it('should execute callback on buttons click', function() {
const increase = sinon.spy();
const descrease = sinon.spy();
const wrapper = shallow(
<Controls onIncrease={increase} onDecrease={descrease} />
);
wrapper.find('button').at(0).simulate('click');
assert(increase.calledOnce);
wrapper.find('button').at(1).simulate('click');
assert(descrease.calledOnce);
});
});
封装好则测试易,反之不恰当的封装让测试变得困难。
可测试性是一个检验组件结构良好程度的实践标准。