Angular:集成测试(浅测试)



当应用程序增长时,或者对于任何重构而言,它都能正常工作对我们非常重要,我们开始考虑进行单元或端到端测试。

在公司部门的Angular应用程序工作了数年之后,在重构和创建新功能时遇到了许多问题,测试似乎不是在浪费时间,而是稳定在程序员手中的关键。

接下来,让我们尝试了解如何在Angular上测试基本应用程序,并介绍一些理论。

让我们从应用程序开发人员可以进行的测试类型开始(在我们的示例中,是前端)。



(Martin Fowler和Google Testing Blog)

Angular开箱即用。

让我们使用Angular应用程序示例仔细看一下测试金字塔。

单元测试 -类方法的隔离测试。我们检查该方法的操作,将各种参数组合传递给输入,然后将结果与预期结果进行比较。

例如,我们有一个将十六进制字符串转换为rgb的组件,该信息将显示给用户,并发送到其余服务器。

如果我们开始着眼于单元测试,那么我们可以为组件类中的每个方法编写许多测试。

如果我们有一个方法rgbToHex(hex:string)=> string,那么要测试具有这种签名的方法,我们需要执行以下操作:Expect(rgbToHex('777'))。ToBe('rgb(119,119,119)') 。

看起来不错,但是由于需要覆盖大量功能,我们遇到了一个问题,而且我们变得太懒了以至于无法编写测试。此外,即使为组件中的每种方法编写单元测试,我们也不保证它们共同工作的正确性。

例如,制作并测试了表输出组件之后,我们可以在“计算器”组件中调用它,但是偶然指定了错误的绑定。这样,数据将不会落入表中,并且正常工作的组件将无法正常工作。

图片

整合测试-测试一堆几个组件。从这一阶段开始,我们不仅要测试类方法,还要测试它们与html的绑定,即单击组件内部的元素。浅测试通常在Angular表示法中找到,它实质上是集成测试。

在浅层测试中,我们将在下面详细介绍。

E2E(端到端)测试-一种完全测试应用程序以解决单元测试问题的方法。

通过这种方法,我们为完全渲染的应用程序编写测试脚本,即所有组件和服务都组装在一起,我们重现用户操作。
这很酷,但是我们可能会遇到动态更改存根(通常是node.js上的静态json服务器)的问题,并且要播放不同的情况,我们可能需要来自仿真服务器的不同数据。

例如:我们有一个包含用户列表的页面,此面板的组成部分之一是分页。对于不同数量的用户,它的行为应有所不同。但是,如果通过json设置了仿真服务器上的数据,那么用户数将始终为1,并且我们将无法检查所有页面数不同的情况。

结果,我们可以得出结论,如果我们要灵活地更改存根并且不按方法而是按接口的逻辑单元进行测试,则需要在e2e和单元测试之间使用某些东西。在这里可以进行浅层测试(集成测试)。

词汇表:

  • 模拟是在各种用例中模拟响应的对象。
  • 存根是没有逻辑的最愚蠢的存根(您可以阅读Martin)。
  • 业力(Karma)是内置在angular中的测试运行程序(通常建议您使用笑话代替,但今天不使用它)。
  • Jasmine是一个以规范形式描述测试的框架(请参阅下文)。
  • spec-测试文件扩展名(BDD样式的规范描述)。
  • 它是Jasmine中测试方法的名称。
  • xit是将无法运行的方法的名称。
  • 适合-如果规格中有相同名称的方法,则只会启动它们。

对Angular以及其他框架进行浅层测试是一种单元测试方法,当呈现大小足够大的组件时,它可以作为具有自己功能的独立ui单元存在。

例如,我们有一个用于转换十六进制-> rgb的组件。我们只能渲染该组件,为不同情况生成存根,从最终用户的角度执行该组件的可能用例,并检查该组件的操作。

让我们尝试找出一个示例(存储库)。

根据PageObject准备用于访问组件的组件的类,并将帮助器添加到项目的根目录。

