在应用程序开发中,常会利用人工手动去测试系统的正确,不过当遇到较复杂的系统时,手动测试相对耗时且容易遗漏。除此之外,也可以撰写单元测试 (Unit Testing) 程序并利用测试框架运行,来验证系统的正确性;此通常针对的是方法或是类别,透过封装隔离来缩小测试的范围,以在持续开发与维护中确保程序品质。这一篇将了解在 Angular 应用程序如何针对元件进行单元测试 (Unit Test)。
Angular 官方建议使用 Jasmine 来撰写测试程序,将测试程序撰写在 *.spec.ts 档案中,透过下列几个方法来定义各种的测试案例。
describe('测试分组描述', () => {
beforeAll(() => { });
afterAll(() => { });
beforeEach(() => { });
it('测试案例描述', () => { });
afterEach(() => { });
});
每一个测试案例皆会写在 it()
方法之中,且每一个案例皆不互相影响;当测试案例愈来愈多时,可以利用 describe()
方法来分组,此方法可以有一至多个 describe()
或 it()
方法。在执行 describe
下的所有测试案例之前,会触发一次 beforeAll()
方法,此方法用於初始化所有案例所需的物件;当所有测试案例完成後,则会触发一次 afterAll()
方法来注销全域物件。而在每一个案例在执行的前後则分别会触发 beforeEach()
与 afterEach()
方法。另外,可以在 describe()
或 it()
前加上 f,来只执行特定的测试案例;或是加上 x 来排除不执行该测试案例。
在执行测试时,Angular 会透过 TestBed 动态建立用来模拟 @NgModule
的测试模组,因此会在此针对所测试的元件对象汇入所需要的模组或元件。
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TaskComponent]
})
.compileComponents();
}));
大致了解 Jasmine 的方法後,首先在 task.component.spec.ts 中新增验证主旨显示的测试案例。
describe('TaskComponent', () => {
it('当指定主旨为 "页面需要显示待办事项主旨", 页面应显示为 "页面需要显示待办事项..."', () => {
});
});
在撰写单元测试程序时会采用 3A 原则来加强测试程序的可读性;此原则包含了:
describe('TaskComponent', () => {
it('当指定主旨为 "页面需要显示待办事项主旨", 页面应显示为 "页面需要显示待办事项..."', () => {
// arrange
const expected = '页面需要显示待办事项...';
// act
// assert
});
});
另外,在对元件进行测试时,Angular 会利用 TestBed 建立元件的实体,并提供 ComponentFixture 物件来对元件与其 DOM 进行互动。因此此测试案例会针对 fixture
物件的元件实体变数 component
设定主旨属性,并呼叫 detectChanges()
方法让 TestBed 执行资料系结。
describe('TaskComponent', () => {
it('当指定主旨为 "页面需要显示待办事项主旨", 页面应显示为 "页面需要显示待办事项..."', () => {
// arrange
const expected = '页面需要显示待办事项...';
// act
component.subject = '页面需要显示待办事项主旨';
fixture.detectChanges();
// assert
});
});
最後需要验证页面显示的主旨是否符合预期,可以查询 ComponentFixture 内的 DebugElement 树来取得到 DebugElement 节点,进一步使用 nativeElement
来取得执行平台的原生物件,而指定的方式可以利用 css 选择器或是直接指定元件类型。然後利用 Jasmine 提供的方法进行验证後,就可以执行 ng test
来运行测试程序了。
describe('TaskComponent', () => {
it('当指定主旨为 "页面需要显示待办事项主旨", 页面应显示为 "页面需要显示待办事项..."', () => {
// arrange
const expected = '页面需要显示待办事项...';
// act
component.subject = '页面需要显示待办事项主旨';
fixture.detectChanges();
// assert
const debugElement = fixture.debugElement.query(By.css('div.content span'));
expect(debugElement.nativeElement.textContent).toContain(expected);
});
});
当元件有注入服务时,所撰写单元测试一般不会直接使用实际的服务,而是建立一个 Spy 服务物件来模拟,如此才可以控制元件的输入与输出的状态。不过,因为在 TaskList 元件中注入了 Router 服务元件,并且有使用到 Tsak 元件,所以在 task-list.component.spec.ts 中需要先汇入所需要的模组与元件。
describe('TaskListComponent', () => {
let component: TaskListComponent;
let fixture: ComponentFixture<TaskListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientModule, FormsModule],
declarations: [TaskListComponent, TaskComponent, TaskStateColorDirective],
}).compileComponents();
}));
});
接着,利用 Jasmine 提供的 createSpyObj 来建立一个待办事项服务的 Spy 物件,且定义此服务元件 getData()
所回传的值,并注入在 TestBed 内以取代实际的待办事项服务。
describe('TaskListComponent', () => {
let component: TaskListComponent;
let fixture: ComponentFixture<TaskListComponent>;
let taskService: jasmine.SpyObj<TaskRemoteService>;
beforeEach(async(() => {
taskService = jasmine.createSpyObj(['getData']);
taskService.getData.and.returnValue(
of([
{
id: 1,
subject: '页面需要显示待办事项主旨',
state: 0,
level: 'XS',
tags: ['FEATURE', 'ISSUE', 'enhancement', 'discussion'],
},
])
);
TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientModule, FormsModule],
declarations: [TaskListComponent, TaskComponent, TaskStateColorDirective],
providers: [{ provide: TaskRemoteService, useValue: taskService }],
}).compileComponents();
}));
});
最後建立一个测试案例,直接利用 TaskComponent 来查询页面上所有 DebugElement 节点,并验证其个数是否符合服务所设定的个数。
fit('应显示一笔待办事项', () => {
const debugElements = fixture.debugElement.queryAll(By.directive(TaskComponent));
expect(debugElements.length).toBe(1);
});
利用单元测试可以将测试案例记录下,在持续的开发与维护中重覆的执行,减少人工测试的时间成本,更进一步的搭配 CI 来避免把有问题的程序更新至正式环境中。
<<: RxJS 错误处理 Operators (1) - catchError / finalize / retry / retryWhen
本文同步更新於blog Singleton Pattern 确保一个类只有一个实例,并提供一个全局...
昨天我们成功安装 Highcharts-Vue 并绘制出一个基本的图表,不过既然都已经使用 Vue ...
套件 在我们写程序时,一定会用很多来支援,让我们更轻松, 尤其是WPF的部分,为了漂亮肯定用了很多套...
开始之前,我相信你已经有碰过flask的经验,或是至少知道藉由 pip install Flask ...
请教各位有没有碰过以下问题 主件料号多阶BOM如下 1.主件料号 2.半成品A(F) 2.半成品B(...