Novembre 2018
Volume 33 Numero 11
The Working Programmer - Come usare MEAN: Test angolarmente
Dal Ted Neward | Novembre 2018
Bentornato, MEANers.
Con un po' di fortuna, il dibattito"" attorno a unit test del codice non richiede più la discussione, gli sviluppatori, dovrà cercare modi per automatizzare il codice di test creato, senza dubbio. Gli argomenti possono restare per quanto riguarda se tali test devono provenire prima o dopo il codice in questione, ad esempio, ma è piuttosto chiaro che i test non sono una parte facoltativa di un progetto software moderno più. Che quindi, ovviamente, è opportuno quindi chiedersi: Come si testare un'applicazione Angular? Trattato brevemente i file di test, tornare nel numero di maggio 2017 quando ho iniziato a compilare alcuni componenti di Angular, ma era difficilmente grandioso (msdn.com/magazine/mt808505). A questo punto è possibile richiedere un'analisi approfondita.
Torna agli albori di
È possibile fare un passo fino all'inizio dell'applicazione. Quando "ng nuovo" viene eseguita per lo scaffolding delle fasi iniziali dell'applicazione, crea tutti gli strumenti necessari e hook e i file per assicurare che l'applicazione può essere testata. In effetti, subito dopo "ng nuovo", senza necessità di eseguire anche una singola modifica di qualsiasi file, è possibile eseguire "test a ng" per eseguire il test runner, che a sua volta esegue il codice scritto con il codice del framework di test sottoposto a scaffolding.
Quando eseguita, viene attivata "npm test" backup Karma, il test runner, che quindi viene attivata un'istanza del browser ed esegue i test all'interno di quell'istanza. Karma rimane in esecuzione e mantenere una connessione WebSocket aperta nel browser in modo che tutte le modifiche ai file di origine possono essere testate immediatamente, rimuovere alcuni degli oneri di test e all'accelerazione ci si avvicina a un ciclo di codice e test no-wait. Lo scaffolding di progetto fornisce tre test, codificato nel file "app.component.spec.ts", che consente di testare il codice "TS" corrispondente. Come regola generale, ogni componente dell'applicazione Angular deve corrispondere un ". spec.ts" file deve contenere tutti i test. Se ogni componente viene creata tramite la CLI di Angular, in genere genererà un file di test corrispondente, con alcune eccezioni (ad esempio "class") che richiede un "-spec" argomento al comando "generazione" per creare il file spec (abbreviazione di "specifica").
Come in effetti, è necessario generare una classe rapida, relatore (naturalmente) e quindi scrivere alcuni test per tale. Come di consueto, creare il componente con il comando di Angular ("ng Genera classe relatore - spec"), quindi modificare il file "speaker.ts" per contenere una classe semplice con cinque proprietà pubbliche di costruttore:
export class Speaker {
constructor(public id: number,
public firstName: string,
public lastName: string,
public age: number,
public bio?: string,
)
{ }
}
Il test corrispondente devono esercitare le varie proprietà per assicurarsi che funzionino come previsto:
import { Speaker } from './speaker';
describe('Speaker', () => {
it('should create an instance', () => {
expect(new Speaker(1, "Ted", "Neward", 47, "Ted is a big geek")).toBeTruthy();
});
it('should keep data handy', () => {
var s = new Speaker(1, "Ted", "Neward", 47, "Ted is a big geek");
expect(s.firstName).toBe("Ted");
expect(s.firstName).toEqual("Neward");
expect(s.age).toBeGreaterThan(40);
})
});
Come la classe ottiene più complessi e più complessi test devono essere aggiunti, nonché. Gran parte dell'efficacia dei test si trova nel framework "previsti", che fornisce un numero elevato di "toBe" metodi da testare vari scenari. Qui è visualizzare diverse versioni di questa operazione, tra cui "toEqual", che esegue un test di uguaglianza e "toBeGreaterThan", con cui vengono eseguite esattamente come suggerisce il nome.
Ma gli utenti che seguono lungo a casa, tuttavia, verrà visualizzato qualcosa non va, ovvero una volta salvato il file delle specifiche, l'istanza del browser aperta diventa un fastidioso sfumatura di rosso, sottolineare che "Ted" non uguale "Neward". Naturalmente, è presente un bug nella seconda "previsti" istruzione, vuoi per confrontare "firstName" deve essere "lastName". Infatti buono, mentre il codice di test è la verifica di bug nel codice, è anche il caso che in alcuni casi il bug è nel test e ottenere commenti non appena si scrittura di test consente di evitare i bug di test.
Altri test
Naturalmente, un'app Angular è costituita da più di semplici classi; può anche includere servizi, che sono in genere molto semplici eseguire il test, perché tendono a fornire il comportamento e lo stato molto piccolo. Nel caso dei servizi che forniscono un comportamento sui propri, ad esempio i dati di formattazione o alcune semplici trasformazione, il test è semplice e simile ai test di una classe come relatore. Ma spesso anche servizi devono interagire con il mondo attorno a esse in qualche modo (ad esempio, creazione di richieste HTTP, come il SpeakerService ha alcune colonne indietro), ovvero test diventa più complicato se non si vuole deve includere le dipendenze. Effettivamente l'invio delle richieste tramite HTTP, ad esempio, renderebbe test soggetta a problemi tipici di rete le interruzioni di comunicazione o di server, che potrebbe produrre alcuni errori falsi negativi e rendere più difficile stabilire il test. Che sarebbe un grosso problema.
È per queste situazioni che Angular Usa questo tipo di inserimento delle dipendenze.
Ad esempio, iniziamo con la versione di SpeakerService che non tutte le richieste HTTP, come illustrato nella figura 1.
Figura 1 la classe SpeakerService che non hai effettuato tutte le richieste HTTP
@Injectable()
export class SpeakerService {
private static speakersList : Speaker[] = [
new Speaker(1, "Ted", "Neward", 47,
"Ted is a big geek living in Redmond, WA"),
new Speaker(2, "Brian", "Randell", 47,
"Brian is a high-profile speaker and developer of 20-plus years.
He lives in Southern California."),
new Speaker(3, "Rachel", "Appel", 39,
"Rachel is known for shenanigans the world over. She works for Microsoft."),
new Speaker(4, "Deborah", "Kurata", 39,
"Deborah is a Microsoft MVP and Google Developer Expert in Angular,
and works for the Google Angular team."),
new Speaker(5, "Beth", "Massi", 39,
"Beth single-handedly rescued FoxPro from utter obscurity
and currently works for Microsoft on the .NET Foundation.")
]
public getSpeakers() : Speaker[] { return SpeakerService.speakersList; }
public getSpeakerById(id : number) : Speaker {
return SpeakerService.speakersList.find( (s) => s.id == id);
}
}
Questa versione è piuttosto semplice per eseguire il test, perché è sincrono e non richiede all'esterno di dipendenze, come illustrato nella figura 2.
Figura 2 test della classe SpeakerService
describe('SpeakerService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SpeakerService]
});
});
it('should be able to inject the SpeakerService', inject([SpeakerService],
(service: SpeakerService) => {
expect(service).toBeTruthy();
}));
it('should be able to get a list of Speakers', inject([SpeakerService],
(service: SpeakerService) => {
expect(service.getSpeakers()).toBeDefined();
expect(service.getSpeakers().length).toBeGreaterThan(0);
}));
});
Si noti che le chiamate "inserire" in ogni test? Si tratta in sostanza come Angular gestisce l'inserimento di dipendenze dell'ambiente di testing. è ciò che consente di specificare qualsiasi tipo di servizio compatibile back-end (reali o simulati) nell'ambiente.
In genere, il servizio esegue un po' più di quali sono le novità di SpeakerService semplice ed è una seccatura di dover impostare come commento e/o sostituita da quella "real" con un'istanza fittizio che non esegue alcuna operazione, in modo che questo è l'uso di un servizio "fittizio" in cui funziona meglio. Angular dispone di un costrutto utile denominato "Spy" che può inserirsi in un normale servizio ed eseguire l'override di alcuni metodi per fornire un risultato simulato:
it('should be able to get a list of Speakers',
inject([SpeakerService], (service: SpeakerService) => {
spy = spyOn(service, 'getSpeakers').and.returnValues([]);
var speakers: Speaker[] = service.getSpeakers();
expect(speakers).toBeDefined();
expect(speakers.length).toBe(0);
}));
Utilizzando le Spy, è possibile "eseguire l'override" del metodo richiamato nel test per fornire i valori che si desidera tornare.
Test dei componenti
Gran parte della compilazione di un'applicazione Angular, tuttavia, è la creazione di componenti che possono essere visualizzati nella pagina ed è importante essere in grado di testare tali valori, troppo. Per informazioni sui ancora migliori di testare un componente visual, iniziamo con un semplice componente del tipo di opzione di attivazione/disattivazione di attivazione/disattivazione:
@Component({
selector: 'app-toggle',
template: `<button (click)="clicked()">
I am {{isOn ? "on" : "off" }} -- Click me!
</button>`,
styleUrls: ['./toggle.component.css']
})
export class ToggleComponent {
public isOn = false;
public clicked() { this.isOn = !this.isOn; }
}
Per testarla, è possibile letteralmente ignorare interamente il modello DOM e just esaminare lo stato del componente quando vengono richiamate varie azioni:
it('should toggle off to on and off again', () => {
const comp = new ToggleComponent();
expect(comp.isOn).toBeFalsy();
comp.clicked();
expect(comp.isOn).toBeTruthy();
comp.clicked();
expect(comp.isOn).toBeFalsy();
});
Questo non verifica il DOM affatto, tuttavia, che è stato possibile nascondere alcuni bug critico. Il fatto che la proprietà "isOn" è stata modificata non significa che il modello ha eseguito il rendering della proprietà in modo corretto, ad esempio. Per verificarlo, è possibile entrare in possesso dell'istanza del componente creato da fixture ed esaminare sottoposto a rendering per il DOM, come illustrato di seguito:
it ('should render HTML correctly when clicked', () => {
expect(fixture.componentInstance.isOn).toBeFalsy();
const b = fixture.nativeElement.querySelector("button");
expect(b.textContent.trim()).toEqual("Click me! I am OFF");
fixture.componentInstance.clicked();
fixture.detectChanges();
expect(fixture.componentInstance.isOn).toBeTruthy();
const b2 = fixture.nativeElement.querySelector("button");
expect(b2.textContent.trim()).toEqual("Click me! I am ON");
});
Il "nativeElement" qui Ottiene il nodo DOM per il componente e che consente di eseguire una query di stile di jQuery per trovare il nodo DOM pertinente all'interno di "querySelector", ovvero in questo caso, il pulsante di attivazione/disattivazione crea. Da qui, scarica il contenuto di testo (e trim, perché la riga di codice precedente demo suddivide in due posizioni che sarebbero difficile da replicare nel test) e lo confronterà con i risultati previsti. Tuttavia, si noti che dopo "Seleziono" del componente, esiste una chiamata a "detectChanges"; Questo avviene perché Angular deve essere specificata la destinazione delle modifiche del processo, il relativo DOM che potrebbe aver causato il gestore dell'evento, ad esempio aggiornare le stringhe interpolate nel modello. In caso contrario, il test avrà esito negativo nonostante il componente utilizza funzioneranno nel browser. (Commette questo esatto durante la scrittura in effetti, l'articolo in modo da sembrare strano se si dimentica di rilevare le modifiche.) Si noti che se il componente esegue qualsiasi inizializzazione significativo all'interno di relativo metodo onInit, il test sarà necessario detectChanges prima di eseguire qualsiasi lavoro significativo, per lo stesso motivo.
Conclusioni
Tenere presente, l'altro, che tutto questo codice di test non è conforme il lato client dell'applicazione, non il lato server. Tenere presente tutto il codice di Express che ho scritto per fornire API per archiviare i dati nel database e così via? Tutto ciò è in pratica "questo framework esterno" e pertanto deve essere gestita ed eseguire separatamente. È possibile usare alcuni degli strumenti "Compila" discussi per l'esecuzione sia lato server e lato client di test come parte di una più grande ciclo di test e verificare pertanto che viene generati come parte di tutte le modifiche a un client o server. Angular supporta anche il test "E2E" (acronimo di "end-to-end"), che non rientrano nell'ambito di ciò che è in corso citato, ma è progettato per supportare esattamente questa situazione.
Buona codifica!
Ted Newardè un consulente polytechnology basato su Seattle, relatore e mentore, attualmente lavora come direttore dell'ingegneria e Developer Relations in Smartsheet.com. Ha scritto una considerevole quantità di articoli, creati e una dozzina libri ed legge tutto il mondo. Contattarlo all'indirizzo ted@tedneward.com o il suo blog blogs.tedneward.com.
Grazie al seguente esperto tecnico per la revisione dell'articolo: Garvice Eakins (Smartsheet.com)