Vue_TS_测试

此文项目代码:https://github.com/bei-yang/I-want-to-be-an-architect
码字不易,辛苦点个star,感谢!

引言


此篇文章主要涉及以下内容:

  1. TypeScript
  2. 使用TypeScript编写vue应用
  3. vue测试
  4. 写易于测试的vue组件和代码

学习资源


  • TypeScript参考
  • Vue中的TypeScript
  • 单元测试
  • 端到端测试参考

TypeScript


TypeScript是JavaScript的超集,它可编译为纯JavaScript,是一种给JavaScript添加特性的语言扩展。ts有如下特点:

  • 类型注解和编译时类型检查
  • 基于类的面向对象编程
  • 泛型
  • 接口
  • 声明文件

ts和es6

Vue_TS_测试_第1张图片

typescript是angular2的开发语言,Vue3正在使用TS重写

准备工作

新建一个基于ts的vue项目

vue create vue-ts

选项选择:

  • 自定义选项
  • 添加ts支持
  • 基于类的组件
  • tslint

浏览基本项目结构
新建一个组件,components/Hello.vue

/* 展示模板 */



在App.vue中引入

import Hello from './components/Hello.vue'
@component({
  components:{
    HelloWorld,
    Hello
  }
})
export default class App extendds Vue{}

类型注解和编译时类型检查

定义变量后,可以通过冒号来指定类型注解

//  Hello.vue
let name='xxx';  //  类型推论
let title:string= '123455';  // 类型注解
name=2;  //  错误
title=4;  // 错误
Vue_TS_测试_第2张图片

数组类型

let names:string[];
names=['Tom'];  //  或Array

任意类型

let foo:any='xxx'
foo=3

//  any类型也可用于数组
let list:any[]=[1,true,'free'];
list[1]=100;

函数中使用类型

function greeting(person:string):string{
  return 'Hello'+person;
}
// void类型,常用于没有返回值的函数
function warnUser():void{alert('this is my warning message');}

内置的类型
1. string
2. number
3. boolean
4. void函数不返回值
5. any任意类型

范例,Hello.vue






函数

必填参:参数一旦声明,就要求传递,且类型需符合

function sayHello(name:string,age:number):string{
  console.log(name,age)
}
sayHello(11,12)  // 报错,与指定类型不一致
sayHello('xxx','xxx')  //  报错,与指定类型不一致

可选参数:参数名后面加上句号,变成可选参数

function sayHello(name:string,age?:number):string{
  console.log(name,age)
}

参数默认值

function sayHello(name:string,age:number=20):string{
  console.log(name,age)
}

函数重载:以参数数量或类型区分多个同名函数

function add(a:number,b:number):string;
function add(a:string,b:string):string;
function add(a:any,b:any):string{
  if(typeof a==='number'){
    return a+b;
  }else{
    return a+b
  }
}
console.log(add(1,1));
console.log(add('foo','bar'));

面向对象:

  • 通过extends实现继承
  • 使用public等访问修饰符实现封装
  • 通过方法覆盖实现多态
class Shape {
  readonly foo: string = "foo";
  protected area: number;

  constructor(public color: string, width: number, height: number) {
    this.area = width * height;
  }

  shoutout() {
    return (
      "I'm " + this.color + " with an area of " + this.area + " cm squared."
    );
  }
}

class Square extends Shape {
  constructor(color: string, side: number) {
    super(color, side, side);
    console.log(this.color);
  }
  shoutout() {
    return "我是" + this.color + " 面积是" + this.area + "平方厘米";
  }
}
const square: Square = new Square("blue", 2);
console.log(square.shoutout());

