前端自动化测试-TDD

TDD

何为tdd (测试驱动的开发)

  • 流程: 1 编写测试用例 2 运行测试,测试用力无法通过测试 3 编写代码,使测试用例通过测试 4 优化代码,完成开发。 5 新增功能,重复以上步骤。
  • 以测试为驱动流程的开发
  • 好处: 1 长期减少回归bug 2 代码指令更好(组织,可维护性) 3 测试覆盖率高 4 错误测试代码不高

基本环境配置

脚手架create-react-app已经内置了jest。自己搭配的项目需要安装,

yarn add jest  ts-jest babel-jest @types/jest -D
复制代码

初始化git,使用git管理项目。
然后在项目下面配置jest.config.js文件,
在这里插入图片描述
在这里插入图片描述
执行yarn test的时候,jest就会通过文件的配置进行测试。
因为我们是react项目,所以需要使用Enzyme。

Enzyme 加强jest测试React组件的功能

enzyme 官网enzymejs.github.io/enzyme/

  • 编写最基本的测试用例
test("renders without crashing", () => {
  const div = document.createElement("div");
  // 测试App的挂载
  ReactDOM.render(<App />, div);
  const container = div.getElementsByClassName('app')
  console.log('container', container.length);
  expect(container.lenght).toBe(2)
});
复制代码

如上,通过ReactDOM挂载App后,可以判断app是否被挂载上去。

  • 像这种简单的测试组件的挂载,比较简单,但是单元测试的时候,希望可以测试组件里的状态等等,那么就比较麻烦了,Enzyme提供了这些功能。

在这里插入图片描述

// 安装依赖
yarn add enzyme enzyme-adapter-react-16 jest-enzyme @types/enzyme  @types/enzyme-adapter-react-16 -D
复制代码
// 测试组件的状态等,就得使用enzyme来测试
import {  shallow }from 'enzyme
test("renders without crashing", () => {
  // 对App做一层浅渲染,shallow只对一个组件做测试的时候使用,并不关心他的子组件。
  // 还可以使用mount,mount会把子组件也渲染出来,做集成测试的时候适合。
  const wrapper = shallow(<App />); //浅渲染App渲染到wrapper
  const continer = wrapper.find('[data-test="app"]');
  // wrapper.find(selector) 当前包装器的渲染树中查找与提供的选择器匹配的每个节点,如类选择器,id选择器等等。
  // 用了enzymen后可以安装jest-enzyme使用其他的匹配器。
  (expect(continer) as any).toExist();
  (expect(continer) as any).toHaveProp("title", "haha,test");
});
复制代码

如上,shallow只会对App测试的时候使用,他不关心他的子组件。
也可以使用mount,moUnt会将子组件也渲染出来,做集成测试的时候适合。
通过shallwo渲染的组件wrapper用法很像jq,如上就是判断App组件是否存在,并且存在特定的属性。
可以看到wrapper长这样

<div className="app1" title="haha,test" data-test="app">
        <TodoList />
      </div>
复制代码

还可以使用mount结合快照的形式,

//使用Mount结合快照
test.only("mounter snapshot", () => {
  const wrapper = mount(<App />); //浅渲染App渲染到wrapper
  console.log("wrapper", wrapper.debug());
  expect(wrapper).toMatchSnapshot(); //第一次会生成快照,第二次会比对,适合一些Ui不能随便改动的场景、
});
复制代码

如上,会生成快照,如果第二次App的ui改动了,就会报错,看mount帮我们渲染出来的wrapper

<App>
        <div className="app1" title="haha,test" data-test="app">
          <TodoList>
            <div>
              <Headers onAddItem={[Function (anonymous)]}>
                <div className="Header">
                  <input type="text" data-test="input" value="" onChange={[Function: onChange]} onKeyUp={[Function: onKeyUp]} />
                </div>
              </Headers>
              <div>
                ------------
              </div>
            </div>
          </TodoList>
        </div>
      </App>
复制代码

可以看到,把子组件也渲染出来了。
ui快照保存
在这里插入图片描述
这就是enzyme的基本用法了。

TDD开发todoList

效果就是输入框输入东西,在List中显示。
准备一个Header组件,用来输入内容。然后显示到List组件去。
那么测试用例应该如下:

test("header 组件包含一个input框", () => {
// 单独测试一个组件,所以使用shallow
  const wrapper = shallow(<Headers/>)
  const inputElem = wrapper.find("[data-test='input']")
  expect(inputElem.length).toBe(1)
});

it("header组件 input框初始化应该是''", () => {
  const wrapper = shallow(<Headers/>)
  const inputElem = wrapper.find("[data-test='input']")
  expect(inputElem.prop('value')).toEqual('')
})


it("header组件 input框内容,当用户输入时,会跟随变化''", () => {
  const wrapper = shallow(<Headers/>)
  const inputElem = wrapper.find("[data-test='input']") //找到输入框
  const userInput = '学习jest'
  // simulate触发事件
  inputElem.simulate('change', {
    target: {
      value: userInput
    }
  })
  // 数据改变
  const newInputElm =  wrapper.find("[data-test='input']")
  expect(newInputElm.prop('value')).toBe(userInput)
})


