Frontend testing tips

Eseguire test funzionali con Protractor e Angular-cli: le basi per iniziare

Il test funzionale detto anche E2E (end to end) consiste nel verificare la funzionalità completa di un’applicazione. Al contrario di quello unitario che si prefigge il test di funzionalità atomiche, il test E2E ha come obiettivo verificare che tutte funzionalità lavorino correttamente assieme.

Lo strumento principale utilizzato con Angular è Protractor, un framework di test E2E che viene integrato in applicazioni generate con Angular cli.

Questa è una guida introduttiva per un quick setup, che ci porterà alla scrittura del primo test che approfondiremo in articoli successivi.

Continua a leggere

Creare applicazioni desktop in maniera efficace grazie ad Angular ed Electron

Oggi, le applicazioni web che utilizziamo quotidianamente sono state sviluppate utilizzando framework e tecnologie che permettono agli sviluppatori di essere efficienti e veloci.

Al contrario, sviluppare applicazioni desktop rimane piuttosto complicato e macchinoso.

Per sviluppare applicazioni desktop, infatti, non c’è un unico stack tecnologico a cui fare riferimento: sono diffusi decine di linguaggi di programmazione (Java, C++, Python, …) e decine di framework e librerie per la costruzione di interfacce grafiche (Qt, Gtk, Swing, JavaFX, …).

Spesso queste librerie, poi, possono essere anche molto complesse da utilizzare (come faccio un layout fluido?), hanno scarsa documentazione e pochi esempi di codice…. Senza considerare che alcune non sono nemmeno cross-platform!

Perchè sviluppare applicazioni desktop deve essere così complicato? Come sarebbe se potessimo, in qualche modo, utilizzare le tecnologie web per creare app desktop con la stessa facilità?

Per rispondere a queste domande nasce Electron: un framework per la creazione di applicazioni native con tecnologie web come JavaScript, HTML e CSS, sviluppato da GitHub.

Ogni applicazione Electron è compatibile con tutti i sistemi operativi (Mac, Windows e Linux) e si integra perfettamente con i rispettivi menu nativi e sistemi di notifiche.

Già parecchie aziende hanno capito il potenziale di Electron ed hanno iniziato a sfruttare questa tecnologia, le app Electron sono tantissime ed insospettabili: Whatsapp, Skype, Slack, l’editor di testo Atom, Microsoft Visual Studio Code solo per citarne alcune.

In questo articolo vedremo come impostare un’applicazione Electron da zero con l’ultima versione di Angular e come preparare la nostra app per la distribuzione.

Creare una classica applicazione Angular

Per prima cosa creiamo una nuova applicazione Angular utilizzando l’Angular CLI:

npm install -g @angular/cli
ng new angular-electron
cd angular-electron

 

Modificare il file index.html

La pagina index.html generata di default punta il base href a /, questo ci creerà problemi con Electron e va cambiato.
E’ sufficiente aggiungere un punto prima dello slash nel file src/index.html.

<base href="./">

 

Installare Electron

Procediamo ad installare Electron utilizzando npm.

npm install electron --save-dev

 

Creare il file main.js

Ora che abbiamo installato Electron come dipendenza di sviluppo, possiamo creare il file main.js che sarà l’entrypoint vero e proprio della nostra applicazione.

const {app, BrowserWindow} = require('electron');

// E' necessario mantenere un riferimento globale all'oggetto 
// della finestra principale dell'app
// altrimenti la finestra verrà chiusa automaticamente quando l'oggetto
// verrà eliminato dal garbage collector di Javascript
let mainWindow;

function createWindow () {
	
	// Crea la finestra
	mainWindow = new BrowserWindow({width: 800, height: 600});

	// e carica il file index.html presente nella directory 'dist'
	// prodotto dalla build di Angular
	mainWindow.loadFile(`file://${__dirname}/dist/angular-electron/index.html`);

	// Callback da invocare quando la finestra viene chiusa
	mainWindow.on('closed', function () {

		// L'oggetto finestra non ci servè più, dereferenziamolo
		mainWindow = null;

	});

}

// Crea la finestra quando Electron è pronto
app.on('ready', createWindow);

// Callback da invocare quando tutte le finestre sono chiuse
app.on('window-all-closed', function () {

	// In macOS è comune che le app rimangano attive 
	// fino a che l'utente non le chiude esplicitamente con Cmd + Q
	if (process.platform !== 'darwin') {

		app.quit();

	}

});

app.on('activate', function () {

	// In macOS è comune che la finestra di un applicazione venga ricreata
	// quando viene cliccata l'icona nel Dock e non ci sono altre finestre aperte
	if (mainWindow === null) {

		createWindow();

	}

});

 

