Angulaire: test d'intégration (test peu profond)



Lorsque l'application grandit, ou qu'il est très important pour nous qu'elle fonctionne correctement avec tout refactoring, nous commençons à penser aux tests unitaires ou e2e.

Pendant plusieurs années de travail avec Angular - applications dans le segment des entreprises, ayant rencontré de nombreux problèmes lors de la refactorisation et de la création de nouvelles fonctionnalités, les tests ne semblent pas être une perte de temps, mais la clé de la stabilité entre les mains des programmeurs.

Ensuite, essayons de comprendre comment tester l'application de base sur Angular et abordons un peu la théorie.

Commençons par les types de tests que les développeurs d'applications peuvent faire (dans notre cas, frontend).



(Martin Fowler et Google Testing Blog)

Angular propose de telles méthodes prêtes à l'emploi.

Examinons de plus près la pyramide de test en utilisant l'exemple des applications angulaires.

Tests unitaires - tests isolés des méthodes de classe. Nous vérifions le fonctionnement de la méthode, en passant diverses combinaisons de paramètres à l'entrée, et comparons le résultat avec celui attendu.

Par exemple, nous avons un composant dans lequel les chaînes hexadécimales sont converties en rgb, ces informations sont affichées pour l'utilisateur et également envoyées au serveur de repos.

Si nous commençons à chercher des tests unitaires, nous pouvons alors écrire de nombreux tests pour chaque méthode de la classe de composants.

Si nous avons une méthode rgbToHex (hex: string) => string, alors pour tester une méthode avec une telle signature, nous devons faire ce qui suit: expect (rgbToHex ('777')). ToBe ('rgb (119, 119, 119)')) .

Cela semble génial, mais nous avons un problème en raison du grand nombre de fonctions qui doivent être couvertes, et nous devenons trop paresseux pour écrire des tests. De plus, même en écrivant des tests unitaires pour chaque méthode d'un composant, nous ne garantissons pas l'exactitude de leur travail commun.

Par exemple, après avoir créé et testé le composant de sortie de table, nous pouvons l'appeler dans le composant «calculatrice», mais spécifier accidentellement la mauvaise liaison. Ensuite, les données ne tomberont pas dans les tableaux et les composants pleinement fonctionnels ne fonctionneront pas correctement.

image

Test d'intégration- tester un tas de plusieurs composants. A partir de cette étape, nous commençons à tester non seulement les méthodes de classe, mais aussi leur liaison au html, c'est-à-dire cliquez sur les éléments à l'intérieur du composant. Les tests superficiels se trouvent souvent dans la notation angulaire , qui est essentiellement des tests d'intégration.

Lors des tests peu profonds, nous nous attarderons plus en détail ci-dessous.

Tests E2E (de bout en bout) - un moyen de tester complètement l'application pour résoudre les problèmes des tests unitaires.

Avec cette approche, nous écrivons des scripts de test pour une application entièrement rendue, c'est-à-dire tous les composants et services sont assemblés ensemble et nous reproduisons les actions de l'utilisateur.
C'est très cool, mais nous pouvons avoir le problème de changer dynamiquement les stubs (généralement un serveur json statique sur node.js), et pour jouer à différentes situations, nous pouvons avoir besoin de données différentes du serveur émulé.

Par exemple: nous avons une page avec une liste d'utilisateurs, l'un des composants de ce panneau est la pagination. Il doit se comporter différemment avec différents nombres d'utilisateurs. Mais si les données sur le serveur émulé sont définies via json, le nombre d'utilisateurs sera toujours un et nous ne pourrons pas vérifier tous les cas avec un nombre de pages différent.

Par conséquent, nous pouvons conclure que si nous voulons changer les stubs de manière flexible et tester non pas par des méthodes, mais par des unités logiques de l'interface, nous devons utiliser quelque chose entre e2e et les tests unitaires. C'est là que les tests superficiels (tests d'intégration) viennent à la rescousse.

Glossaire:

  • Les maquettes sont des objets pour simuler des réponses dans divers cas d'utilisation.
  • Les stubs sont les stubs les plus stupides sans logique (vous pouvez lire Martin).
  • Karma est un lanceur de test intégré à angulaire (souvent conseillé d'utiliser la plaisanterie à la place, mais pas à ce sujet aujourd'hui).
  • Jasmine est un cadre pour décrire les tests sous forme de spécifications (voir ci-dessous).
  • spec - extension de fichier de test (description des spécifications dans le style BDD).
  • c'est le nom des méthodes de tests dans Jasmine.
  • xit est le nom des méthodes qui ne s'exécuteront pas.
  • fit - s'il existe des méthodes portant le même nom dans les spécifications, seules elles seront lancées.

Les tests superficiels pour Angular, ainsi que pour d'autres frameworks, sont une approche de test unitaire, lorsqu'un composant de taille suffisamment grande est rendu pour qu'il puisse exister en tant qu'unité distincte d'interface utilisateur avec ses propres fonctionnalités.

Par exemple, nous avons un composant pour convertir hex -> rgb. Nous pouvons uniquement rendre ce composant, générer des stubs pour différentes situations, exécuter des cas d'utilisation possibles pour ce composant du point de vue des utilisateurs finaux et vérifier le fonctionnement du composant.

Essayons de trouver un exemple (référentiel).

Préparez la classe pour accéder aux composants du composant conformément à PageObject et ajoutez un assistant à la racine du projet.

Aide - aidera à rechercher des éléments dans les composants qui ont été sélectionnés pour le rendu. Nous pouvons donc rendre la vie plus facile si nous utilisons un matériau angulaire: les éléments de type select créeront alors une liste avec option dans un bloc séparé, et la recherche de ces éléments peut conduire à des passe-partout, et un wrapper sous la forme d'une aide peut vous aider.

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 est un modèle de test automatisé populaire. Utilisé pour simplifier la prise en charge des tests écrits. Si nous changeons l'interface utilisateur, nous n'avons pas à réécrire les tests, il suffit de changer les sélecteurs d'éléments.


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')
  }

}

et les tests eux-mêmes:


// ,        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('');
  });

});

Tout semble simple, mais voici quelques notes intéressantes pour les tests Angular Shallow:

  • Vérifiez toujours qu'il n'y a pas de requêtes http non réservées, cela vous aidera à comprendre exactement s'il y a des requêtes inutiles pour notre fonctionnalité.
  • PageObject , .
  • json , .
  • , .
  • .
  • .
  • , session, local, cookie ( ).
  • fake.js.
  • .




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


All Articles