注意事项:

  • 私有private:当成员被标记成private时,它就不能在声明它的类的外部访问。
  • 保护protected:protected成员在派生类中仍然可以访问。
  • 只读readonly:只读属性必须在声明时或构造函数里被初始化。
  • 参数属性:给构造函数的参数加上修饰符,能够定义并初始化一个成员属性。
 class Animal{
   constructor(private name:string){//  定义name属性并将构造参赋值给他}
 }
  • 存取器:当获取和设置属性时有额外逻辑时可以使用存取器(又称getter、setter)
class Employee{
 private _fullName:string='Mike James';
 get fullName():string{
   return this._fullName;
 }
 set fullName(newName:string){
   console.log('您修改了用户名');
   this._fullName=newName;
 }
}
const employee=new Employee();
employee.fullName='Bob Smith';

范例代码:通过类可以声明自定义类型约束数据结构,Hello.vue

// 定义一个特性类,拥有更多属性
class Feature {
  constructor(public id: number, public name: string, public version: string) {}
}

// 可以对获取的数据类型做约束
@Component
export default class HelloWorld extends Vue {
  private features: Feature[]

  constructor() {
    super()
    this.features = []
  }

  created() {
    setTimeout(()=>{
      // 数据结构相同即可,不必是Feature实例
      this.features=[
        {id:1,name:'类型注解',version:'2.0'},
        {id:2,name:'编译型语言',version:'1.0'}
      ];
    },1000)
  }
}

// template中的变化
  • {{feature.name}} ,{{feature.version}}
  • 范例:利用getter设置计算属性

  • 特性数量:{{count}}
  • get count(){ return this.features.length; }

    接口

    接口仅约束结构,不要求实现,使用更简单

    interface Person {
      firstName: string
      lastName: string
    }
    function greeting(person:Person){
      return 'Hello,'+person.firstName+" "+person.lastName;
    }
    const user={firstName:'Jane',lastName:'User'};
    console.log(user)
    console.log(greeting(user))
    

    面向接口编程

    interface Person {
      firstName: string
      lastName: string
      sayHello(): string // 要求实现方法
    }
    // 实现接口
    class Greeter implements Person {
      constructor(public firstName = '', public lastName = '') {}
      sayHello() {
        return 'Hello,' + this.firstName + ' ' + this.lastName
      }
    }
    // 面向接口编程
    function greeting(person: Person) {
      return person.sayHello()
    }
    // const user = { firstName: 'Jane', lastName: 'User' }
    const user = new Greeter('Jane', 'User') // 创建对象实例
    console.log(user)
    console.log(greeting(user))
    

    范例:修改Feature为接口形式

    
    

    泛型

    泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

    // 不用泛型
    // interface Result {
    //   ok: 0 | 1
    //   data: Feature
    // }
    
    //  使用泛型
    interface Result {
      ok: 0 | 1
      data: T[]
    }
    

    范例:使用泛型约束接口返回类型

    // Hello.vue
    function getData(): Result {
      const data: any[] = [
        { id: 1, name: '类型注解', version: '2.0' },
        { id: 2, name: '编译性语言', version: '1.0' }
      ]
      return { ok: 1, data }
    }
    

    使用接口

    created(){
      this.features=getData().data;
    }
    

    甚至返回Promise

    function getData(): Promise> {
      const data: any[] = [
        { id: 1, name: '类型注解', version: '2.0' },
        { id: 2, name: '编译型语言', version: '1.0' }
      ]
      return Promise.resolve({ ok: 1, data } as Result)
    }
    

    使用

    async created(){
      const result=await getData();
      this.features=result.data;
    }
    

    装饰器

    装饰器实际上是工厂函数,传入一个对象,输出处理后的新对象。
    典型应用是组件装饰器@Component

    @Component
    export default class Hello extends Vue {}
    

    若不加小括号,则装饰器下面紧挨着的对象就是目标对象

    如果装饰器需要配置,则要以函数形式使用并传入配置

    @Component({
      props:{ //  属性也可以在这里配置
        name:{
          type:String,
          default:'匿名'
        }
      }
    })
    export default class Hello extends Vue {}
    
    // 类似的还有App.vue中配置的依赖组件选项components
    

    范例:事件处理@Emit
    新增特性时派发事件通知父组件,Hello.vue

    // 通知父类新增事件,若未指定事件名则函数名作为事件名(羊肉串形式)
    @Emit()
    private addFeature(event:any){  // 若没有返回值形参将作为事件参数
      const feature={name:event.target.value,id:this.features.length+1};
      this.features.push(feature);
      event.target.value='';
      return feature; // 返回值作为事件参数
    }
    

    父组件接收并处理,App.vue

    @Watch('msg')
    onRouteChange(val:string,oldVal:any){
      console.log(val,oldVal);
    }
    

    测试


    测试分类

    常见的开发流程里,都有测试人员,这种我们成为黑盒测试,测试人员不管内部实现机制,只看最外层的输入输出,比如你写一个加法的页面,会设计N个case,测试加法的正确性,这种代码里,我们称之为E2E测试
    更负责一些的我们称之为集成测试,就是集合多个测试过的单元一起测试。
    还有一种测试叫做白盒测试,我们针对一些内部机制的核心逻辑,使用代码进行编写,我们称之为单元测试

    测试是前端开发人员进阶必备的技能
    我们日常使用console,算是测试的雏形

    编写测试代码的好处

    • 提供描述组件行为的文档
    • 节省手动测试的事件
    • 减少研发新特性时产生的bug
    • 改进设计
    • 促进重构

    自动化测试使得大团队中的开发者可以维护复杂的基础代码。让你改代码不再小心翼翼。


    Vue_TS_测试_第3张图片

    准备工作

    在vue中,推荐用Mocha+Chai或者Jest,演示代码使用Jest,它们语法基本一致
    新建vue项目,手动选择特性,添加Unit Testing和E2E Testing


    Vue_TS_测试_第4张图片

    单元测试解决方案选择:Jest


    Vue_TS_测试_第5张图片

    端到端测试解决方案选择:Cypress
    Vue_TS_测试_第6张图片

    单元测试

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。
    新建test/unit/test.spec.js,*.spec.js是命名规范,写一下代码:

    function add(num1,num2){
      return num1+num2
    }
    
    // 测试套件 test suite
    describe('test',()=>{
      // 测试用例 test case
      interface('测试add函数',()=>{
        // 断言assert
        expect(add(1,3)).toBe(3)
        expect(add(1,3)).toBe(4)
        expect(add(-2,3)).toBe(1)
      })
    })
    

    执行单元测试

    npm run test:unit
    

    api介绍

    • describe:定义一个测试套件
    • it:定义一个测试用例
    • expect:断言的判断条件
    • toBe:断言的比较结果

    测试Vue组件


    一个简单的组件

    
    
    
    
    // 导入 Vue.js 和组件,进行测试
    import Vue from 'vue'
    import KaikebaComp from '@/components/Kaikeba.vue'
    
    describe('KaikebaComp', () => {
      // 检查原始组件选项
      it('由created生命周期', () => {
        expect(typeof KaikebaComp.created).toBe('function')
      })
    
      // 评估原始组件选项中的函数的结果
      it('初始data是vue-text', () => {
        // 检查data函数存在性
        expect(typeof KaikebaComp.data).toBe('function')
        // 检查data返回的默认值
        const defaultData = KaikebaComp.data()
        expect(defaultData.message).toBe('vue-text')
      })
    })
    

    检查mounted之后

    it('mount之后测试',()=>{
      const vm=new Vue(KaikebaComp).$mount()
      expect(vm.message).toBe('test vue组件')
    }
    

    用户点击

    和写vue没什么本质区别,只不过我们用测试的角度去写代码,vue提供了专门针对测试的@vue/test-utils

    it('按钮点击后',()=>{
      const wrapper=mount(KaikebaComp)
      wrapper.find('button').trigger('click')
      expect(wrapper.vm.message).toBe('按钮点击')
      //  测试HTML渲染结果
      expect(wrapper.find('span').html()).toBe('按钮点击')
    })
    

    测试覆盖率

    jest自带覆盖率,如果用的mocha,需要使用istanbul来统计覆盖率
    package.json里修改jest配置

    'jest':{
      'collectCoverage':true,
      'collectCoverageFrom':['src/**/*.{js,vue}'],
    }
    

    在此执行npm run test:unit
    可以看到我们kaikeba.vue的覆盖率是100%。

    端到端测试E2E

    借用浏览器的能力,站在用户测试人员的角度,输入框,点击按钮等,安全模拟用户,这个和具体的框架关系不大,完全模拟浏览器行为。

    运行E2E测试

    npm run test:e2e
    

    E2E了解即可。

    你的赞是我前进的动力

    求赞,求评论,求分享...

    你可能感兴趣的:(Vue_TS_测试)