Creare il comando di build ed esecuzione in modalità produzione

Aggiungiamo al file package.json alcuni comandi per fare la build e lanciare la nostra applicazione.

{
	"name": "angular-electron",
	"version": "0.0.0",
	"main": "main.js", // <-- Aggiungi questa riga
	"scripts": {
		"ng": "ng",
		"start": "ng serve",
		"build": "ng build",
		"test": "ng test",
		"lint": "ng lint",
		"e2e": "ng e2e",
		"electron-build": "ng build --prod && electron ." // <-- Aggiungi questa riga
	},

...

Ora, lanciare la build di Angular ed eseguire la nostra app sarà semplice come digitare

npm run electron-build

Se hai eseguito tutti i passi precedenti correttamente, dopo aver lanciato questo comando, dovresti vedere la finestra della nostra app con all’interno la pagina di test con il logo di Angular.

Ambiente di sviluppo con hot code reload e strumenti per sviluppatori di Chrome

La nostra app è già pronta per essere distribuita (vedi sezione seguente) e non necessita di altro per funzionare.

E’ utile, però, configurarla per lo sviluppo in modo tale che le modifiche al codice Angular vengano caricate immediatamente, senza dover rieffettuare build, in una finestra dell’app che terremo sempre aperta.

Per fare ciò, prevederemo la possibilità di lanciare la nostra app in modalità sviluppo, impostando una variabile d’ambiente ENV con il valore DEV.

La finestra dell’app, se ENV=DEV, punterà all’indirizzo localhost:4200, dove sarà in ascolto il server web di sviluppo di Angular lanciato con il comando ng serve .

Siccome ng serve prima di mettersi in ascolto su localhost:4200 effettua alcune operazioni preliminari, potrebbe non essere subito raggiungibile.
E’ per questo motivo che, prima di far puntare la finestra Electron a localhost:4200, ci assicuriamo che ng serve sia in ascolto, altrimenti attendiamo e ritentiamo successivamente.

Modificare il file main.js

Nel file main.js al posto di

mainWindow.loadFile(`file://${__dirname}/dist/angular-electron/index.html`);

andiamo ad inserire

if (process.env.ENV == 'DEV') {

	// Mi collego all'endpoint ogni retryInterval millisecondi
	// fino a che non ricevo correttamente la pagina
	const request = require('request');
	const devLoadUrl = function(url, retryInterval) {

		request(url, function (error, response, body) {

			if (!error && response.statusCode == 200) {

				// Carica la pagina in una finestra Electron
				win.loadURL(url);
				// Apri gli strumenti per sviluppatori
				win.webContents.openDevTools();

			} else {

				setTimeout(function() { devLoadUrl(url); }, retryInterval);

			}

		});

	};

	devLoadUrl('http://localhost:4200', 1000);

} else {

	// Se la variabile d'ambiente ENV non è settata a DEV
	// procedo a caricare il file index.html
	// prodotto dalla build di Angular
	win.loadURL(`file://${__dirname}/dist/angular-electron/index.html`);

}

 

Creare il comando per lanciare l’app in modalità sviluppo

Aggiungiamo al file package.json il comando per lanciare la nostra applicazione in modalità sviluppo.

{
	"name": "angular-electron",
	"version": "0.0.0",
	"main": "main.js",
	"scripts": {
		"ng": "ng",
		"start": "ng serve",
		"build": "ng build",
		"test": "ng test",
		"lint": "ng lint",
		"e2e": "ng e2e",
		"electron-start": "ENV=DEV electron . | ng serve", // <-- Aggiungi questa riga
		"electron-build": "ng build --prod && electron ."
	},

...

Ora, per lanciare l’applicazione in modalità sviluppo, sarà sufficiente lanciare

npm run electron-start

 

Preparare l’applicazione per la distribuzione

Quando avremo terminato la nostra app, arriverà il momento di condividerla con il mondo.
Grazie ad Electron, la creazione di pacchetti per i vari sistemi operativi sarà davvero semplice.

Installiamo il tool electron-packager con npm

npm install electron-packager -g

Per creare gli eseguibili della nostra app basterà lanciare electron-packager con il parametro platform opportuno.

Il tool electron-packager, oltre al parametro platform, ha molte altre configurazioni possibili, rimando alla documentazione ufficiale per il dettaglio.

Eseguibile macOS:

electron-packager . --platform=darwin

Eseguibile Linux:

electron-packager . --platform=linux

Eseguibile Windows:

electron-packager . --platform=win32

Per poter generare un eseguibile Windows utilizzando electron-packager da sistemi Linux o macOS è necessario avere installato WineHQ.

Il CTO Mastermind, il gruppo Facebook per CTO, CIO e responsabili IT

Essere il CTO, il CIO, l’IT Manager o comunque una persona che prende decisioni tecnologiche per la propria azienda o quelle dei clienti non è affatto facile, soprattutto nell’attuale contesto.

Se hai un ruolo di questo genere, il successo tecnologico del prodotto o piattaforma digitale di cui sei responsabile è in gran parte in mano tua e, per quanto tu possa essere esperto ed avere esperienza, potresti a volte sentirti solo o avere dubbi.

Ogni progetto digitale è innanzitutto un esperimento, che può avere successo o fallire, solo che impatta in modo anche importante sul business dell’azienda stessa.

So perfettamente cosa vuole dire ricoprire ruoli del genere, perché nella mia carriera li ho svolti anche io come CTO/Temporary Manager per alcuni clienti.

E’ in questi casi che potersi confrontare con un gruppo di pari che condividono simili responsabilità può essere insostituibile.

Un gruppo di questo genere viene chiamato “mastermind”: è un luogo, anche solo virtuale, dove si scambiano le esperienze, si danno e ricevono i consigli, si fruisce delle conoscenze degli altri e si mettono a disposizione le proprie.

E’ per questi motivi che ho creato un gruppo specifico su Facebook, il “CTO Mastermind”, e se ti ritrovi nella descrizione puoi farne parte cliccando qui sotto.

Il CTO Mastermind non è come un normale gruppo Facebook.

Continua a leggere

Come gestire file in modo sicuro con Angular e Amazon S3, senza dover scrivere API

Amazon Web Services offre un ottimo e conosciutissimo servizio per lo storage di file chiamato S3 (Simple Storage Service) che in Innoteam apprezziamo particolarmente per la sua semplicità, robustezza e per il suo pricing particolarmente conveniente.

In questo articolo vedremo come interfacciare una Web App con il servizio S3 di Amazon per effettuare upload e download di file in maniera sicura ed efficiente.

Il punto di forza dell’approccio descritto è che upload e download vengono effettuati direttamente verso il bucket S3 senza necessità di dover appoggiare i file su server intermediari.

Supponiamo di dover sviluppare un’app gestionale che ci permetta di caricare e scaricare documenti.

Bucket S3

Per questo tipo di applicazioni non possiamo utilizzare un bucket pubblico e accessibile da chiunque ma, anzi, vogliamo creare un bucket privato a cui possa accedere solo e soltanto il nostro applicativo.

Creiamo quindi un bucket privato dal pannello di amministrazione di Amazon S3 come indicato nella guida ufficiale di AWS.

Pre-Signed URL

Come possiamo gestire upload e download di file verso un bucket privato?
Utilizziamo una feature di Amazon S3 chiamata Pre-Signed URL.

Una Pre-Signed URL è una particolare URL che è stata generata e “firmata” dall’SDK di Amazon e che ci permette di accedere ad un ben definito oggetto di un bucket S3 in lettura oppure in scrittura in base al tipo di Pre-Signed URL.

Chi è in possesso di una di queste URL è quindi in grado fare il download oppure l’upload di uno specifico file, effettuando verso questa URL rispettivamente una chiamata di tipo HTTP GET per il download e HTTP PUT per l’upload.
Una Pre-Signed URL ha una validità temporale prefissata, terminata la quale non è più utilizzabile.

Per poter utilizzare le Pre-Signed URL è necessario modificare le configurazioni CORS del bucket S3 che abbiamo creato, in modo che vengano accettate chiamate HTTP di tipo GET e PUT verso il bucket.

E’ possibile modificare la configurazione CORS del bucket dal pannello di amministrazione di Amazon S3:
Cliccare sul nome del bucket -> Permissions -> CORS configuration

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
  </CORSRule>
  <CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>Authorization</AllowedHeader>
  </CORSRule>
</CORSConfiguration>

Architettura

In questo articolo faremo riferimento ad un’architettura che prevede un’applicazione Angular ed un’API REST JSON con un DB relazionale sottostante.

Modello Dati

Nella nostra applicazione terremo traccia di tutti i file presenti sul bucket S3, creando un record nel nostro DB associato ad ogni file.

Avremo quindi una tabella `attachments` strutturata in questo modo:

Campo Descrizione
filename: string Nome del file (key nel bucket)
uploaded: boolean Flag che indica se l’upload del file sul bucket è terminato con successo

E avremo i seguenti endpoint di una classica API REST:

Endpoint Descrizione
POST /attachments Creazione di un nuovo record di tipo Attachment
PUT /attachments/:id Modifica di un record esistente di tipo Attachment
GET /attachments/:id Recupero di un record esistente di tipo Attachment

Download di un file

Quando il frontend Angular effettua una richiesta GET all’endpoint `/attachments/:id` per il recupero di un record di tipo Attachment, la nostra API, nel caso il record abbia flag `uploaded` impostato a `true`, provvederà a generare una Pre-Signed URL di lettura e ad aggiungerla alle informazioni prelevate da DB.

Generazione di una Pre-Signed URL di lettura in PHP:

// Creating a presigned URL
$cmd = $s3Client->getCommand('GetObject', [ 'Bucket' => 'my-bucket', 'Key' => 'testKey' ]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');

// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();

Body della risposta:

{
  filename: "notes.txt",
  uploaded: true,
  presignedUrl: "https://..."
}

Upload di un file

L’upload di un file verrà invece effettuato in tre passaggi.

1. Creazione di un record Attachment in DB e conseguente recupero della Pre-Signed URL di scrittura

Il frontend Angular effettua una richiesta POST all’endpoint `/attachments` per la creazione di un nuovo record di tipo Attachment.

Body della richiesta:

{ filename: "notes.txt" }

L’API REST crea un record nel DB, imposta automaticamente il flag `uploaded` di questo record a `false` e utilizza l’SDK di AWS per generare una Pre-Signed URL di scrittura.
La Pre-Signed URL viene restituita ad Angular.

Generazione di una Pre-Signed URL di scrittura in PHP:

// Creating a presigned URL
$cmd = $s3Client->getCommand('PutObject', [ 'Bucket' => 'my-bucket', 'Key' => 'testKey' ]);
$request = $s3Client->createPresignedRequest($cmd, '+20 minutes');

// Get the actual presigned-url
$presignedUrl = (string) $request->getUri();

Body della risposta:

{
  id: 44,
  filename: "notes.txt",
  presignedUrl: "https://...",
  uploaded: false
}

2. Upload vero e proprio direttamente verso il bucket di Amazon S3

Angular farà semplicemente una richiesta HTTP PUT verso la Pre-Signed URL ricevuta dalle API con il contenuto del file.

Upload di file utilizzando Pre-Signed URL in service Angular:

@Injectable()
export class AttachmentService {
  ...
  constructor(private http: HttpClient) {}
  ...
  upload(file: File, presignedUrl: string): Promise<any> {
    const options = {
      headers: { 'Content-Type': file.type },
      responseType: 'text'
    };
    return this.http
      .put(presignedUrl, file, options)
      .toPromise();
  }
  ...
}

3. Aggiornamento del record precedentemente creato con flag `uploaded` impostato a `true`

Una volta terminato l’upload verso il bucket S3, Angular chiamerà le API per aggiornare il record Attachment nel DB.

Body della richiesta:

{ uploaded: true }

I 3 step possono essere riassunti dal seguente diagramma:

Conclusioni

Uno dei più grandi vantaggi di questo approccio è sicuramente il fatto di poter effettuare upload e download comunicando direttamente con il bucket S3 senza dover passare dal server che ospita le API, sgravandolo dal lavoro di intermediario verso S3 e non occupandone inutilmente banda e risorse.

Ci teniamo inoltre a sottolineare che per gli esempi è stato utilizzato codice PHP e Angular ma l’approccio descritto è del tutto generale e può essere applicato con qualsiasi combinazione di tecnologie Backend/Frontend.

Testare angular

Eseguire test singoli tramite Karma ed Angular CLI quando i test rallentano lo sviluppo dei progetti più grandi

Le modifiche da applicare ad un progetto standard per avere più controllo sull’esecuzione dei test unitari tramite karma.

Per chi sviluppa frontend, le questioni relative al testing sono spesso “meno lineari” rispetto al classico approccio TDD dello sviluppo backend. Chi ha avuto occasione di lavorare in angular 2 avrà sicuramente potuto apprezzare angular CLI e gli strumenti che mette a disposizione, soprattutto per il testing.

Nello sviluppo quotidiano ho riscontrato che “ng test”, il comando che ci permette di eseguire test unitari tramite karma, non fornisce la possibilità di scegliere quali test eseguire, cosa che al crescere del numero dei test rallenta considerevolmente l’esecuzione, soprattutto in modalità “watch” rendendo così quasi impossibile un approccio TDD.

Infatti, ci capita spesso di lavorare su progetti frontend molto complessi per i nostri clienti e, nonostante questo tipo di problema, al contempo vogliamo mantenere alti i livelli qualitativi, anche con tecniche come appunto il TDD.

Per ovviare al problema ho perciò applicato le seguenti modifiche al mio progetto angular per avere la possibilità di lanciare e tenere in modalità watch solo i test che mi interessano durante lo sviluppo.

Continua a leggere