// 改变内容并且输入回车
test("header组件 input框输入回车的时候,如果input无内容,无操作", () => {
  const fn = jest.fn()
  const wrapper = shallow(<Headers onAddItem={fn}/>) // 传入回调函数
  const inputElem = wrapper.find("[data-test='input']")
  const userInput = '学习jest'
  // simulate触发事件
  inputElem.simulate('change', {
    target: {
      value: userInput
    }
  })
  inputElem.simulate('keyUp', {
   keyCode: 13 //回车事件
  })
  // 按下回车键,fn被调用,并且value被置为''
  expect(fn).toHaveBeenCalled()
  const newInputElm =  wrapper.find("[data-test='input']")
  expect(newInputElm.prop('value')).toBe('')
})

复制代码

通过上面的测试用来来写组件,应该是这样的。

import React from "react";

interface Props {
  onAddItem?: (v: string) => void;
}

function Headers({ onAddItem }: Props) {
  const [value, setValue] = React.useState("");

  return (
    <div className="Header">
      <input
        type="text"
        data-test="input"
        value={value}
        onChange={(e) => {
          setValue(e.target.value);
        }}
        onKeyUp={(e) => {
          if (e.keyCode === 13) {
            onAddItem && onAddItem(value);
            setValue("");
          }
        }}
      />
    </div>
  );
}

export { Headers };

复制代码

接着编写List组件内容。
先编写测试用例

import React from "react";
//import ReactDOM from "react-dom";

import Enzyme, { shallow, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import TodoList from '../../index'
Enzyme.configure({adapter: new Adapter()})
复制代码

初始化列表应该为空

test("toDoList 组件初始化列表为空", () => {
  const wrapper = shallow(<TodoList/>)
  const todoListItems = wrapper.find("[data-test='list-item']")
  expect(todoListItems.length).toBe(0)
});

复制代码

lists组件应该给Header传递一个onAddItem方法

test("toDoList 组件应该给Headers传递一个onADdItem方法", () => {
    const wrapper = shallow(<TodoList/>)
    const Headers = wrapper.find('Headers')
    expect(Headers.length).toBe(1)
    // headers组件应该有一个Props为onAddItem
    expect(Headers.prop('onAddItem')).toHaveLength(1)
  });
复制代码

当input回车键按下的时候,list组件应该新增内容

test.only("当Headers回车的时候, todoListItem应该新增内容", () => {
    const wrapper = shallow(<TodoList/>)
    const Headers = wrapper.find('Headers')
    const addFunc = Headers.prop('onAddItem')
    typeof addFunc === 'function' && addFunc('哈哈哈哈') //调用headers组建的addFunc
    const todoListItems = wrapper.find("[data-test='list-item']")
    expect(todoListItems.length).toBe(1)
    console.log('todoListItems', JSON.stringify(todoListItems));
    
  });
复制代码

先这三个逻辑,然后根据测试用例开发组件

import React, { useState, useCallback } from "react";

import { Headers } from "./componnets/Headers";

function TodoList() {
  const [item, setItems] = useState<string[]>([]);

  const onAddItem = useCallback((e) => {
    setItems((pre) => {
      return [...pre].concat(e);
    });
  }, []);

  return (
    <div>
      <Headers onAddItem={onAddItem} />
      <div>------------</div>
      {item.map((ctem) => {
       return  <div data-test="list-item" key={ctem}>{ctem}</div>;
      })}
    </div>
  );
}

export default TodoList;

复制代码
测试代码覆盖率

jest提供了测试代码覆盖率的问题:在package.json添加这句。

"covery": "jest --coverage --watchAll=false",
复制代码

执行过后会成成这样一份文件供你分析,可以看到两个主要的组件的覆盖率是100%,但是
在这里插入图片描述
Header组建的分支是75%,也就是说有if else没被俘获的判断,如在这里插入图片描述
可以直接点进去打开查看是什么原因;
这里少了个else的判断,所以需要添加多一个测试用例

test("header组件 input框按键不是回车的时候", () => {
  const fn = jest.fn();
  const wrapper = shallow(<Headers onAddItem={fn} />);
  const inputElem = wrapper.find("[data-test='input']");
  const userInput = "学习jest";
  // simulate触发事件
  inputElem.simulate("change", {
    target: {
      value: userInput,
    },
  });
  inputElem.simulate("keyUp", { keyCode: 0 });
  const newInputElm = wrapper.find("[data-test='input']");
  expect(newInputElm.prop("value")).toBe(userInput);
  expect(fn).toHaveBeenCalledTimes(0);
});

复制代码

测试组件按键不是回车事件的时候,然后在执行npm run covery。
在这里插入图片描述
这次可以看到百分之一百了。

总结

通过一个小的dmoe了解了tdd的流程,以测试用例驱动的开发,就是先写测试用例,再写组件内容。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享