Archivi tag: test

Xamarin.UITest: il nome inganna, ci si può testare (quasi) tutto

Al recente Xamarin Day di Roma l’MVP Massimo Bonanni ci ha parlato dello Xamarin UITest, framework basato su Calabash per la scrittura e l’automazione di test di accettazione nel mondo dello sviluppo mobile.
Niente di nuovo sotto il sole, se non che in precedenza mi ero fatto ingannare troppo facilmente dall’apparentemente elevato costo del servizio in cloud e non avevo approfondito.

Detto che su GitHub ci sono molti esempi di utilizzo, e che io stesso in un progetto al lavoro ho preso “fortemente spunto” dall’app sviluppata per Xamarin Evolve 2016, riporto qui un mio piccolo esperimento fatto con un’applicazione sviluppata in Titanium. Ebbene sì, il framework è stato sviluppato dal team di Xamarin, ma può essere usato per automatizzare il processo di test su ogni applicazione mobile per Android e iOS. Per Windows non ancora, e la cosa può far sorridere considerato chi è il nuovo padrone di Xamarin, ma magari un giorno colmeranno la lacuna.
Per intenderci, funziona con tutte le tecnologie, ma ha senso usarlo soltanto con quelle che renderizzano componenti nativi: Android, iOS, Xamarin, Titanium, React Native, Native Script, … con i framework ibridi basati su Cordova/Phonegap questo approccio è sbagliato perché tutta l’applicazione gira all’interno di una webview.

Esperimento con Appcelerator Titanium

L'albero degli elementi in un'applicazione Titanium, visualizzato tramite repl

L’albero degli elementi in un’applicazione Titanium visualizzato tramite repl

Quello sopra è l’albero degli elementi in una delle maschere dell’applicazione, così come Titanium l’ha generato a runtime. Gli elementi ci sono tutti, ma scriverci sopra dei test non è sempre banale perché non sempre ci sono degli id a cui poter fare riferimento e a volte bisogna ripiegare sul contenuto testuale. Mmmmm.

Quando si crea un progetto di tipo Xamarin.UITest Xamarin Studio genera in automatico una coppia di file, l’AppInitializer.cs e il Tests.cs. Naturalmente saremo poi liberi di modificare la struttura del nostro progetto come preferiamo (vedi l’app di Xamarin Evolve), ma in quei due file ci sono comunque tutti gli elementi di cui abbiamo bisogno per partire.

public class AppInitializer
{
    public static IApp StartApp(Platform platform)
    {
        if (platform == Platform.Android)
        {
            return ConfigureApp
                .Android
                .ApkFile ("/Users/m.piccotti/src/ti_eatevolution/build/android/bin/Eat Evolution.apk")
                .EnableLocalScreenshots()
                .StartApp();
        }

        return ConfigureApp
            .iOS
            //.AppBundle ("../../../iOS/bin/iPhoneSimulator/Debug/XamarinForms.iOS.app")
            .StartApp();
    }
}
[TestFixture(Platform.Android)]
//[TestFixture(Platform.iOS)]
public class Tests
{
    IApp app;
    Platform platform;

    public Tests(Platform platform)
    {
        this.platform = platform;
    }

    [SetUp]
    public void BeforeEachTest()
    {
        app = AppInitializer.StartApp(platform);
    }