助手-将帮助您搜索已选择进行渲染的组件中的元素。这样,如果我们使用Angular Material,我们可以使生活更轻松:然后,select类型的元素将在单独的块中创建一个带有option的列表,搜索这些元素可以创建样板,并且以帮助器形式的包装器可以提供帮助。

export class PageObjectBase {

  constructor(private root: HTMLDivElement) { }
  //    input  
  _inputValue(cssSelector: string, value: string) { 
    if (value) {
      this.root.querySelector<HTMLInputElement>(cssSelector).value = value;
      this.root.querySelector<HTMLInputElement>(cssSelector).dispatchEvent(new Event('input'));
    }
    else {
      return this.root.querySelector<HTMLInputElement>(cssSelector).value
    }
  }
  //          
  _buttonClick(cssSelector: string) {
    this.root.querySelector<HTMLButtonElement>(cssSelector).dispatchEvent(new Event('click'));
  }

}

PageObject是一种流行的自动化测试模板。用于简化对笔试的支持。如果更改界面,则不必重写测试,只需更改元素选择器即可。


export class ConverterFromHexPageObject extends PageObjectBase {

  constructor(root: HTMLDivElement) {
    super(root)
  }

  hex(text?: string) {
    return this._inputValue('.input-hex', text);
  }

  rgb(text?: string) {
    return this._inputValue('.input-rgb', text);
  }

  clear() {
    this._buttonClick('.btn-clear')
  }

  calc() {
    this._buttonClick('.btn-calc')
  }

}

和测试本身:


// ,        api   
const urlToSave = 'http://localhost:4200/save-hex';


//  -   
describe('ConverterFromHexComponent', () => {
  let component: ConverterFromHexComponent;
  let fixture: ComponentFixture<ConverterFromHexComponent>;
  let page: ConverterFromHexPageObject;
  let httpTestingController: HttpTestingController;


  //      ,   
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [ConverterModule, HttpClientTestingModule]
    })
      .compileComponents();
    httpTestingController = TestBed.get(HttpTestingController);
  }));


  //  ,   
  beforeEach(() => {
    fixture = TestBed.createComponent(ConverterFromHexComponent);
    component = fixture.componentInstance;
    page = new ConverterFromHexPageObject(fixture.nativeElement);
    fixture.detectChanges();
  });


  //    ,     http 
  afterEach(() => {
    httpTestingController.verify();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should clear', async () => {
    page.hex('112233'); //  input  
    expect(page.hex()).toBe('112233'); // ,    
    await page.clear(); //      
    fixture.detectChanges(); //    
    expect(page.hex()).toBe(''); // ,   clear 
  });

  it('should convert', async () => {
    page.hex('123123');
    expect(page.rgb()).toBe('');
    page.calc();
    const req = httpTestingController.expectOne(urlToSave);
    expect(req.request.method).toEqual('POST');
    expect(req.request.body.hex).toEqual('123123');
    req.flush({});
    await fixture.detectChanges();
    expect(page.rgb()).toBe('rgb(18, 49, 35)');
  });

  it('should convert three-digit hex', async () => {
    page.hex('567');
    expect(page.rgb()).toBe('');
    page.calc();
    const req = httpTestingController.expectOne(urlToSave);
    expect(req.request.method).toEqual('POST');
    req.flush({});
    await fixture.detectChanges();
    expect(page.rgb()).toBe('rgb(85, 102, 119)');
  });

  it('rgb should be empty when entered incorrectly hex', async () => {
    page.hex('qw123we');
    page.calc();
    const req = httpTestingController.expectNone(urlToSave);
    await fixture.detectChanges();
    expect(page.rgb()).toBe('');
  });

});

一切似乎都很简单,但是这里有一些有关Angular Shallow测试的有趣注释:

  • 始终检查是否有未保留的http请求,这将帮助您准确了解是否有对我们功能的任何不必要的请求。
  • PageObject , .
  • json , .
  • , .
  • .
  • .
  • , session, local, cookie ( ).
  • fake.js.
  • .




Source: https://habr.com/ru/post/undefined/


All Articles