在前后台开发的项目中,我们当前已经做到了后台依靠单元测试,完全的脱离前台进行开发。那么,在进行单台开发时,是否也可以做到只依赖于UML
图,不依赖于后台进行独立的开发呢?答案是肯定的。
本文旨在带领你:在前台完全脱离后台开发路上更近一步。
前期代码准备
在此,我们以近期开发的Alice学生管理系统为例,将学院管理的index
组件拿出来,以展示脱离后台的前台。
文件列表:
CollegeIndexComponent: 学院管理INDEX列表组件
College: 学院实体
CollegeService: 学院服务
其它辅助文件略
CollegeIndexComponent 学院管理INDEX列表组件
import {Component, OnInit} from '@angular/core';
import {CollegeService} from '../../../../../service/college.service';
import {College} from '../../../../../entity/college';
import {Page} from '../../../../../entity/page';
@Component({
selector: 'app-college-index',
templateUrl: './college-index.component.html',
styleUrls: ['./college-index.component.css']
})
export class CollegeIndexComponent implements OnInit {
page: number;
size: number;
total: number;
collegeList: Array;
constructor(private collegeService: CollegeService) {
}
ngOnInit() {
console.log(this.collegeService);
this.collegeList = new Array();
this.page = 1;
this.size = 5;
this.queryPage();
}
queryPage() {
this.collegeService.getCollegeByPage(this.page - 1, this.size)
.subscribe((data: Page) => {
this.collegeList = data.content;
this.total = data.totalElements;
}, () => {
console.log('network error');
});
}
}
College: 学院实体
import {Teacher} from './teacher';
import {Major} from './major';
/**
* 学院实体
*/
export class College {
id: number; // id
name: string; // 名称
teacherList: Teacher[]; // 负责教师
majorList: Major[];
[propName: string]: any;
/**
* 获取学院实体
*/
static instance(): College {
const college = new College();
college.id = 1;
college.name = '测试学院';
college.teacherList = new Array(
Teacher.instance()
);
college.majorList = new Array();
return college;
}
}
CollegeService: 学院服务
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { College } from '../entity/college';
import { Observable } from 'rxjs';
import { Page } from '../entity/page';
@Injectable({
providedIn: 'root',
})
export class CollegeService {
baseUrl = 'College';
constructor(protected http: HttpClient) {}
/**
*获取分页数据
*/
public getCollegeByPage(page: number, size: number): Observable> {
const params = {
page: page.toString(),
size: size.toString(),
};
return this.http.get>(this.baseUrl + '/page', {
params: params,
});
}
}
单元测试
代码如下:
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {CollegeIndexComponent} from './college-index.component';
import {NzGridModule, NzDividerModule, NzTableModule, NzFormModule} from 'ng-zorro-antd';
import {HttpClientModule} from '@angular/common/http';
import {RouterTestingModule} from '@angular/router/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
/**
* 对测试的描述:CollegeIndexComponent
*/
describe('CollegeIndexComponent', () => {
// 定义两个变量
let component: CollegeIndexComponent;
let fixture: ComponentFixture;
beforeEach(async(() => {
// TestBed工作台
// TestBed.configureTestingModule 配置要测试的模块
// 这贴近于现实生活。现实生活中,我们测试一块电表是否正确.
// 1. 需要一个专门用于测试的工作台
// 2. 需要对这个测试的工作进行配置,以让其符合测试电表的基础工作
TestBed.configureTestingModule({
// 声明要上工作台的组件
declarations: [CollegeIndexComponent],
// 配置测试的依赖,没有这些模块,测试就进行不了。
// 比如我们测试电表是否准确,需要有交流电,需要有电流表,需要有电压表等
imports: [NzGridModule,
HttpClientModule,
NzDividerModule,
NzTableModule,
RouterTestingModule,
ReactiveFormsModule,
FormsModule,
NzFormModule]
}).compileComponents(); // 配置完后,开始编译要测试组件
}));
// 每次测试前,均执行一遍
beforeEach(() => {
// fix = 固定。用工作台创建一个供测试的组件。
// 由于其想要被测试,就必然还需要其它的一些脚手架。
// 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件
fixture = TestBed.createComponent(CollegeIndexComponent);
// 实例化要测试的组件
component = fixture.componentInstance;
// 检测变化
fixture.detectChanges();
});
/**
* 测试方法的描述信息:should create
*/
it('should create', () => {
// 期待 组件 被成功创建
expect(component).toBeTruthy();
});
});
执行此单元测试,虽然能够通过,但将在控制台得到以下错误信息。
同时,展示的界面如下:
我们看到,在测试时依赖于httpClient
发起的请求,由于我们没有启动后台服务(即使启动了,使用绝对地址访问的方法也有问题),所以无法由后台获取数据。其实,即使是我们进行跨域的地址设置,由于后台我们往往会进行权限验证(是否登录,当前登录用户是否有当前操作的权限等),所以:想发起一个正常的请求,并不容易。
下面,让我们在不破坏原有服务层的基础上,自定义返回数据,从而规避http
请求,达到不需要后台,便可以看到真实数据的目的。
自定义返回数据
在单元测试时,调用beforeEach
及it
方法时均可以将@angular/core/testing -> injetct()
传入。在此,我们以beforeEach
为例,注入CollegeService
,并在测试组件实例化以前,对其getCollegeByPage
方法的返回值,进行重写。
/**
* 每次测试前,均执行一遍
* inject([], () => {}) 单元测试中的方法
* CollegeService 要注入的服务
* StudentService 要注入的服务 (仅用于展示注入多个服务)
* s为服务起的别名,类型为CollegeService,其对应注入的第一个参数
* t为服务起的别名,类型为StudentService,其对应注入的第二个参数(仅用于展示注入多个服务)
*/
beforeEach(inject([CollegeService, StudentService], (s: CollegeService, t: StudentService) => {
console.log(s);
console.log(t);
// fix = 固定。用工作台创建一个供测试的组件。
// 由于其想要被测试,就必然还需要其它的一些脚手架。
// 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件
fixture = TestBed.createComponent(CollegeIndexComponent);
// 实例化要测试的组件
component = fixture.componentInstance;
// 检测变化
fixture.detectChanges();
}));
使用如下方法改写返回值:
/**
* 每次测试前,均执行一遍
* inject([], () => {}) 单元测试中的方法
* CollegeService 要注入的服务
* StudentService 要注入的服务(仅用于展示注入多个服务)
* collegeService为服务起的别名,类型为CollegeService,其对应注入的第一个参数
* studentService为服务起的别名,类型为StudentService,其对应注入的第二个参数(仅用于展示注入多个服务)
*/
beforeEach(inject([CollegeService, StudentService], (collegeService: CollegeService, studentService: StudentService) => {
// 变量初始化
const collegePage = new Page();
collegePage.content = new Array(
College.instance()
);
// 改写collegeService中的getCollegeByPage方法的返回值。
spyOn(collegeService, 'getCollegeByPage').and.returnValue(of(collegePage));
// fix = 固定。用工作台创建一个供测试的组件。
// 由于其想要被测试,就必然还需要其它的一些脚手架。
// 我们把脚手架与被测试的组件组成的联合体称为:ComponentFixture 被固定的组件
fixture = TestBed.createComponent(CollegeIndexComponent);
// 实例化要测试的组件
component = fixture.componentInstance;
// 检测变化
fixture.detectChanges();
}));
此时,我们再次测试,正常的通过了测试,控制台没有报错,而且:我们在浏览器上看到了我们的组件。最关键的是:我们即没有使用后台,也没有改变CollegeService
中的代码。
参考文献:
深度好文
言简意赅
官方文档