    [Test]
    public void AppWorks()
    {
        //app.Repl();

        Func<AppQuery, AppQuery> title = (arg) => arg.Marked("Eat Evolution");
        Func<AppQuery, AppQuery> barFofoGarden = (arg) => arg.Marked("Bar Fofo Garden");
        Func<AppQuery, AppQuery> filtersButton = (arg) => arg.Class("ActionMenuItemView").Marked("Filters");

        // 1 - info page
        app.WaitForElement(title);
        app.Screenshot("1 - info page");
        Assert.IsNotNull(app.Query("JOIN US OR RECOMMEND").First().Text);

        app.TapCoordinates(100, 300);

        // 2 - list
        app.WaitForElement(barFofoGarden);
        app.Screenshot("2 - list");

        app.Tap(barFofoGarden);

        // 3 - Fofo Garden detail
        app.WaitForElement(barFofoGarden);
        app.Screenshot("3 - Fofo Garden detail");
        Assert.IsNotNull(app.Query("Bar Fofo Garden").First().Text);

        app.TapCoordinates(100, 100);
        app.WaitForElement(barFofoGarden);
        app.Tap(filtersButton);

        // 4 - filters screen
        app.WaitForElement(x => x.Marked("Confirm"));
        app.Screenshot("4 - filters screen");
        Assert.IsNotNull(app.Query("Confirm").First().Text);
        Assert.IsNotNull(app.Query("Cancel").First().Text);

        app.Back();
    }
}

Ora… chi è un po’ pratico di test automatici di unità potrebbe rimanere inorridito al vedere quell’unico metodo AppWorks; certo sarebbe bello se ogni asserzione potesse essere verificata isolatamente, il problema è che questi test sono più lenti di quanto si possa immaginare, e i servizi cloud si pagano a ore. In sostanza entro certi limiti può aver senso anche una robaccia come quella sopra.

Il log generato dal test

Il log generato dal test

Per la cronaca quelle sotto sono le schermate catturate dal test.

Giusto un giretto tra le altre piattaforme

Che tutto avrebbe funzionato anche con le altre tecnologie concorrenti di Xamarin e Titanium non avevo dubbi, ma un giro di prova ho preferito farlo comunque. Sotto potete vedere i miei esperimenti fatti con le applicazioni demo delle varie piattaforme.

Il tree in un'applicazione Native Script

Il tree in un’applicazione Native Script

Per qualche ragione che non ho avuto voglia di approfondire con React Native l’applicazione è esplosa, come se il bridge tra l’applicazione di test e quella da testare non abbia funzionato. Il comando tree richiamato con il repl (strumento utilizzabile da cli) ha visualizzato comunque l’albero degli elementi della schermata rossa.

Il tree in un'applicazione React Native

Il tree in un’applicazione React Native

Il tentativo con Ionic 2 l’ho fatto solo a scopo didattico, per vedere la famosa WebView. Teoricamente si può interagire con l’applicazione a furia di TapCoordinates, ma il test risultante sarebbe così fragile che non ha proprio senso perderci tempo. Per le applicazioni ibride basate su html tanto vale usare Protractor.

Il tree in un'applicazione Ionic 2

Il tree in un’applicazione Ionic 2

Conclusioni

Xamarin.UITest è una figata.

PRO:

  • È possibile interagire con il dispositivo mentre il test automatico è in atto, sia fisicamente che attraverso il repl
  • Con il comando tree da riga di comando si può visualizzare la struttura reale della propria applicazione così com’è stata renderizzata sul dispositivo, cosa che può tornare molto utile
  • Funziona su Android e iOS.
  • Con Xamarin Test Recorder si può registrare il proprio test andando poi a modificarne il codice
  • Il test in cloud è utile non solo per testare automaticamente su molti dispositivi diversi, ma anche per fare test specifici quando ci vengono segnalati dei bug su un dispositivo particolare che non abbiamo a disposizione
  • L’interfaccia web dei test in cloud è spaziale, ci da un sacco di informazioni anche sulle performance ed è possibile fare degli screenshot mirati in presenza di errori e non solo

CONTRO:

  • Non funziona su Windows
  • Il test recorder funziona solo per Android ed è stabile solo per Visual Studio
  • L’esecuzione dei test è lenta da far schifo
  • Il test in cloud costa un occhio della testa anche nella versione Newbie

TiShadow: un hack che (il più delle volte) può far risparmiare un sacco di tempo

Lo sviluppo mobile è una rogna, perché sul mercato ci sono un’infinità di dispositivi e svariate piattaforme e ogni singolo utente si aspetta di poter usare ciascuna applicazione di cui sente parlare.
Quando uno degli obiettivi è raggiungere più pubblico possibile la strada dello sviluppo multi-piattaforma offre varie alternative, ciascuna con i suoi punti di forza e le sue debolezze. Appcelerator Titanium è una di queste.

Compilazione e deploy a ogni modifica, ovvero come girarsi i pollici per la metà del tempo

Quasi in ogni contesto di sviluppo si deve affrontare l’enorme spreco di tempo dovuto a compilazione e deploy delle proprie applicazioni su dispositivi/simulatori, e Titanium non fa eccezione. O almeno non la faceva fino a un paio di anni fa.
Nel 2012 in Australia devono essersi stancati di aspettare il compilatore, e si sono inventati un hack: TiShadow. Il nome non rende molto bene l’idea, si tratta di un infrastruttura basata su un’applicazione mobile che funge da container (di applicazioni sviluppate con Titanium), un’applicazione server che ha il compito di iniettare il nostro codice all’interno del container, e una serie di comandi per console (CLI).

Un'immagine vale più di mille parole

Un’immagine vale più di mille parole


L’utilizzo in se non è complicato, e nemmeno capirne il funzionamento, ma è uno strumento ancora non così diffuso anche perché nei primi passi si può sbattere in alcuni spigoli e rimanere scoraggiati.

Distribuzione ed esecuzione paralleli su più dispositivi

Come è facile intuire stiamo parlando di strumenti sviluppati in Javascript per il Javascript, ecco quindi che spunta fuori npm e l’installazione consiste semplicemente nell’installazione di un modulo NodeJS, e ogni passaggio richiede l’utilizzo della linea di comando.

Installazione di TiShadow

npm install -g tishadow
(per un doveroso approfondimento sui moduli NodeJS leggete qui)
Una volta installato l’apposito modulo globale tutta una serie di comandi diventano disponibili da console.

Creazione dell’applicazione TiShadow

Andiamo quindi a creare una directory per la nostra applicazione, spostiamoci al suo interno, e lanciamo il comando per la creazione dell’app:
tishadow app -d ./
Fatto questo nella directory corrente comparirà un’applicazione Titanium installabile su tutte le piattaforme e contenente i moduli più comunemente usati. Quest’applicazione deve contenere tutto il necessario per il funzionamento della nostra app, perché sarà lei che ospiterà il pacchetto della nostra applicazione, o meglio tutto il contenuto della directory /app. Come potete vedere osservando la struttura di un progetto Titanium i moduli, i plugin, e soprattutto i file tiapp.xml e il manifest sono esterni alla directory app, è importante quindi andare ad aggiungere eventuali moduli non già presenti nell’installazione di TiShadow, e andare a “completare” il tiapp.xml della nuova applicazione ombra inserendovi gli eventuali moduli aggiunti e correggendo appid, version e altre informazioni che potrebbero essere importanti per il buon funzionamento della nostra app.

Deploy dell’applicazione ombra sui dispositivi

L’applicazione “ombra” creata al passo precedente deve essere installata sui nostri dispositivi (o simulatori) in cui dobbiamo testare la nostra applicazione mobile.
Se ad esempio vogliamo provare la nostra applicazione su un simulatore di Motorola Moto X creato con Genymotion sarà sufficiente utilizzare il CLI di Titanium con il comando:
ti build -p android -T emulator --device-id "Motorola Moto X - 4.3 - API 18 - 720x1280"
Questo passo in linea di principio è sufficiente farlo una volta, o almeno solo quando facciamo modifiche “sostanziali” alla nostra applicazione (come aggiunta di moduli o upgrade dell’sdk), perché poi l’applicazione TiShadow rimarrà sul nostro dispositivo/simulatore.

L'applicazione "ombra" TiShadow sul simulatore Genymotion

L’applicazione “ombra” TiShadow sul simulatore Genymotion


L’applicazione TiShadow è disponibile per lo scaricamento anche sull’Android Market, ma se ne sconsiglia vivamente l’utilizzo in favore dell’installazione vista sopra, perché non è chiaro quale sia la sua versione e non avremo modo di mettere le mani al suo interno (ad esempio per modificare il tiapp.xml o per aggiungere moduli).
TiShadow sul market. Ora dimenticatela.

TiShadow sul market. Ora dimenticatela.

Lancio del server TiShadow e connessione dell’applicazione ombra

Una volta installata e lanciata l’applicazione “container”, ci ritroveremo di fronte a una richiesta di connessione a un server…

Quale server TiShadow ?

Quale server TiShadow ?


Facendo un rapido riavvolgimento fino all’inizio dell’articolo potete vedere che avevo nominato un’applicazione server. Questa è una delle parti fondamentali del modulo TiShadow, e per lanciarla è sufficiente digitare:
tishadow server
Si possono specificare vari parametri ma niente di fondamentale. L’applicazione server si mette in ascolto su una porta (la 3000 di default), e aspetta che delle applicazioni client TiShadow vi si connettano così da stabilire un collegamento attraverso il quale iniettare la nostra applicazione.
Inseriamo quindi l’IP della nostra macchina sulla maschera di login dell’app TiShadow e lasciamo che i due attori si connettano.
Ripetiamo questo passaggio per ogni altro dispositivo/simulatore in cui vogliamo testare la nostra applicazione.

Esecuzione della nostra applicazione

Arrivati a questo punto dobbiamo solo posizionarci nella directory della nostra applicazione e dire a TiShadow di eseguirla:
tishadow run
Per magia ci ritroveremo con la nostra applicazione distribuita su ogni dispositivo/simulatore collegato al server.
Anche questo comando dispone di vari parametri, ad esempio con tishadow @ run il deploy è automatico ad ogni salvataggio… una manciata di secondi per un deploy a caldo su dispositivi multipli.
È doveroso specificare che spesso le cose si “incastrano” e c’è da riavviare server e/o client e/o fare qualche aggiustamento, fatto sta che lo strumento è di una potenza devastante.

Unit-test con TiShadow

Ipotizziamo per un momento che chi sta leggendo sia abituato a lavorare soltanto su una piattaforma e abbia una macchina abbastanza veloce da non trarre troppo giovamento da deploy a caldo e distribuzione parallela su più dispositivi, può trarre comunque giovamento da TiShadow? Una domanda scritta in questo modo presuppone una risposta affermativa, specialmente dopo aver letto il titolo, e infatti la risposta è sì.
Ci sono un sacco di moduli e librerie per eseguire test automatici di unità in Javascript, ma farli funzionare con Titanium non è sempre così semplice. TiShadow ci aiuta perché integra al suo interno la libreria Jasmine, e una volta scritte le specifiche nella consueta forma describe-it e inserite nella directory app_main_directory/spec il gioco è fatto.

describe(&quot;Spec description&quot;, function(){
  it(&quot;Test description&quot;, function(){
    expect(true).toBe(true);
  });
});

Non serve installare moduli o includere librerie, perché è già tutto dentro TiShadow. Basta descrivere le nostre specifiche come nell’esempio sopra, inserirle in file terminanti con il suffisso “_spec.js” e includere questi file nella directory “spec”. L’esecuzione dei test consiste in due parole:
tishadow spec

Conclusioni

La lentezza delle procedure di deploy è un problema sentito, specialmente nell’ambito dello sviluppo mobile. Forse meno sentito ma non meno importante è l’argomento dei test automatici. Su Titanium entrambi i problemi sono più o meno risolti da TiShadow, un modulo ancora ignoto a molti forse anche a causa del nome enigmatico.
Speriamo che questo breve articolo serva a fare un po’ di luce sull’argomento e a dissipare le ombre che molti vedono aleggiare su Titanium.