Full text
Títol: Testing on iPhone Volum: 1 Alumne: Francesc Navarro González Director: Alejandro Ríos Ponent: José Ramón Herrero Zaragoza Departament: Arquitectura de Computadors Data: 24 de gener del 2011
Testing on iPhone 2
Testing on iPhone 3 DADES DEL PROJECTE Títol del Projecte: Testing on iPhone Nom de l'estudiant: Francesc Navarro González Titulació: Enginyeria en Informàtica Crèdits: 37.5 Director: Alejandro Ríos Ponent: José Ramón Herrero Zaragoza Departament: Arquitectura de Computadors MEMBRES DEL TRIBUNAL (nom i signatura) President: Enrique Morancho Llena Vocal: José Miguel Rivero Almeida QUALIFICACIÓ Qualificació numèrica: Qualificació descriptiva: Data:
Testing on iPhone 4
Testing on iPhone 5 Índex 1. Introducció ...........................................................................................................................8 1.1. Estat de l’art .................................................................................................................9 1.2. Descripció del problema ...............................................................................................9 1.3. Solució proposada ......................................................................................................10 1.4. Mobivery ....................................................................................................................11 2. Què és el testing? ...............................................................................................................12 2.1. Història .......................................................................................................................12 2.1.1. Testing orientat al debugging .........................................................................12 2.1.2. Testing orientat a les demostracions ..............................................................12 2.1.3. Testing destructiu ...........................................................................................13 2.1.4. Testing orientat a la avaluació ........................................................................13 2.1.5. Testing orientat a la prevenció .......................................................................13 3. Per què fer testing? ............................................................................................................14 3.1. Quant costa solucionar un error? ...............................................................................15 3.2. Testing VS correcció d’errors ......................................................................................16 3.3. Testing i la gestió del projecte ....................................................................................17 3.4. Altres beneficis ...........................................................................................................17 4. Tipus de testing ..................................................................................................................18 4.1. Nivell de testing ..........................................................................................................18 Tests Unitaris ..................................................................................................................18 Tests d’Integració ...........................................................................................................18 Tests funcionals o de sistema .........................................................................................19 4.2. Tècniques de testing ...................................................................................................19 Tests de caixa negra .......................................................................................................20 Tests de caixa blanca ......................................................................................................22 4.3. Testing convencional Vs Agile Testing .......................................................................26 5. Mètriques en el testing ......................................................................................................27 5.1. Code Coverage ...........................................................................................................28 5.2. Qualitat en els tests ....................................................................................................28 6. Testing a Mobivery en l'actualitat ......................................................................................30 6.1. Actualitat ....................................................................................................................30 6.2. Primeres millores........................................................................................................31
Testing on iPhone 6 6.3. Futur ...........................................................................................................................32 7. Tecnologies disponibles .....................................................................................................33 7.1. Breu història dels frameworks ....................................................................................33 7.2. Anàlisi dels frameworks ..............................................................................................36 Tests unitaris ......................................................................................................................36 Tests Funcionals .................................................................................................................46 Integració Continua ............................................................................................................59 7.3. Comparació amb altres camps de desenvolupament .................................................73 7.4. Conclusions ................................................................................................................75 8. Planificació del projecte .....................................................................................................77 8.1. Estudi econòmic .........................................................................................................80 8.2. Gestió del projecte: Scrum .........................................................................................82 8.3. Així funciona Scrum ....................................................................................................83 8.4. Scrum en detall ...........................................................................................................84 9. Objective-c .........................................................................................................................85 9.1. Història .......................................................................................................................85 9.2. Ús d’Objective-c .........................................................................................................85 9.3. Característiques ..........................................................................................................86 Resum de sintaxis ...........................................................................................................86 Propietats .......................................................................................................................88 Herència, protocols i categories .....................................................................................89 Tipificació dinàmica ........................................................................................................91 Garbage collector ...........................................................................................................91 No disposa de namespaces ............................................................................................91 Algunes optimitzacions no són aplicables ......................................................................92 10. Testejant una aplicació: FCBMobile ................................................................................93 10.1. FCBMobile ..............................................................................................................93 10.2. Tests unitaris ..........................................................................................................93 10.3. Tests Funcionals .....................................................................................................97 Resum: funcionalitats de l'aplicació ...................................................................................97 Test Cases ...........................................................................................................................98 10.4. Millores en el codi per facilitar el testeig ..............................................................106 11. Conclusions ..................................................................................................................107 11.1. Dificultats durant el PFC .......................................................................................108
Testing on iPhone 7 11.2. Camí a seguir ........................................................................................................108 11.3. Conclusions personals ..........................................................................................110 12. Cites i referències al peu ..............................................................................................111 13. Bibliografia ...................................................................................................................112 14. Més informació ............................................................................................................113
Testing on iPhone 8 1. Introducció L’objectiu principal d’aquest Projecte Final de Carrera, realitzat com un Conveni de Cooperació Educativa a l’empresa Mobivery, ha estat investigar quines són les tècniques de testing més interessants que poguéssim aplicar al desenvolupament d’aplicacions per dispositius iPhone. Els primers mesos van ser realment interessants. D’una banda, em vaig començar a familiaritzar amb el món del testing i totes les seves implicacions en el desenvolupament d’un projecte: diferents tipus de tests, metodologies de testing que han evolucionat al llarg de la història, opinions i punts de vista diferents sobre com aplicar aquestes tècniques, etc. Però el més important era aplicar aquests coneixements als projectes que realitza l’empresa, de manera que al mateix temps que anava adquirint coneixements sobre testing, aprenia el llenguatge de programació Objective-c i les particularitats del desenvolupament en una plataforma tan especial com un dispositiu mòbil. Per escriure els tests bé calia conèixer l’entorn en el qual treballaria. Tot i així, ben aviat descobriria que tots els coneixements tècnics que estava aprenent només serien una part de la meva formació. Des del primer dia vaig passar a formar part d’un equip integrat per programadors, dissenyadors i gestors de projectes (un equip dels molts que formen Mobivery). I que implica això de ser part d’un equip? A l’empresa feia uns mesos que havien pres la decisió d’organitzar-se d’una manera diferent i començar a aplicar una nova metodologia de gestió de projectes: Scrum. En resum, Scrum promou la organització en equips interdisciplinaris (en comptes de tenir el “departament” de disseny per una banda i el de programadors per una altra) i una interacció més directa entre l’equip i els clients pels qui s’estan desenvolupant els projectes, de manera que la comunicació sigui més fluïda entre tots els interessats en la correcta realització del projecte, i estableix un conjunt de reunions per tal que els membres de l’equip decideixin quines tasques de quins projectes es realitzaran durant la setmana. També he aprés molt de les discussions entre l’equip per com gestionar els múltiples projectes que es desenvolupaven en paral·lel, i com afrontar les exigències de cadascun dels clients pensant en les necessitats de l’empresa. He tingut la oportunitat de veure en primera persona la, sempre complicada, relació entre clients i empresa / caps de projecte / programadors, la qual cosa m’ha permès adquirir una experiència que considero molt valuosa per a la formació de qualsevol que vulgui dedicar-se al desenvolupament d’aplicacions o webs comercials. Així doncs, de seguida em vaig trobar col·laborant amb la resta de programadors escrivint aplicacions iPhone per a clients, i sobretot, gaudint de l’oportunitat d’aprendre de tots ells: m’han ajudat a donar les primeres passes i a resolt els dubtes que, com a novell, m’anaven sorgint, i a més a més he pogut discutir amb ells sobre com dissenyar les aplicacions o si era millor programar d’una manera o d’una altra. Així que, des d’aquestes línies, vull aprofitar per agrair-los el temps que m’han dedicat i els divertidíssims moments que hem passat. I si tot això ja és molt més del que esperava aprendre, he pogut entendre una mica millor com funciona una startup des de dins, amb els problemes i dificultats que comporta intentar
Testing on iPhone 9 aixecar una empresa en els temps que corren, però sobretot amb les ganes i dedicació que posen cada dia tots els membres les oficines de Madrid, Sevilla i Barcelona. 1.1. Estat de l’art El testing cada cop més s’està convertint en una eina fonamental en el desenvolupament de tot tipus d’aplicacions. En els entorns més madurs, tant en la creació d’aplicacions d’escriptori com webs, s’han consolidat una gran quantitat de frameworks i eines que ens ajuden a realitzar tests de tot allò que puguem necessitar: interfícies gràfiques, comunicació entre una aplicació i un servidor, si les funcions dels nostres objectes retornen els valors esperats, la càrrega de visitants que és capaç de suportar una web, i un llarg etc. Però si totes aquestes eines han estat cabdals per a facilitar el testing, i d’aquesta manera, promoure el seu ús en una gran varietat d’entorns, igual d’important ha estat la evolució de les metodologies de desenvolupament de software per tal de maximitzar els beneficis que aporta escriure tests. Si en els primers models, com el desenvolupament en cascada, es va incloure la fase de testing com una etapa posterior a la creació del software que servia per comprovar si la aplicació era prou estable com per començar a distribuir-la, ara ens trobem amb uns models, com el Test Driven Development (o TDD), que promou la escriptura dels tests abans de la codificació del producte i anar passant progressivament conjunts cada cop més grans de tests fins que el software estigui finalitzat. Això mostra fins a quin punt els tests són protagonistes en el món de la programació actual. Però aquesta cultura del testing no està arrelada de la mateixa manera en tots els països, ni per descomptat, en totes les empreses. Tradicionalment, les empreses espanyoles que desenvolupen software no han estat les més “formals” en l’aplicació de metodologies per a la gestió, disseny i programació dels seus projectes, així que la gran majoria d’empreses encara estan molt endarrerides en l’aplicació de tècniques de testing. Així doncs, ens trobem en una situació en que la competitivitat de les empreses passa per ser més eficients, millorar la qualitat dels productes i ser tan flexibles com sigui possible per respondre ràpidament als canvis en el mercat i a les necessitats dels clients. I per resoldre aquest complicat trencaclosques, els tests (i les estructures que l’envolten, com els sistemes d’integració continua, controls de qualitat, etc.) s’estan tornant una eina imprescindible per a acostar-nos a aquests objectius. 1.2. Descripció del problema El principal problema que cal resoldre és la gran quantitat de bugs i errors de funcionament d’una aplicació, que provoquen tres greus contratemps: D’una banda, els errors ens obliguen a repassar codi de l’aplicació que s’havia donat per bo, cercar quin és l’origen del bug, resoldre’l i distribuir una nova versió del programa. Això alhora danya la imatge del producte i de la nostra empresa, ja que els clients i els usuaris perceben una certa fragilitat en el programa que estan provant.
Testing on iPhone 16 El que podem observar és que, quant més triguem en detectar i solucionar l’error, major serà el seu cost. Els errors en les primeres fases del cicle de vida són els més perillosos ja que s’aniran propagant a les posteriors fases i el cost creixerà exponencialment. 3.2. Testing VS correcció d’errors El testing intenta, d’una manera proactiva, detectar els errors que puguin sorgir durant les etapes de desenvolupament més tècniques del projecte. La idea és que en comptes de resoldre els problemes quan algú se’ls trobi (típicament això passa a les demostracions), escriurem un conjunt de tests que forcin l’aplicació a provar els casos extrems o aquells on és més probable que falli. Alguns errors seran detectats immediatament i ens estalviarem haver de resoldre’ls en el futur, de manera que els programadors encara tindran present el codi que està fallant i trigaran menys temps en arreglar-ho que si s’haguessin de rellegir parts del programa que potser fa setmanes que no han modificat. Els tests tenen un altre gran avantatge: un cop escrits, es poden executar automàticament quan vulguem. Molts problemes apareixen com a efectes secundaris de modificacions en altres parts del codi, ja que potser compartien la mateixa informació i ara la canviem, o passem a utilitzar biblioteques que no són exactament equivalents a les anteriors, etc. D’aquesta manera
Testing on iPhone 17 podrien deixar de funcionar parts de l’aplicació que havien estat provades i validades, però no ens adonarem fins que algú torni a provar les parts que ja havien estat validades en el passat. Ja veiem que és inviable fer proves manuals de tota l’aplicació cada cop que hi ha un petit canvi. Però els tests automàtics es poden executar periòdicament (cada vegada que compilem, quan enviem dades al repositori, etc.), i tindrem la certesa de que les noves modificacions afecten o no a les parts que ja havíem testejat. 3.3. Testing i la gestió del projecte No només el testing ajuda a minimitzar l’aparició tardana d’errors: la gestió de projectes esdevé clau a l’hora d’organitzar el cicle de vida de l’aplicació. És important que totes les fases del cicle estiguin orientades a obtenir un resultat “avaluable” tan aviat com sigui possible. Per exemple, si no podem dissenyar completament una aplicació en una setmana, segurament sí que podrem tenir algunes parts i dedicar una mica de temps a comprovar que són correctes. D’aquesta manera detectarem els errors abans de finalitzar el disseny i podrem realitzar els canvis que calguin per adaptar-lo a les veritables necessitats. Per aquest motiu és important no incloure el testing com un procés que va al final de cada fase, sinó que es realitza en paral·lel amb el desenvolupament del producte. 3.4. Altres beneficis Escriure tests, o programar pensant que haurem d’escriure aquests tests, ajuda a organitzar millor el codi perquè obliguen als programadors a tenir en compte algunes idees: Els tests només tenen sentit si s’apliquen sobre porcions petites del codi, com ara funcions. Si tenim una funció que realitza diverses tasques complexes, com per exemple una sèrie d’operacions matemàtiques que acaben modificant valors d’una base de dades Serà més difícil escriure un únic test que ho comprovi tot que no si estigués dividida en dues funcions (una per fer les matemàtiques i una altra per l’accés a base de dades) i poguéssim escriure dos tests simples. Si hem repetit el mateix codi en diferents llocs, també haurem de replicar el test, així que per estalviar-nos feina repetitiva normalment encapsularem el codi que es repeteix en una funció i escriurem un únic test. Ajuda a mantenir separades parts del codi que afecten a conceptes diferents. No voldrem (i moltes vegades no podrem) escriure un test que alhora comprovi aspectes de la capa de dades i de la part visual.
Testing on iPhone 18 4. Tipus de testing Els tipus de testing s’agrupen en funció del nivell de detall que testegen i de la metodologia seguida a l’hora d’escriure els test. 4.1. Nivell de testing Tests Unitaris Aquests tests verifiquen que els elements més atòmics del codi, normalment funcions i classes, es comporten adequadament. L’objectiu dels tests unitaris és comprovar que totes les parts del programa funcionen correctament quan estan aïllades de la resta de components, de manera que puguem detectar qualsevol problema abans de començar a integrar-lo amb la resta de components. Els tests unitaris ajuden a detectar els errors aviat, el que reduirà els costs de reparar-los. A més a més, els tests serveixen com a documentació del codi ja que estableixen un contracte molt estricte sobre el que s’espera que facin els objectes testejats. Vegem la estructura bàsica d’un test unitari: int a = funcio1(); // funcio1() i funcio2() retornen un enter int b = funcio2(); STAssertTrue( a == b, “Error! a i b són diferents”); En aquest exemple comprovem que els valors d’a i b siguin iguals, és a dir, que sigui certa la expressió que passem en el primer paràmetre de STAssertTrue. Sinó, s’imprimiria per pantalla el missatge que hem inclòs com a segon paràmetre i ens alertarà de que ha fallat un test. “STAssertTrue” forma part de la col·lecció de mètodes que ens ofereixen les biblioteques per escriure tests unitaris. N’hi ha de tota mena: STAssertNotNull, STAssertEquals, etc., més endavant els veurem amb més detall. Tests d’Integració Els tests d’integració s’utilitzen quan el nostre sistema haurà d’utilitzar diversos components que s’han creat independentment, comunicar-se amb altres aplicacions o obtenir informació de sistemes que no controlem (webservices, servidors de dades externs, etc.). Els tests d’integració més comuns comproven casos com ara: Si l’aplicació disposa de diverses parts que cal integrar, es dissenyen uns tests que validin si funcionen coherentment un cop les unim. Com reacciona el sistema si hi ha problemes amb la connexió (a Internet, a altres dispositius, per Bluetooth, etc.) i no podem accedir a determinats recursos.
Testing on iPhone 19 Tests de disponibilitat que periòdicament comproven si els servidors estan operatius i són accessibles. En l’actualitat, els tests d’integració s’estan centralitzant en el que s’anomena “Sistemes d’Integració Continua”. Sistemes d’Integració Continua Aquests sistemes són aplicacions que faciliten la execució automàtica tant dels tests d’integració com dels unitaris. El cicle de vida típic dels Sistemes d’Integració Continua consisteix en: 1. Descarregar la última versió del codi font d’un projecte cada cert temps 2. Compilar-lo i generar un informe si no s’ha pogut finalitzar amb èxit 3. Executar els tests i donar un informe indicant si alguns tests han fallat 4. Generar estadístiques sobre els problemes que ha detectat Gràcies a aquestes eines els programadors poden obtenir una informació molt valuosa que els permetrà identificar i corregir problemes molt més ràpidament i veure l’estat actual del projecte. Tests funcionals o de sistema Són els tests que comproven el bon funcionament de tot el sistema, com si es tractés d’un únic element. En aquest nivell s’inclouen molts tipus de tests, entre els que destaquem: Tests de la interfície gràfica: comproven que es mostren tots els elements visuals i són interactuables, que la informació que veu l’usuari s’actualitza correctament, etc. Tests d’usabilitat: avaluen si el sistema compleix els objectius pels quals s’ha construït des del punt de vista de l’usuari final. Tests d’estrès, càrrega i rendiment: són tests que forcen situacions de màxima exigència al sistema, com per exemple fent una gran quantitat de peticions a un servidor web i comprovar si totes reben resposta i quant temps triguen. Tests de seguretat: simulen atacs al sistema, robustesa dels permisos als diferents usuaris, etc. 4.2. Tècniques de testing Les tècniques de testing s’acostumen a dividir en dues categories depenent de si els tests coneixen o no el codi intern d’allò que volem comprovar. Els tests que fan ús d’aquest coneixement s’anomenen tests de caixa blanca o caixa transparent, ja que “veuen” a través del codi que testegen. Pel contrari, els tests de caixa negra són els que només testegen els “inputs” i els “outputs” (o pre-condicions i post-condicions) del sistema.
Testing on iPhone 20 Amb aquesta primera definició dels tests de caixa blanca i negra ja tenim una idea general sobre quins tipus de tests es podrien incloure en cadascuna de les dues categories, però en un anàlisi més profund hem trobat interessants discrepàncies entre diferents bibliografies. En concret, la bibliografia més estesa fa una distinció molt clara entre quins tipus de tests són de cada categoria: si per escriure el test necessites conèixer qualsevol estructura de dades o algoritme del sistema, és de caixa blanca, altrament és de caixa negra. Per exemple, els tests unitaris es consideren de caixa blanca perquè clarament cal conèixer els objectes del sistema per poder-lo escriure, mentre que els tests d’usabilitat o rendiment serien de caixa negra perquè els podem realitzar independentment a com s’hagi construït aquest sistema. Així doncs podríem concloure que en general, els nivells de testing unitari i d’integració són de caixa blanca i els del nivell de sistema de caixa negra. Arran d’aquesta definició va sorgir una discussió5 molt interessant amb companys amb força experiència en el camp del testing. La discussió girava al voltant de la següent idea: realment no puc escriure tests unitaris de caixa negra? Amb un exemple quedarà més clar. Suposem que volem testejar una classe que té diversos mètodes, alguns privats i d’altres públics. Si escrivim un test per a una funció pública on l’únic que comprovem és que donat un paràmetre concret (input) obtenim el resultat esperat (output), no es podria considerar com un test de caixa negra? No hem tingut en compte quina és la implementació d’aquest mètode a l’hora de dissenyar el test ni ens preocupa quins altres efectes pugui provocar la seva execució, de manera que des del nostre punt vista estem tractant la funció (i la classe) com una caixa negra. Tot i que aquesta idea sembla contradictòria amb la definició més acceptada, en realitat l’únic que estem canviant és el punt de vista. Típicament es defineix “la caixa” com tot el sistema, de manera que els tests es consideren d’una categoria o d’una altra en funció d’aquesta caixa. Si definíssim la caixa en funció del nivell de testing veuríem que hi ha una riquesa que val la pena tenir en compte i podríem parlar de tests unitaris de caixa blanca i caixa negra, tests d’integració de caixa blanca i caixa negra, etc. Per aquest motiu ens ha semblat més adequat definir les categories seguint aquest criteri en comptes de limitar-nos a explicar els conceptes tal i com surten a la bibliografia, ja que creiem que és més coherent amb els tipus de tests que hem escrit durant la realització d’aquest projecte. Tot i així, també hi ha autors com Marc Clifton6 que ho entenen com nosaltres. Tests de caixa negra Un test de caixa negra és aquell que proporciona els paràmetres d’entrada (inputs) i verifica els resultats obtinguts (outputs), sense conèixer els mecanismes interns de l’objecte que testegem. Són tests de més alt nivell que els de caixa blanca i en general no es pretenen ser exhaustius amb totes les possibilitats. Més aviat es tracta d’assegurar-se que si algú canvia parts de 5 Voldria agrair l'esforç de l'Alejandro Scandroli especialment en aquest tema, ja que bona part de les idees i exemples aquí explicats han sorgit de les nostres discussions. 6 Marc Clifton: Advanced Unit Testing
Testing on iPhone 21 l’objecte que testegem, l’aplicació continuarà funcionant (o almenys algun test fallarà i sabrem que hem trencat alguna cosa). En aquests tests, normalment no coneixem la següent informació: Quin és el tractament d’errors. Si els inputs estan executant totes les alternatives. No sabem si hauríem de passar uns paràmetres concrets per tal de comprovar tota la casuística, ens limitem als casos que ens interessen. Possibles dependències amb altres recursos. L’objecte que testegem pot necessitar altres objectes, accedir al disc, etc. però des del punt de vista d’aquest test, no ens importa. Típicament, un test de caixa negra comprova que les entrades correctes produeixen resultats satisfactoris, precisament per la manca d’informació sobre gestió d’errors, camins d’execució, etc. que hem comentat. Exemples Ara veurem alguns exemples de tests de caixa negra. Test Unitari Aquest test pretén posar a provar una biblioteca que converteix un array d’objectes a un string en format JSON. Com que no estem interessats en la implementació interna de la biblioteca, l’únic que testejem és si donat un array conegut obtenim la conversió a JSON que hauríem fet nosaltres. - (void) testSerializeArray { NSArray* array = [NSArray arrayWithObjects:@"a string", [NSNumber numberWithBool:YES], [NSNumber numberWithInt:23], [NSNull null], nil]; NSString* result = [[CJSONSerializer serializer] serializeArray:array]; STAssertEqualStrings(result, @"[\"a string\",true,23,null]", @"Serializing a NSArray doesn't returns the correct string"); } En la primera instrucció creem un array amb diferents objectes. Després el convertim en un JSON amb la biblioteca CJSONSerializer. Finalment comprovem que el resultat de la conversió és igual al que nosaltres esperàvem. Test Funcional Uns altres tests típicament de caixa negra són els funcionals. En aquest exemple mostrem un fragment de test en el que comprovem si l’usuari pot “tocar” un botó d’una determinada pantalla.
Testing on iPhone 22 El test està definit com un XML (concretament una PList) que és executat per la biblioteca FoneMonkey, que s’encarrega de controlar la interpretació dels events que normalment realitzar l’usuari (prémer un botó, seleccionar una cel·la de la taula, etc.). Més endavant veurem aquests tests en detall. <dict> <key>args</key> <array> <string>3</string> </array> <key>className</key> <string>UITableView</string> <key>command</key> <string>VScroll</string> <key>monkeyID</key> <string>-33</string> </dict> <dict> <dict> <key>args</key> <array/> <key>className</key> <string>UIButton</string> <key>command</key> <string>Touch</string> <key>monkeyID</key> <string>Himne 1957</string> </dict> En el primer bloc hem definit un scroll vertical cap avall d’una taula, mentre que en el segon premem el botó anomenat “Himne 1957”. El tipus d’event a realitzar s’indica en el camp “command”, ì en el “monkeyID” especifiquem l’identificador de l’objecte que rebrà l’event. Tests de caixa blanca En un test de caixa blanca disposem de la informació necessària per ser molt més específics. A diferència dels de caixa negra, amb aquests tests podem comprovar: Si es llencen les excepcions al produir-se un error, o si es validen correctament els paràmetres d’entrada (per exemple, si és nul). Que els tests executin tots els camins possibles. Això ens pot ajudar a descobrir errors en els casos menys freqüents i ens farà pensar sobre si ens estem deixant alguna condició. Les dependencies entre recursos són conegudes, i per tant, podrem escriure tests que s’assegurin de que estan disponibles abans d’utilitzar-los. Podem analitzar els algoritmes de la caixa, de manera que els podríem forçar a executar-se amb valors que provin els seus límits. Aquests tests, al ser molt més precisos, es converteixen en una mena de documentació molt útil per al manteniment de l’objecte testejat. Un altre aspecte interessant a tenir en compte és que els tests de caixa blanca podem comprovar l’estat de la caixa després d’executar els tests. D’aquesta manera no només ens
Testing on iPhone 23 assegurem que els resultats són correctes, sinó també que l’estat final de la caixa és correcte. Per exemple, si tenim un llistat d’usuaris i esborrem un, el resultat podria ser “OK” i passar el test de caixa negra. Però en un de caixa blanca a més a més podríem comprovar si a l’estructura de dades que guarda l’usuari (una taula en memòria, una base de dades, etc.) realment s’ha esborrat. Exemples Els test de caixa blanca acostumen a ser unitaris ja que són els que tenen més fàcil accés als objectes de baix nivell de l’aplicació. Test Unitari En aquest test comprovem que el càlcul d’unes coordenades dóna el resultat correcte. Per aconseguir-ho necessitem crear l’objecte que volem testejar, assignar-li uns valors pels quals coneixem el resultat i realitzar el càlcul. Desprès podrem comprovar que els valors són els esperats. - (void) testCoords { CuleCluster *mundo = [CuleCluster new]; mundo.radio = 1; mundo.lat = 0; mundo.lng = 0; [mundo recalculate]; STAssertEqualsWithAccuracy(mundo.xPoint, 1.0, 0.01, @"lat=0 and lng=0 returns incorrect result"); STAssertEqualsWithAccuracy(mundo.yPoint, 0.1, 2.45, @"lat=0 and lng=0 returns incorrect result"); mundo.lng = M_PI; [mundo recalculate]; STAssertEqualsWithAccuracy(mundo.xPoint, 1.0, 0.01, @"lat=0 and lng=PI returns incorrect result"); STAssertEqualsWithAccuracy(mundo.yPoint, 0.055, 0.01, @"lat=0 and lng=PI returns incorrect result"); mundo.lng = 2*M_PI; [mundo recalculate]; STAssertEqualsWithAccuracy(mundo.xPoint, 0.993, 0.01, @"lat=0 and lng=2*M_PI returns incorrect result"); STAssertEqualsWithAccuracy(mundo.yPoint, 0.109, 0.01, @"lat=0 and lng=2*M_PI returns incorrect result"); /* etc. */ } Stubs i mocks per ajudar als tests Moltes vegades quan volem testejar un objecte hem de crear altres objectes “col·laboradors”7. Per exemple, si en una botiga virtual volem comprovar que quan un Client compra un llibre, 7 Martin Fowler: Mocks Aren't Stubs
Testing on iPhone 24 aquest passa a formar part d’una llista de coses comprades pel Client, necessitarem crear també l’objecte Botiga. Però per comprar el llibre també caldria utilitzar la passarel·la de pagament, i probablement estaríem obligats a tenir connexió a Internet per a executar el test. Ja veiem que escriure un test per comprovar la part que ens interessa ens obliga a generar una gran quantitat de soroll en el sistema. Això no és un problema menor, perquè la configuració d’aquests col·laboradors pot ser costosa tant en recursos (memòria, CPU, etc.) com en temps (quan triga a validar-se el pagament?), i si volem executar els tests tantes vegades com sigui possible per detectar els errors quan abans millor, estarem creant un coll d’ampolla amb qualsevol test d’aquest tipus. En l’actualitat, hi ha diverses tècniques que permeten realitzar tests de caixa blanca molt precisos, observant quins mètodes d’una classe s’han cridat, i que simplifiquen els testeig dels objectes col·laboradors. Stubs Una de les tècniques més comunes és la construcció d’objectes stubs. Aquests objectes són implementacions parcials dels objectes que tindríem a l’entorn de producció. Seguint amb l’exemple anterior, el Client compraria un llibre a una BotigaStub en comptes d’una Botiga. A la BotigaStub reimplementaríem el mètode pagarUsantPasarela() i només retornaria quelcom que sigui útil per a l’execució del test. Veiem com crearíem aquest objecte stub en Java: public interface Botiga { public void comprar (Client client, String idProducte); private boolean pagarUsantPasarela(); } public class BotigaStub implements Botiga { private boolean pagarUsantPasarela() { return true; } } Així, quan el mètode comprar() comprovi si s’ha pogut realitzar el pagament forcem a que sempre digui “ok”. D’aquesta manera evitem tractar la complexitat de la pasarel·la de pagament en un test on l’objectiu és comprovar si el producte s’afegeix a la llista de productes comprats pel client. El test es podria escriure de la següent manera: public void testProductesCompratsSonActualitzats () { // Configuració Client client = new Client(); BotigaStub botiga = new BotigaStub(); botiga.afegirStock(“Firmin”, 1); // Entrenament botiga.comprar(client, “Firmin”);
Testing on iPhone 25 // Verificació assertTrue(client.haComprat(“Firmin”)); } Si el test té èxit, voldrà dir que al comprar el producte “Firmin” aquest s’ha registrat correctament en la llista de productes comprats pel client i ens em estalviat connectar amb la passarel·la de pagament, comprovar que el client tingués diners suficients, etc. De la mateixa manera podríem haver reescrit el mètode afegirStock() si aquest necessités accedir a una base de dades que no valgués la pena crear en aquest moment. La idea és que si aquestes operacions ja es comproven en altres tests, estaríem repetint la mateixa feina (una feina costosa) cada vegada que un test necessita una part molt petita de l’objecte col·laborador per a poder executar-se. Mocks Una altra aproximació és utilitzar objectes mocks. Aquests objectes són representacions dels objectes reals, i no implementen totes les seves característiques. Per a utilitzar els mocks necessitem alguna biblioteca que ens doni aquesta funcionalitat, com ara OCMock per Objective-c o jMock per Java. Veiem un exemple d’aquest últim. public void testProductesCompratsSonActualitzats () { // Configuració Client client = new Client(); Mock botigaMock = new Mock(Botiga.class); // Expectatives botigaMock.expects(once()).method("hiHaStock") .with(eq(“Firmin”)) .will(returnValue(true)); botigaMock.expects(once()).method("pagarUsantPasarela"). .will(returnValue(true)); botigaMock.expects(once()).method("afegirProducteAClient") .with(eq(client), eq(“Firmin”)) .after("eliminarStock"); //no és necessari però serveix d’exemple // Entrenament Botiga botiga = (Botiga) botigaMock.proxy(); botiga.comprar(client, “Firmin”); // Verificació botigaMock.verify(); assertTrue(client.haComprat(“Firmin”)); } La diferència més interessant respecte als stubs és la creació de les “expectatives”. En la primera expectativa diem que el mètode “hiHaStock” s’hauria de cridar una vegada (once()) amb el paràmetre “Firmin” i que retornarà “true”.
Testing on iPhone 32 Provar l’aplicació en diversos dispositius, principalment amb les versions d’iOS més propenses a donar problemes. Per ara, caldria provar-la en un dispositiu amb multitasking (4.0 o superior) i en un sense. Però aviat veurem com això no és suficient. L’aplicació està evolucionant constantment, i les proves que vam realitzar la setmana passada haurem de repetir-les aquesta perquè el comportament d’algunes pantalles han canviat (o per qualsevol altre motiu), i els tests ha realitzar s’aniran acumulant fins que tornem a deixar-los de banda per falta de temps. La solució a aquest dilema passa per automatitzar-los. En una primera fase no cal pensar en escriure un test de totes i cada una de les classes. De fet, en el nostre entorn són més interessants els tests funcionals que els unitaris perquè la majoria de problemes estan relacionats amb la interfície gràfica, ja sigui per que no podem navegar d’una pantalla a una altra, l’aplicació es penja al fer una acció determinada o no es mostren els resultats esperats. Més endavant veurem com escriure un test amb FoneMonkey no costa gaire més temps que fer la prova manualment, amb l’avantatge de que el podrem reproduir tantes vegades com calgui. Amb una bona col·lecció de tests funcionals, serem capaços de detectar la majoria de problemes que sorgeixin al canviar el comportament de l’aplicació. Si a més a més afegim un test (o modifiquem un existent) per cada bug que se’ns hagi escapat en la primera revisió, poc a poc construirem una sòlida estructura que detectarà la majoria d’errors i ens donarà molta més seguretat sobre el producte que entreguem al client. 6.3. Futur Donat que a Mobivery es gestionen els projectes seguint Scrum, un estaria temptat de dir que el seu futur passa per desenvolupar amb una metodologia Test Driven Development. Personalment, crec que seria complicat adaptar tota la empresa al TDD tenint en compte que fins ara no s’estaven escrivint tests. També és cert que alguns projectes són més curts i altres més llargs, uns més simples i altres més complicats. Valdria la pena tenir en compte aquestes diferències i si des del principi sabem que estarem treballant en una aplicació complexa, hauríem d’escriure més tests de tot tipus encara que al principi el desenvolupament sigui més lent. Uns bons tests unitaris i sobretot funcionals des del primer o segon sprint, ens estalviaran molta feina en el futur sense cap mena de dubte. Però de la mateixa manera que es va començar a aplicar Scrum i s’ha anat perfilant aquesta metodologia per adaptar-la a les necessitats concretes de Mobivery, el mateix hauria de passar amb les metodologies de testing. Tenint una base per on començar i entenent els seus principis, és qüestió de valorar quins tests són els que ens donen més rendiment i potenciarlos, a l’hora que ens mantenim al dia de les novetats que ens puguin ajudar a ser més eficients.
Testing on iPhone 33 7. Tecnologies disponibles Des del principi d’aquest projecte, vam considerar fonamental investigar a fons quines eren les millors opcions per escriure tests en un camp encara relativament nou com és el desenvolupament per iPhone. Això ens ha portat a comparar les diferents eines i frameworks, tant oficials com desenvolupades per terceres parts, i triar les que aportaven solucions més interessants per a nosaltres. Les principals característiques que hem valorat per escollir entre un o altre han estat: Útils per al nostre entorn. Els tipus de projectes desenvolupats per nosaltres no són iguals que en altres empreses, ja que acostumen a ser curts (entre 2 i 3 mesos) i la interacció entre l’usuari final i l’aplicació és la part essencial. Facilitat de configuració i instal·lació. Els tests els escriuran els propis programadors, no tindrem un equip de testers. Per tant, tothom haurà d’aprendre a utilitzar els frameworks i quant més fàcil sigui utilitzar-ho, millor acollida tindrà entre els programadors. Suport en les diferents versions del Sistema Operatiu. Fins i tot abans de l’aparició de l’iPad ja coneixíem les grans diferències que introdueix Apple pràcticament cada any, tant a nivell de dispositius (iPhone, iPhone 3G, iPhone 3Gs, iPhone 4) com de SO (per exemple, des de la sortida de iOS 2.0 fins a la 2.2.1, la última release abans de la 3.0, només van passar set mesos i hi van haver sis versions diferents). Per tant, necessitem eines que estiguin en desenvolupament constant, adaptant-se a les novetats que vagin apareixent. Codi Obert. Si bé no és una característica indispensable, sí és força interessant disposar del codi font dels frameworks que utilitzem per a poder actualitzar o millorar alguna part que considerem fonamental pel nostre desenvolupament. 7.1. Breu història dels frameworks Abans d’analitzar els diferents frameworks disponibles en l’actualitat, és interessant fer un petit repàs sobre com han evolucionat i en quin moment va aparèixer cadascun. Veurem com, d’entrada, no hi havia cap eina disponible per escriure ni tan sols els tests més bàsics, de manera que ha estat la comunitat de programadors qui va proposar les primeres solucions a aquesta total falta de recursos. Seguirem cronològicament l’aparició de les principals versions del sistema operatiu (anomenat iPhone OS en el seu inici, fins a la versió 4.0, quan va passar a dir-se iOS), juntament amb el llançament de nous dispositius com l’iPod touch i l’iPad.
Testing on iPhone 34 iOS 1.0 29 de juny del 2007 És la primera versió del sistema operatiu amb la qual l’iPhone surt a la venta. Les eines disponibles per a la seva programació eren limitades, i en el cas del testing, inexistents. 13 de setembre del 2007 Es comercialitza el primer iPod touch, sense canvis rellevants en el sistema operatiu. iOS 2.0 11 de juliol del 2008 Apareix la primera revisió del dispositiu iPhone, l’iPhone 3G. Tot i disposar d’una versió més avançada del sistema operatiu, continuem sense frameworks per a facilitar l’escriptura de tests. 9 de setembre del 2008 També s’actualitza l’iPod touch, i apareix l’iPod touch 2nd generation. iOS 2.2 21 de novembre del 2008 Primer versió del sistema operatiu que inclou una eina per a l’escriptura de tests unitaris, basada en el framework JUnit de Java: OCUnit. Al voltant d’aquestes dates Google també posa a disposició un framework similar a OCUnit, anomenat Google Toolbox for Mac, que també permet escriure tests unitaris. iOS 2.2.1 27 de gener del 2009 Durant el mes de febrer apareix el primer framework que permet escriure tests funcionals, també open source: UISpec. Ha estat desenvolupat seguint l’exemple de RSpec, un conegut framework de Ruby. iOS 3.0 17 de juny del 2009 S’actualitza l’iPhone 3G i passa a ser iPhone 3GS. Tot i ser una important revisió del sistema operatiu, no hi ha novetats en els frameworks de testing. De fet, molta gent se sent més còmode amb les eines de Google, que estan sent adoptades per la majoria d’interessats en escriure tests, que amb OCUnit. 9 de setembre del 2009 Tenim un nou iPod touch: l’iPod touch de tercera generació. iOS 3.1.3 2 de febrer del 2010 Sorgeix un nou framework al voltant d’aquestes dates, FoneMonkey, que permet escriure els tests funcionals d'una manera molt més còmode que UISpec, tot enregistrant les accions que realitza el programador directament sobre el dispositiu. 1 de març del 2010 Iniciem aquest PFC amb Mobivery.
Testing on iPhone 35 iOS 3.2 – només iPad 3 d’abril del 2010 Apareix l’iPad i es revoluciona el desenvolupament d’aplicacions. S’obre un mercat diferent que requereix redissenyar les aplicacions per tal d’adaptar-les a una pantalla més gran, amb unes capacitats diferents. Comprovem com amb aquesta nova versió del sistema operatiu hi ha diversos problemes per fer funcionar el framework FoneMonkey, que ja havíem adoptat per a realitzar els tests funcionals a iPhone i iPod touch. Els creadors de FoneMonkey no tenien intenció de corregir aquests problemes a curt termini, tot i que en els fòrums hi havia força gent interessada. Així que, gràcies a que el framework és open source, amb l’ajuda de l’usuari dels fòrums ericfode vam treure una modificació10 que resolia la majoria de problemes, i la vam fer disponible a tots els usuaris. iOS 4.0 – només iPhone i iPod touch 21 de juny del 2010 És una de les actualitzacions més significatives de totes les llançades. Inclou multitasking entre altres moltes novetats. La nova versió del sistema operatiu apareix amb un nou iPhone, l’iPhone 4. Apple introdueix per fi un sistema per a escriure tests funcionals en Javascript, anomenat UIAutomation. FoneMonkey no és compatible amb aquesta versió almenys d’entrada, ja que els esdeveniments que enregistrava han canviat completament. 8 de setembre del 2010 De nou, s’actualitza l’iPod touch 4th generation. iOS 4.0.2 – només iPhone i iPod touch 11 d’agost del 2010 Apareix un nou framework open source per a escriure tests unitaris: gh-unit. iOS 4.1 – només iPhone i iPod touch 8 de setembre del 2010 Actualització de FoneMonkey, fent-lo compatible amb les versions d’iOS 4.0 i 3.2. En aquest moment el Conveni amb Mobivery ja ha finalitzat però mantenim la comunicació per tal d’avançar el projecte. iOS 4.2.1 –iPhone, iPad i iPod touch 24 de novembre del 2010 Última revisió del sistema operatiu. Porta les novetats de la versió 4.0 a l’iPad. 10 Discussió amb ericfode i arxius per a la versió iPad. Malauradament no vam poder fer un branch en el propi repositori de FoneMonkey.
Testing on iPhone 36 7.2. Anàlisi dels frameworks Les àrees del testing que vam decidir cobrir primer van ser els tests unitaris i els funcionals. Si aconseguim passar de no tenir tests a escriure uns quants d’aquests tipus en cada projecte seria un pas molt important. També ens va semblar interessant veure quines opcions teníem per tal de que els tests escrits s’executessin remotament, i alhora controléssim els seus resultats. Així doncs, vam proposar instal·lar un servidor d’integració continua que ens permetés conèixer l’estat dels diferents projectes a través dels seus tests. Així doncs, repassem quines han estat les eines escollides per dur a terme les tasques esmentades. Tests unitaris Per escriure tests unitaris hem analitzat les dues eines més populars, i realment les úniques completes i funcionals. OCUnit És el framework integrat en l’entorn de desenvolupament d’Apple. Similar a JUnit per Java, proporciona un conjunt de funcions anomenats “asserts” que permeten testejar si el valor de les variables que estem comprovant és l’esperat o no. Els seus punts a favor són la facilitat d’integrar en el nostre projecte ja que està disponible directament des de l’entorn de desenvolupament, i és d’esperar que s’actualitzi per ser compatible amb les futures versions de l’iOS i el SDK. En contra té alguns detalls de la seva configuració un cop integrat en el projecte, estranyament complexa tenint en compte que és la solució “estàndard”, i sobretot, que no podem fer debugging dels tests d’una manera convencional. Google Toolbox for Mac Un dels frameworks més estesos en el món Mac. Es basa en la mateixa idea d’”asserts” que OCUnit, però té més varietat d’instruccions. Tot i ser igual en línies generals que OCUnit, té alguns punts a favor importants: la configuració un cop integrat en el projecte és molt més simple, i el debugging dels tests es realitza de la mateixa manera que la resta del codi. Això facilita molt la tasca d’escriure els tests, encara que sigui un framework una mica més complicat d’integrar i actualitzar, ja que cal fer-ho a través de SVN o del seu projecte a code.google.com. A més a més, en diverses ocasions hem pogut comprovar com aquesta eina de Google s’adaptava abans als canvis d’Apple que el propi OCUnit, la qual cosa ens ha fet decantar definitivament per Google Toolbox for Mac.
Testing on iPhone 37 Google Toolbox for Mac Després de valorar les diverses opcions, ens hem decidit per adoptar l’eina creada per Google. Ha pesat més que sigui una biblioteca open-source i el fet de ser molt més intuïtiva a l’hora de configurar i executar els tests. Passem doncs, a veure quina és l’estructura bàsica que cal seguir per implementar tests unitaris amb GTM. El primer que cal crear és un nou Target en el qual inclourem les clases que volem testejar. Al Xcode, un target és una configuració diferent del mateix projecte, on només incloem algunes classes o recursos. Per exemple, si volem compilar el nostre projecte tant per iPhone com per iPad segurament dissenyarem vistes diferents per a cada dispositiu per aprofitar l’espai de que disposem, i una manera d’aconseguir-ho seria creant un target per iPhone amb les seves vistes i un altre target per iPad. Similarment, crearem un target que sigui exclusivament per a executar els tests unitaris. Un cop creat aquest target per als tests, li hem d’afegir aquelles classes que volem testejar. Típicament afegiríem totes les classes del projecte, però en determinats casos ens podria interessar crear diferents targets pel testing i afegir en cadascun només les classes del model de dades, del servidor, de la capa de persistència, etc. Ara cal crear tants arxius del tipus “Objective-c test case class” com classes volem testejar, que no són res més que una classe que ja hereta d’un objecte amb unes propietats determinades per tal de poder executar els tests. Per conveni, acostumem a anomenar a les classes de tests amb el mateix nom que les classes originals acabats en Test (per exemple, si volem escriure tests per a la classe NSArray, anomenarem NSArrayTest a la classes que contindrà els tests). Amb les classes de test ja configurades, només ens queda escriure els tests pròpiament. Cada test s’escriu com una acció, i el seu nom ha de començar necessàriament per “test”, per exemple “testArraySize”. Això és així perquè el procés que s’encarrega d’executar automàticament tots els tests cerca i executa les accions que comencen per la paraula “test” i ignora la resta. Dins d’una classe de test també podem tenir funcions comuns que no realitzin cap test, i que simplement ens serveixen com a funcions auxiliars (pot haver-hi codi que es repeteixi en més d’un test i ens interessi encapsular, funcions que realitzin càlculs, etc.). Per escriure els tests disposem de diversos mètodes que ens dóna la biblioteca de Google Toolbox for Mac. Els podem dividir en dues categories: els de configuració i els asserts. Mètodes de configuració Disposem de quatre mètodes de configuració; dos de classe i dos d’instància. De fet, els de classe i els d’instància s’anomenen igual perquè realitzen una tasca molt similar. Mètodes de classe: + (void) setUp: s’executa abans de començar qualsevol test de la seva classe. És útil per a inicialitzar objectes que utilitzaran diversos tests, com ara bases de dades de proves, etc.
Testing on iPhone 38 + (void) tearDown: s’executa un cop han finalitzat tots els tests de la classe, i s’acostuma a utilitzar per alliberar els recursos utilitzats. Mètodes d’instància: - (void) setUp: s’executa abans de començar cadascun dels tests. Serveix principalment per assegurar-nos que tots els tests tenen les mateixes condicions inicials i que el resultat d’un test no estigui afectant al comportament del següent. Per exemple, podríem crear una base de dades amb tres registres durant el setUp per tenir la certesa que si en un test hem esborrat un registre, el següent test disposarà novament de la base de dades amb els tres registres. - (void) tearDown: de manera equivalent al setUp, aquest mètode s’executa sempre que finalitza un test. Asserts Els “asserts” són els mètodes que ens permeten comparar els resultats obtinguts durant l’execució del test, amb els valors esperats. Típicament un test unitari es dissenya seguint aquests passos: 1. Primer decidim quin mètode volem testejar 2. Desprès pensem quins són els casos que val la pena provar, ja sigui perquè són casos-límit per a la funció o bé perquè són rellevants pel sistema. 3. Finalment calculem d’alguna manera els resultats correctes pels casos que volem provar, i que ens serviran per comparar amb els resultats obtinguts durant la execució del test. Per tant, bàsicament els mètodes que ens ofereix la biblioteca serveixen per comparar valors o comprovar si es compleix una determinada condició. Un assert té la següent estructura: STAssertTipus(exp, descripció) - “Tipus” pot ser qualsevol mena de comparació de la col·lecció de mètodes que ens ofereix la biblioteca. - “exp” és una expressió que serveix per a realitzar la comparació entre el valor esperat i el valor calculat durant el test. - “descripció” és el missatge que volem que s’escrigui per pantalla en cas que el test falli. Anem a veure en detall quins són els diferents asserts que ens ofereix GTM: STAssertNoErr(a1, descripció) a1: un objecte Falla quan a1 és un error del sistema operatiu STAssertErr(a1, a2, descripció) a1: error del sistema operatiu a2: error del sistema operatiu Falla quan a1 és diferent d’a2
Testing on iPhone 39 STAssertNotNULL(a1, descripció) a1: un punter Falla quan a1 és NULL STAssertNULL(a1, descripció) a1: un punter Falla quan a1 no és NULL STAssertNotEquals(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 és igual a a2 STAssertNotEqualObjects(a1, a2, descripció) a1: un objecte a2: un objecte Falla quan l’objecte a1 és igual a a2 STAssertOperation(a1, a2, op, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct op: una operació Falla quan a1 no és ‘op’ a a2. STAssertGreaterThan(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 no és més gran que a2 STAssertGreaterThanOrEqual(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 no és més gran o igual que a2 STAssertLessThan(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 no és més petit que a2 STAssertLessThanOrEqual(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 no és més petit o igual que a2 STAssertEqualStrings(a1, a2, descripció) a1: un string a2: un string Falla quan l’string a1 no és igual que l’string a2. STAssertNotEqualStrings(a1, a2, descripció) a1: un string a2: un string
Testing on iPhone 40 Falla quan l’string a1 és igual que l’string a2. STAssertEqualCStrings(a1, a2, descripció) a1: un string de C a2: un string de C Falla quan l’string a1 no és igual que l’string a2. STAssertNotEqualCStrings(a1, a2, descripció) a1: un string de C a2: un string de C Falla quan l’string a1 és igual que l’string a2. STAssertEqualObjects(a1, a2, descripció) a1: un objecte a2: un objecte Falla quan l’objecte a1 no és igual a l’objecte a2, o bé un dels objectes és nil. STAssertEquals(a1, a2, descripció) a1: un escalar de C (int, float, punter, etc.) o struct a2: un escalar de C (int, float, punter, etc.) o struct Falla quan a1 no és igual a a2. STAssertEqualsWithAccuracy(a1, a2, accuracy, descripció) a1: un escalar de C de tipus float o double a2: un escalar de C de tipus float o double accuracy: la diferència màxima entre a1 i a2 per a ser considerats com iguals Falla quan a1 no és igual a (a2 + accuracy) o a (a2 - accuracy). STFail(descripció) Falla incondicionalment. STAssertNil(a1, descripció) a1: un objecte Falla quan a1 no és nil. STAssertNotNil(a1, descripció) a1: un objecte Falla quan a1 és nil. STAssertTrue(expr, descripció) expr: la expressió booleana a evaluar Falla quan expr s’evalua a fals. STAssertTrueNoThrow(expr, descripció) expr: la expressió booleana a evaluar Falla quan expr s’evalua a fals, i a més a més generarà missatges d’errors si llença alguna excepció. STAssertFalse(expr, descripció) expr: la expressió booleana a evaluar Falla quan expr s’evalua a cert
Testing on iPhone 41 STAssertFalseNoThrow(expr, descripció) expr: la expressió booleana a evaluar Falla quan expr s’evalua a cert, i a més a més generarà missatges d’errors si llença alguna excepció. STAssertThrows(expr, descripció) expr: la expressió a evaluar Falla quan expr no llença una excepció. STAssertThrowsSpecific(expr, specificException, descripció) expr: la expressió a evaluar specificException: el tipus d’excepció que hauria de llençar Falla quan expr no llença una excepció del tipus specificException. STAssertThrowsSpecificNamed(expr, specificException, aName, descripció) expr: la expressió a evaluar specificException: el tipus d’excepció que hauria de llençar aName: el nom de la excepció Falla quan expr no llença una excepció del tipus specificException que s’anomeni aName. STAssertNoThrow(expr, descripció) expr: la expressió a avaluar Falla quan expr llença una excepció. STAssertNoThrowSpecific(expr, specificException, aName, descripció) expr: la expressió a avaluar specificException: el tipus d’excepció que hauria de llençar Falla quan expr llença una excepció del tipus specificException. STAssertNoThrowSpecificNamed(expr, specificException, aName, descripció) expr: la expressió a avaluar specificException: el tipus d’excepció que hauria de llençar aName: el nom de la excepció Falla quan expr llença una excepció del tipus specificException que s’anomeni aName. Instal·lar Google Toolbox for Mac Com que la resta de companys ha de ser capaç d’instal·lar les eines que aquí proposem, hem elaborat un seguit de tutorials o guies per a simplificar aquest procés. En aquest cas, GTM no s’ha d’instal·lar en sí sinó que cal importar certes classes i objectes al nostre projecte. Per aconseguir-ho n’hi ha prou de seguir el manual que hem esmentat unes línies més a munt, però hem volgut detallar alguns passos i fer petites anotacions per tal de que quedi tot clar. Anem doncs a seguir els passos que ens recomanen els creadors de GTM: 1. Hem de crear un nou target en el nostre projecte del tipus "Cocoa Touch Application”. És important donar-li un nom que identifiqui que es tracta d’un target per a realitzar tests ja que visualment serà igual que el target de la nostra aplicació; típicament
Testing on iPhone 48 Should be: s’utilitza per comprovar les propietats booleanes de les vistes, com ara si un camp de text és editable o si una cel·la està seleccionada. [app.textView.should.be editable]; UIQuery Aquests mètodes ens permeten “tocar” els objectes de les vistes: [app.backButton touch]; [app.tableView scrollToBottom]; Però a vegades voldrem tocar elements que no estan clarament identificats dins una vista o que no podrem saber quin és el seu contingut. Per aconseguir-ho UISpec utilitza filtres i cerques per trobar fàcilment el que necessitem, com per exemple actuar sobre tots els elements d’un mateix tipus. [app.tableViewCell.all touch]; UIQuery també ens dóna algunes funcions per tractar els temps d’espera abans d’executar-se algunes accions, ja que potser hi ha operacions lentes que han de finalitzar primer. Però UISpec té diversos inconvenients. El principal és que els tests s’han d’escriure en Objective-c, i no és el llenguatge més pràctic per fer aquest tipus de comprovacions. Llenguatges com Javascript o Ruby permeten treballar amb strings molt més còmodament (ho farem sovint), i la tipificació dèbil ens estalviaria força sucre sintàctic que no necessitem en els tests. Hi un projecte dins UISpec amb la intenció de crear un petit llenguatge d’scripting que ens alliberi d’escriure Objective-c anomenat UIScript, però encara està en fase experimental i serà un llenguatge propi de manera que haurem d’aprendre la seva sintaxis (per simple que sigui). Un altre problema és que UISpec utilitza constantment crides a mètodes privats d’objectes de la biblioteca UIKit per implementar UIExpectation i UIQuery. Això vol dir que la biblioteca no ens ofereix una interfície pública per a poder utilitzar-los, però per la pròpia naturalesa dinàmica d’Objective-c, si coneixem la seva signatura el podrem cridar en temps d’execució tot i que durant la compilació tindrem un warning per a cada crida a un mètode privat. Fins ara, aquests warnings no es poden obviar de cap manera, i ens donarà dos principals problemes: Primer, no podrem utilitzar la opció del compilador “tracta els warnings com si fossin errors”. En alguns projectes pot ser una bona idea activar-ho per assegurar-nos de que tots els warnings estan resolts, ja que encara que alguns poden semblar inofensius, sempre és millor saber que els tenim solucionats. I segon, els warnings de UISpec amagaran els que puguin aparèixer en el codi que estem programant. Fàcilment UISpec ens pot donar més d’un centenar de warnings (quants més tests escrivim, més es generen), i no és fàcil separar-los d’un warning que sorgeix perquè estem cridant a un mètode amb paràmetres incorrectes. UIAutomation
Testing on iPhone 49 UIAutomation és l’aposta d’Apple per a escriure tests funcionals, i es va incloure a l’Xcode com una nova eina en el llançament de l’iOS 4.0. Els tests s’escriuen en Javascript utilitzant una col·lecció d’objectes que ens ofereix per facilitar-nos la navegació entre les vistes de la nostra aplicació. Aquests objectes mapegen el comportament de les classes d’Objective-c que s’usen per a crear els elements visuals: si un botó pertany a la classe UIButton, quan escrivim un test farem servir l’objecte UIAButton per interactuar amb ell. El funcionament general és el següent: Primer obtenim la vista que volem testejar. Ara busquem l’element que necessitem dins de la vista. Un cop tenim l’element, podem preguntar pel valor dels seus atributs o modificar-los si volem simular que l’usuari ha introduït certes dades. Gràcies a l’objecte UIATarget podrem realitzar una gran varietat d’accions com ara tocar la pantalla, fer zoom, drag & drop, etc. UIAutomation és segurament el framework que dóna més facilitats per simular la interacció amb la pantalla tàctil. Però tot i ser la opció oficial i la que en principi ha d’oferir una solució més robusta i actualitzada, no disposa pràcticament de documentació, la qual cosa està fent que passi pràcticament desapercebuda per la majoria dels desenvolupadors. A més a més, la complexitat a l’hora de configurar un projecte per utilitzar UIAutomation també pot estar sent una barrera per estendre el seu ús, ja que les altres opcions disponibles són més fàcils de posar en marxa. UIAutomation és una opció a tenir en compte de cara al futur, perquè és molt possible que properes versions millorin la seva integració amb els projectes i amb una mica de sort, tindrem més documentació i exemples. FoneMonkey FoneMonkey, un altre framework open source, dóna una perspectiva totalment diferent a la manera d’escriure els tests funcionals: la majoria del test es genera automàticament. Un cop integrem aquest framework amb el nostre projecte, podrem executar-lo en mode test i apareixerà una interfície literalment a sobre de la vista de la nostra aplicació. L’acció principal d’aquesta interfície és la de gravar les accions que realitzem sobre la nostre vista; desapareix la interfície de FoneMonkey i interactuem amb la nostra aplicació tal i com ho fem normalment, però tots els botons que premem, dades que introduïm en els camps de text, scrolls per veure la última casella d’una taula, etc. es guarden en un XML que FoneMonkey és capaç de reproduir quan li demanem. Des de la pròpia interfície de FoneMonkey que apareix sobre la nostra aplicació podem editar aquests scripts com si fos un llistat d’accions, de manera que podem afegir temps d’espera entre algunes accions que ho necessitin, canviar l’ordre en que s’executen, modificar el text que hem introduït en algun camp o esborrar les accions que s’hagin gravat però que no ens facin falta.
Testing on iPhone 50 Crear els tests d’aquesta manera és tan simple que ho pot fer qualsevol membre de l’equip (o fins i tot personal extern com ara beta testers), ja que ni tan sols cal saber programar o conèixer el codi del projecte en qüestió. Se’ns dubte aquest és el gran avantatge respecte a la resta d’eines analitzades. Navegar programàticament per les diferents vistes fins a trobar el botó que volem prémer no és trivial, i ens obliga a entendre com està construïda la jerarquia de pantalles. Evidentment també té inconvenients. Depenent del tipus d’objecte amb el que estem tractant pot ser que la nostra interacció no es gravi correctament, i en general, quant més personalitzat sigui el component sobre el qual volem enregistrar accions, menys acurat serà l’script que es generi. Però també hi ha comandes que podem introduir manualment a l’script per tal d’obligar a que faci certes accions; si automàticament no detecta que hem premut un element, podem afegir una comanda del tipus “prem en les coordenades de la pantalla (23, 55)” i normalment ens solucionarà el problema. Un altre problema aparent és com podrem executar els tests automàticament en un servidor d’integració continua, si es creen dins d’una aplicació concreta. Doncs és força fàcil extreure els scripts si seguim una convenció en el nom, ja sigui afegint a tots els noms dels tests sempre el mateix prefix o extensió, i després els copiem de la carpeta del simulador a on tinguem el codi del projecte (els tests que vulguem executar automàticament s’han d’escriure pensant en el simulador, ja que no tindrem cap dispositiu connectat permanentment en el servidor on s’executaran). Després veurem en més detall com hem solucionat aquests inconvenients, perquè tot i no ser una solució perfecte, és absolutament més còmode treballar amb FoneMonkey que amb UISpec o UIAutomation. FoneMonkey Per explicar el seu funcionament, el millor serà veure-ho en imatges. Començarem per les funcionalitats de la interfície principal de FoneMonkey.
Testing on iPhone 51 La primera imatge mostra la pantalla principal de la nostra aplicació, mentre que a la segona ja han aparegut els botons de FoneMonkey que ens permeten fer les tasques principals. D’esquerra a dreta, aquestes són les accions que podem realitzar: 1. El primer botó ens permet amagar la interfície per poder navegar per la nostra aplicació com ho faríem normalment, sense que es gravi res. 2. Si premem la claqueta, la interfície desapareix i s’enregistraran totes les accions que realitzem sobre la aplicació. Si durant uns quants segons no fem cap acció, la gravació s’aturarà automàticament i tornarà a aparèixer la interfície. 3. El “play” fa que s’executi el test actual, que pot ser el que acabem de gravar o bé algun que ja teníem guardat. 4. Mostra una taula amb totes les accions que s’han guardat. Des d’aquí podem afegir manualment altres accions, esborrar-les o editar-les. També podrem guardar el test i carregar-los. Analitzem en més detall com és aquesta última pantalla:
Testing on iPhone 52 La vista apareix quan premem el botó de la interfície inferior amb forma de fletxa (el punt 4 del llistat anterior). La taula mostra per cada cel·la una de les accions que s’han gravat. És molt fàcil entendre el test només veient aquesta taula: La icona de l’esquerra mostra el tipus d’acció que s’ha realitzat, com ara una fletxa verda per indicar que hem premut la pantalla, o unes fletxes blaves perquè hem fet un scroll. El text en negreta senyala quin tipus d’objecte ha rebut l’acció, com ara un Button o la TableView, seguit del seu identificador. Aquest identificador pot ser un string o un enter depenent de les propietats que haguem configurat a l’objecte tocat. Els nombres de la part inferior de la cel·la són els paràmetres de l’acció, que acostumen a ser les coordenades de la pantalla on hem executat l’acció. Però cada tipus té els seus paràmetres, per exemple a la acció “Wait” li diem els mil·lisegons que s’esperarà fins a executar la següent acció. El botó blau de la dreta ens permet editar tots els paràmetres d’una acció. Veiem, d’esquerra a dreta, quines són les tasques que ens permeten fer els botons de la barra superior:
Testing on iPhone 53 1. El botó amb forma de carpeta serveix per guardar el test que hem gravat. Tots els tests que creem s’han de guardar per poder reproduir-los la pròxima vegada que obrim l’aplicació. 2. El següent botó ens permet administrar els tests guardats: podem carregar-ne un, esborrar-lo o crear un de nou. 3. La icona amb forma de “+” activa el mode d’edició de la taula i ens permet afegir noves accions manualment i moure-les amunt o avall en la taula (el test executa les accions seqüencialment, de dalt a avall). 4. I la última icona activa el mode d’edició per eliminar accions. A la part inferior d’aquesta vista trobem un slider. Serveix per configurar els segons que passen des que deixem d’interactuar amb la pantalla tàctil mentre gravem el test, fins que torna aparèixer la interfície de FoneMonkey. Quan comencem a gravar la interfície desapareix i la única manera de fer que es mostri és no tocar la pantalla durant el temps indicat en aquesta opció. Llistat d’accions Com ja hem vist, la majoria d’accions en un test estan relacionats amb els esdeveniments generats per la pantalla tàctil (prémer en unes coordenades, fer scroll, etc.) i FoneMonkey els detecta automàticament. Però en determinats casos pot no ser suficient. D’aquí que el framework ens ofereixi altres accions que hem d’afegir manualment a l’script, ja siguin per obligar al test a que s’esperi uns quants segons o bé per comprovar que un element de la vista compleix unes propietats. Per cada acció anem a descriure breument el seu funcionament i donarem un exemple amb els paràmetres que cal utilitzar per afegir-lo al nostre test des de la interfície de FoneMonkey. Touch: simula que l’usuari ha tocat un objecte de la pantalla o bé unes coordenades. Command: Touch Class Name: UIButton (la classe de l’objecte sobre el que volem executar l’acció) Monkey ID: 23 (identificador de l’objecte que rebrà l’acció, ha de ser únic en la vista) Arguments: 46 (coordenada x, opcional) 69 (coordenada y, obligatòria si especifiquem la x) 2 (nombre de vegades que es toca la pantalla, útil per simular el doble tap) Verify: comprova si la propietat d’un objecte té el valor esperat. Command: Verify Class Name: UITextField Monkey ID: 12 Arguments: text (nom de la propietat) “Hello world!” (valor esperat de la propietat) WaitFor: atura l’execució del test fins que apareix l’objecte esperat, o bé s’arriba a un timeout. Command: WaitFor Class Name: UITableViewCell Monkey ID: 78 Arguments:5000 (timeout, en mil·lisegons) accessoryType (nom de la propietat) UITableViewCellAccessoryCheckmark (valor esperat de la propietat)
Testing on iPhone 54 Pause: introdueix un temps d’espera, en mil·lisegons, entre l’execució de dues accions. Command: Pause Arguments: 2500 Shake: simula que l’usuari agita el dispositiu. Command: Shake Scroll: fa l’scroll necessari per a que les coordenades passades com a paràmetres quedin a la cantonada superior esquerra de la pantalla. Command: Scroll Class Name: UIScrollView Monkey ID: 3 Arguments: 10 (coordenada x) 60 (coordenada y) VScroll: mou una taula a la fila i secció especificats. Command: VScroll Class Name: UITableView Monkey ID: 5 Arguments: 1 (fila) 2 (secció de la taula, és opcional) Slide: mou un slider al valor que li donem per paràmetre. Command: Slide Class Name: UISlider Monkey ID: 6 Arguments: 3.1415 Switch: canvia l’estat d’un UISwitch d’encès a apagat i viceversa. Command: Switch Class Name: UISwitch Monkey ID: 23 InputText: introdueix un string en un camp de text. Command: InputText Class Name: UITextField Monkey ID: 8 Arguments: “Hello world!”
Testing on iPhone 55 Move: arrossega un objecte per les diferents coordenades que li proporcionem (drag & drop) Command: Move Class Name: UIView Monkey ID: 33 Arguments: 0, 1, 1, 2, 3, 5, 8, 13, 21 (llistat de coordenades x per on ha de passar) 0, 1, 2, 4, 7, 6, 7, 10, 15 (i les corresponents y) Executar una col·lecció de tests Crear un test ja hem vist que és molt senzill, només cal gravar o afegir manualment les accions que volem que s’executin cada vegada que llencem el test. Però un dels aspectes més interessants de qualsevol test és poder-lo executar tantes vegades com sigui possible i amb el mínim esforç per part dels programadors, i idealment s’haurien d’executar automàticament al mateix temps que els tests unitaris utilitzant el servidor d’integració continua. Això planteja dos problemes: D’una banda els tests s’escriuen, es guarden i s’executen en un dispositiu. Com podem treure’ls del dispositiu i fer-ne còpies de seguretat, provar-los en altres dispositius, etc? Com executar-los automàticament en un servidor remot que no té cap iPhone, iPad, etc. connectat, i que segurament tampoc disposa de monitor per executar-los en el simulador de l’Xcode. Ambdós problemes tenen solució, però impliquen seguir certes convencions que ha de seguir tothom qui escrigui tests. El problema de com aconseguir els tests escrits en un dispositiu té una solució molt simple: els tests s’han d’escriure en el simulador i no un dispositiu real. Això facilita la còpia dels tests a la carpeta que creguem convenient, ja que el simulador és una aplicació del nostre ordinador i tenim ple accés als arxius que genera, i a més a més en el servidor d’integració continua també s’executaran sobre el simulador. Ja hem avançat una mica la solució al segon problema: en el servidor d’integració continua executarem la nostra aplicació en el simulador, però desactivant la interfície gràfica. D’aquesta manera ens assegurem que podrem executar els tests en qualsevol ordinador, encara que no disposi de monitor. Però tot i poder copiar i executar els tests creats en un servidor, necessitem dir-li d’alguna manera quins són els que cal executar. Això ho aconseguirem creant una classe, similar a la dels tests unitaris, amb un llistat de funcions que seran les que finalment executin cadascun dels tests funcionals escrits amb FoneMonkey. Com instal·lar FoneMonkey Integrar FoneMonkey en el nostre projecte és similar a com hem instal·lat Google Toolbox for Mac, i en general a qualsevol biblioteca externa. Anem a veure-ho pas a pas.
Testing on iPhone 56 1. Primer descarregarem la biblioteca directament de la web oficial: http://www.gorillalogic.com/fonemonkey/download. Cal registrar-se (gratuïtament) a la web per poder accedir als arxius. 2. En el nostre projecte de l'Xcode, dupliquem el target actual i li canviem el nom a NomProjecteUITests per exemple. 3. Afegim la carpeta que s'ha descomprimit al projecte de l'Xcode. Quan surti la finestra d'alerta per confirmar que afegim els arxius al projecte, en la secció Add to Targets cal desmarcar NomProjecte i marcar NomProjecteUITests. Per a que els arxius es copiïn al nostre projecte, seleccionem la opció "Recursively create groups for any added folders". 4. Ara fem "Get Info" al nou target i a la pestanya "General" esborrem de Linked Libraries: libFoneMonkey.a libFoneMonkeyOCUnit.a I afegim els següents frameworks si no existeixen: CoreGraphics.framework QuartzCore.framework 5. A la pestanya "Build", a Other Linker Flags afegim el següent: -ObjC -lFoneMonkey - LFoneMonkey/lib -all_load 6. Ja ho tenim tot configurat i podem executar el nou target. Un cop hagi arrancat el programa, passats uns 10 segons ha d’aparèixer la interfície de FoneMonkey sobre la nostra pantalla principal (botons vermells a la part inferior). Execució automàtica dels tests funcionals Com hem dit abans, el primer que hem d’aconseguir és extreure els tests que hem gravat a la nostra aplicació. Tota aplicació que executem en el simulador d’iOS crea una carpeta dins la qual emmagatzema la informació de l’usuari. Per exemple si l’usuari pot modificar la configuració d’una aplicació, voldrem guardar els canvis realitzats de manera que la propera vegada que obri l’aplicació puguem restaurar els valors que va posar l’última vegada. Podríem guardar aquests canvis en un XML i escriure’l en l’espai de disc que el sistema operatiu reserva per cadascuna de les aplicacions, i llegir-lo al iniciar l’aplicació. FoneMonkey fa això mateix per guardar els tests: els escriu en l’espai del disc reservat per a la nostra aplicació. En el cas del simulador, això és una carpeta que es troba a /Users/<nom_usuari>/Library/ApplicationSupport/iPhone Simulator/3.1/Applications/<cadena_ alfanumèrica>/ a la qual hi podem accedir amb drets de lectura i escriptura. La opció més simple per obtenir els tests d’una manera que
Testing on iPhone 57 posteriorment es puguin executar automàticament, és copiar-los a mà a la carpeta del nostre projecte. Evidentment seria més còmode copiar-ho amb un script, però això planteja alguns problemes que no tenen una solució satisfactòria en tots els casos. Les rutes d’arxius, tant de la aplicació en el simulador com la del codi del projecte, depenen de cada ordinador i usuari (la cadena alfanumèrica es genera aleatòriament cada vegada que instal·lem l’aplicació al simulador). Per tant la ruta on es troba instal·lada l’aplicació només la podem conèixer en temps d’execució, i la ruta on tenim el nostre projecte només en temps de compilació: caldria doncs guardar en un arxiu la ruta del projecte i llegir-lo en l’aplicació per saber a on hem de guardar els tests. Tot i que seguir aquestes passes pugui semblar massa enrevessat en realitat el problema més greu és la gestió del SVN; cada vegada estarem sobreescrivint uns tests que algú pot haver modificat i serà una font de conflictes a l’hora de fer un commit. I si per algun motiu dos tests diferents tenen el mateix nom, no ens adonarem fins que algú trobi a faltar un dels tests. Així doncs, aquesta serà la única passa que valgui la pena fer manualment cada vegada que generem nous tests. Un cop ens trobem a la carpeta del simulador on està instal·lada la nostra aplicació, trobarem els tests dins de Documents/. Els podem copiar a qualsevol lloc del nostre projecte, però per seguir aquesta guia suposarem que ho fem a Resources/FMTests. Ara que ja tenim els tests en el nostre projecte, crearem un nou target que s’encarregui d’executar-los. 1. Afegim la biblioteca SenTestingKit al target on hem configurat FoneMonkey. Ho podem aconseguir fent doble clic sobre el target i a la pestanya “General”, afegim la biblioteca a la secció “Linked libraries” i seleccionem “Add Other”. SenTestingKit es troba normalment a /Developer/Library/Frameworks. També cal afegir el flag “-lFoneMonkeyOCUnit” a la secció “Other Linker Flags” de la pestanya “Build”. 2. Creem una classe per a que executi els tests. En aquest exemple es diu “SampleTest” i executa dos dels tests que hem copiat al nostre projecte: Test_AjudaSamarreta i Test_Calendari. #import <SenTestingKit/SenTestingKit.h> #import <UIKit/UIKit.h> #import "FoneMonkeyAPI.h" #define PATH_FROM_DOCUMENTS_TO_BUNDLE @"../FCBMobile.app/" //FoneMonkey busca els arxius a /Documents, i nosaltres volem que els llegeixi del bundle @interface SampleTest : SenTestCase @end @implementation SampleTest // Funció auxiliar per simplificar la resta de crides
Testing on iPhone 64 En l’informe que es genera amb els resultats de la compilació (dia i hora, temps que ha trigat, si hi ha hagut alguna incidència, etc.) també podrem veure quina màquina s’ha encarregat de fer la tasca. Si per algun motiu un esclau deixa d’estar disponible (tanquem el client, perd la connexió amb la xarxa, etc.), Hudson l’obviarà i intentarà executar els projectes en altres esclaus o en el propi servidor. Aquesta és una molt bona eina per aprofitar els ordinadors dels programadors i alleugerir la càrrega del servidor. Si els configurem els per actuar com a esclaus aprofitarem la capacitat de càlcul d’aquests ordinadors d’una manera quasi transparent per a qui està treballant amb ells. Taulell (o Dash board) Hudson ens ofereix una pantalla molt compacte on podem veure, d’un cop d’ull, l’estat de tots els projectes: quan s’han executat per última vegada, si va anar bé o malament, quina ha estat la tendència de les últimes execucions (si normalment ha fallat, últimament han anat bé, etc.) amb unes icones de “Sol”, “Núvols” o “Pluja”, links per a executar manualment algun projecte, etc. També es poden definir taulells propis on només apareixen determinats projectes. Per exemple, si tenim tres projectes amb dependències entre ells ens pot ajudar tenir una vista particular per a ells de manera que puguem veure ràpidament si algun no ha passat els tests, o per obtenir informació sobre aquests projectes més fàcilment. Rols d’usuari Es poden definir els privilegis de que disposa cada usuari per cadascun dels projectes. En general, tots els usuaris poden veure la informació de tots els projectes, però només alguns usuaris poden realitzar execucions manuals de cada projecte. No és una funcionalitat que necessitem especialment per al nostre entorn de treball, però pot ser útil en determinats casos. Plugins Com ja hem comentat, hi ha una gran quantitat de plugins creats per terceres parts, i nosaltres mateixos podríem escriure’n un en qualsevol moment. N’hi ha de tot tipus, des dels que permeten utilitzar altres repositoris de dades (per defecte només té Subversion, però podem afegir Git, Mercurial...) o generen gràfiques amb les diverses dades de què disposa Hudson, fins als que modifiquen l’aparença del taulell o fan que Hudson parli (Hudson Speaks! Plugin). Actualitzacions Hudson té una pàgina dedicada a la seva configuració general i a la gestió dels plugins instal·lats. Si Hudson o algun dels seus plugins s’actualitza, podrem actualitzar-los prement un botó i reiniciant el servidor. Les “minor releases” de Hudson són relativament comunes, així que és útil poder actualitzar el servidor d’una manera tan senzilla.
Testing on iPhone 65 Altres utilitats Evidentment, hi ha moltes altres tasques que Hudson ens ofereix. Podem fer que s’executin els projectes a través d’un script, o donar-li aquestes ordres a través d’un xat si hem configurat el plugin de Jabber. També disposem d’informes per cada execució que podem millorar depenent dels productes que generi el nostre projecte. Per exemple, si tenim un projecte en Java integrat amb Sonar, una eina per mesurar la qualitat del software basat en diverses mètriques, gràcies al “Sonar plugin” podrem veure tota la informació que ens proporciona aquesta eina des de Hudson. Existeixen altres plugins per a mostrar el code coverage generat amb eines com Emma o Cobertura, però malauradament a Objective-c no disposem d’aquests frameworks més avançats i no podem aprofitar aquesta interessant informació pel nostre servidor d’integració continua. En definitiva, aquestes són les principals característiques que utilitzem de Hudson. Execució automàtica al fer un commit La configuració més interessant que hem realitzat ha estat la execució dels projectes a Hudson cada vegada que s’actualitza el repositori de codi, en el nostre cas Subversion. La opció simple és que Hudson pregunti cada cert temps si hi ha hagut canvis en el repositori, però això ens obliga a generar cert tràfic en projectes que ja estan pràcticament tancats i s’actualitzen molt poques vegades, i alhora ens impedeix saber exactament quina de les actualitzacions ha fet fallar un test. Per aconseguir-ho, ens hem aprofitat d’un event que Subversion genera cada vegada que algú actualitza el repositori (és a dir, fa un commit): el post-commit hook. Quan es llença aquest event, executem un script propi que accedeix a una url de Hudson i fa que s’executi el projecte identificat per un paràmetre. Si es fa un commit mentre s’està executant el projecte de Hudson, es prepara una nova execució i es posa a la cua. En quant finalitzi la primera execució, començarà la següent. Això en general és positiu, però a vegades pot sobrecarregar innecessàriament el servidor amb execucions que s’acumulen perquè s’estan fent molts commits amb petites modificacions. Per evitar-ho, Hudson disposa d’un mecanisme d’execució retardada. A cada projecte li podem configurar un temps d’espera entre el moment en el qual algú demana que s’executi el projecte, i quan efectivament es comença la seva execució. Per exemple si donem 10 minuts d’espera, quan es demana al projecte que s’executi aquest començarà un compte enrere de 10 minuts, i en finalitzar, començarà l’actualització del codi del projecte, la compilació, etc. Si durant aquest interval de temps algú demana que es torni a executar, s’ignorarà. Així evitem que s’acumulin moltes execucions d’un projecte si els programadors fan commits molt sovint. Com instal·lar Hudson Com amb els altres frameworks, hem elaborat un manual per tal de poder instal·lar tot el que necessitem: Hudson a com a servidor d’integració continua, Site Monitor per a realitzar els tests de disponibilitat i els scripts per a executar els projectes de Hudson al fer un commit.
Testing on iPhone 66 Instal·lar Hudson Web Oficial: http://hudson-ci.org/ La instal·lació és molt simple, només hem de descarregar la última versió de la web oficial i executar-lo fent java –jar Hudswon.war. En el nostre cas hem preferit instal·lar-lo a Tomcat. Cal recordar que per compilar els projectes per iPhone/iPad necessitem tenir instal·lat, en el mateix servidor on està Hudson, l'eina xcodebuild i l'iPhoneSimulator per tal de poder executar els tests funcionals. Vull agrair molt especialment l’esforç (i mals de cap!) que hi va dedicar el Kilian, l’administrador de sistemes de Mobivery, per ajudar-me a instal·lar i configurar en els servidors de l’empresa gran part de les funcionalitats de Hudson que ara anem a comentar. Seguretat El primer que farem serà configurar la seguretat de Hudson per a que ens demani l'autenticació que hem afegit al Tomcat. Administrar-hudson -> Configurar el sistema 1. Marcar "Activar seguridad" 2. Seguridad: Delegar seguridad al contenedor de servlets 3. Autorización: Modo 'legacy' Guardar Builders Ara instal·larem Maven, JDK i Ant. Són les eines essencials per a compilar i construir els nostres projectes. Administrar-hudson -> Configurar el sistema 1. Añadir Maven Maven nombre: AutoMavenFromApache Instalar automáticamente: Sí Instalar desde Apache: 2.2.1 Al final de la pàgina de configuració hem d'afegir el valor per a la variable MAVEN_OPTS Valor para la variable global MAVEN_OPTS: -Djava.awt.headless=true 2. Añadir JDK JDK nombre: AutoJDKFromJava Instalar automáticamente: Sí Instalar desde java.sun.com: 6 update 20 3. Añadir Ant
Testing on iPhone 67 Ant nombre: AutoAntFromApache Instalar automáticamente: Sí Instalar desde Apache: 1.8.1 Guardar * Els builders es descarregaran quan hi hagi un projecte que els necessiti, així que de moment no ha instal·lat cap. Un projecte Maven 2 d’exemple Hudson anomena "Jobs" (o "tareas") als projectes, així que començarem amb un job d'exemple per a un projecte Maven 2. Crear nueva tarea 1. Nombre del proyecto: joketranslator-maven2 Crear un proyecto maven2 2. Configurar el origen del código fuente: Subversion URL del repositorio: https://svn.mmip.es/p/moclam/joketranslator/trunk Ara es queixarà perquè no pot accedir al subversion ja que no té les credencials. En aquest mateix missatge tenim la opció "Introducir credenciales": cliquem i en la nova finestra seleccionem "Autenticación basada en usuario/contraseña" i podrem posar l'usuari i el password del svn. 3. Goles y opciones: clean install La resta d'opcions les deixem per defecte. Guardar Ara ja podem executar el projecte: Ejecutar ahora Un projecte d’exemple amb Ant Crear nueva tarea 1. Nombre del proyecto: bearsutra-ant Crear un proyecto de estilo libre 2. Configurar el origen del código fuente: Subversion URL del repositorio: https://svn.mmip.es/workspace/projects/Kamasutra/trunk o Ara es queixarà perquè no pot accedir al subversion ja que no té les credencials. En aquest mateix missatge tenim la opció "Introducir
Testing on iPhone 68 credenciales": cliquem i en la nova finestra seleccionem "Autenticación basada en usuario/contraseña" i podrem posar l'usuari i el password del svn. 3. Ejecutar -> Añadir un nuevo paso: Ejecutar Ant 4. Version de Ant: AutoAntFromApache Hem de seleccionar la versió que vam afegir en el pas Configuració -> Builders -> 3. Añadir Ant Destinos: init compile La resta d'opcions les deixem per defecte. Guardar Ara ja podem executar el projecte: Ejecutar ahora Afegir un node Tutorial: http://wiki.hudson-ci.org/display/HUDSON/Distributed+builds El servidor està instal·lat en una màquina Linux per respectar la organització dels serveis a l'empresa, però per compilar els projectes d'iPhone calen unes eines només disponibles per a Mac OS. Així doncs aprofitarem la capacitat de Hudson per compilar projectes remotament en altres màquines. Per crear un node cal anar a: Administrar Hudson -> Administrar nodos -> Nuevo nodo, i li donem un nom. 1. Al projecte cal assignar-li el node que volem utilitzar des de la seva pantalla de configuració. 2. Quan executem el projecte, enviarà el codi font al node i començarà a instal·lar automàticament les eines necessàries (bàsicament els builders que hem configurat al servidor). Problemes que hem trobat: JAVA_HOME is not defined correctly. We cannot execute /Users/fnavarro/hudson-slave/tools/AutoJDKFromJava/bin/java A la configuració del node hem de seleccionar "Variables de entorno" i afegir: Nombre: JAVA_HOME Valor: /System/Library/Frameworks/JavaVM.framework/Home/ Reiniciar el slave Si en aquest node es compilaran projectes per iPhone, caldrà afegir també un nou job a Hudson que s'encarregui d'actualitzar els builders propis a hudson/tools/builders. Per fer-ho, definim el següent job:
Testing on iPhone 69 1. "Nombre": own-builders-NODENAME_node (per exemple own-builders-dodona_node) 2. "Asociar este proyecto con el nodo": el node que acabem de crear 3. Subversion, URL del repositorio: https://svn.mmip.es/workspace/libraries/builder/trunk 4. "Ejecutar después de que otro proyecto se haya ejecutado": own-builders-master_node Comprovar APIs privades Apple rebutja aplicacions que utilitzin mètodes que no estan documentats. Tot i semblar molt obvi quins mètodes són els que es poden utilitzar, a vegades hi ha biblioteques creades per tercers que poden estar fent crides a aquests mètodes. El que acostuma a passar és que en el moment en que es van crear les biblioteques, Apple no es fixava tant en aquest aspecte i podien superar la fase de revisió. Per evitar ensurts, vam crear un petit script que cercava els noms de totes les funcions que coneixem i sabem de primera mà que Apple no permet utilitzar. Si en troba alguna, fa fallar el build per cridar la nostra atenció sobre aquest problema. En el servidor de hudson o als slaves cal crear la carpeta "PrivateAPIS" a hudson/tools (o hudson-slave/tools) i afegir l’script privateApiFinder.rb. Per cada projecte que volem que faci aquesta comprovació: A la secció "Ejecutar", "Añadir un nuevo paso" -> Ejecutar línea de comandos Comando: ruby ../../tools/PrivateAPIS/privateApiFinder.rb <llista de binaris que volem comprovar> Exemple: ruby $HOME/hudson-slave/tools/PrivateAPIS/privateApiFinder.rb ./trunk/build/Releaseiphonesimulator/MyApp.app/MyApp ./trunk/build/Releaseiphoneos/MyApp.app/MyApp ./trunk/build/Debug-iphonesimulator/MyApp.app/MyApp Per automatitzar la actualització d'aquest projecte al SVN, crearem un job a Hudson anomenat "own-builders-master_node" 1. "Nombre": own-builders-master_node 2. "Asociar este proyecto con el nodo": master 3. Subversion, URL del repositorio: https://svn.mmip.es/workspace/libraries/builder/trunk 4. Marcar "Lanzar ejecuciones remotas (ejem: desde 'scripts')" i posar com a token "UN_PASSWD" 5. Haurem de configurar al SVN l'arxiu que executarà automàticament el job (veure l'apartat "Execució remota de projectes" per a més instruccions)
Testing on iPhone 70 Plugins IphoneView: http://wiki.hudson-ci.org//display/HUDSON/iPhoneView+Plugin Amb aquest plugin podem generar una vista dels panells principals de Hudson més fàcil de llegir des d'un dispositiu mobil. Email-ext: http://wiki.hudson-ci.org//display/HUDSON/Email-ext+plugin El plugin permet enviar correus electrònics al finalitzar un build. Execució remota de projectes Tutorial: http://www.bigbeeconsultants.co.uk/svn-hudson-post-commit-hook Gràcies a aquesta configuració podrem executar automàticament els jobs de Hudson en quan fem un commit al SVN. També podem usar aquest script per passar informació diversa a Hudson, com ara quin usuari ha estat el que ha fet aquest commit, i poder avisar-lo directament a ell si el build no passa tots els tests. Configuració del SVN Primer cal afegir alguns scripts al respositori SVN (NO a cadascún dels projectes, sino als directoris del repositori) i modificar-los: 1. El post-commit hook a /hooks/post-commit. Cal fer chmod a+x post-commit per donar-li permissos d'execució. 2. Afegir svn-hudson.py també a /hooks/svn-hudson.py 3. Afegir svn-hudson.conf a /conf/svn-hudson.conf. Aquest arxiu SÍ l'haurem de modificar per afegir cadascun dels projectes del svn que executaran un job a Hudson. Ara cal modificar svn-hudson.conf i canviar: urlprefix = http://82.165.130.243:8080/hudson/job/ urlsuffix = /build?token=UN_PASSWD
Testing on iPhone 71 Configuració per a cada projecte: a Hudson 1. Cliquem al job que volem configurar i després a "Configurar" 2. A la secció "Disparadores de ejecutores" activem la opció "Lanzar ejecuciones remotas" i escrivim a "Identificador de seguridad" UN_PASSWD *L'identificador de seguretat (o token) ha de ser el mateix per a tots els projectes. 3. "Guardar" i ja hem acabat la configuració Configuració per a cada projecte: al repositori SVN (no al directori local) 1. Editar l'arxiu /conf/svn-hudson.conf com a root. 2. A la secció "[mappings]" cal afegir una línia per al nostre projecte del tipus <nom projecte svn> = <nom projecte a Hudson> El "nom projecte svn" ha de ser una expressió regular que permeti identificar aquest projecte de forma única respecte a la resta de projectes del repositori. El "nom projecte a Hudson" ha de ser exactament el nom que li hem donat al job dins de Hudson. Resum de funcionament: Quan es realitza un commit del projecte X, el hook "post-commit" és executat automàticament pel repositori SVN. Post-commit s'encarrega de cridar a svn-hudson.py i aquest utilitza l'arxiu svn-hudson.conf per comprovar si el projecte X es correspon amb la part esquerra d'algun mapping. Si coincideix amb un mapping, s'utilitza el nom del job de Hudson per construir la URL que farà el build remot. Per provar el funcionament podem fer: python hooks/svn-hudson.py -p . -r REVISION -d des de la arrel del repositori (no del directori local). Cal substituir REVISION per un numero de revisió vàlid en el nostre repositori. Exemple: Tenim un projecte que al SVN s'anomena myProject, i al Hudson s'anomena just-a-hudsontest. Per tant, hem d'afegir un mapping del tipus ^myProject/.* = just-a-hudson-test a l'arxiu svn-hudson.conf. Si a la revisió 11 vam fer un commit d'aquest projecte, podem executar l'script svn-hudson.py (en mode debug per obtenir més informació) amb la següent línia: python hooks/svn-hudson.py -p . -r 11 –d
Testing on iPhone 72 Tests de Disponibilitat – Site Monitor Plugin de Hudson: http://wiki.hudson-ci.org/display/HUDSON/SiteMonitor+Plugin Amb aquest senzill plugin realitzarem els tests de disponibilitat cada cert temps, monitoritzant les urls que hi configurem. Administrar Hudson -> Administrar Plugins -> Todos los plugins -> SiteMonitor (marcar) Al final de la pàgina hi ha el botó per a instal•lar. Després ens demanarà que reiniciem per a que els canvis tinguin efecte. Per afegir URLS: 1. Anem al projecte al qual pertanyen les urls que volem monitoritzar, i després a l'apartat "Configurar". 2. Marquem la casella "Monitor Site" 3. Afegim les urls a monitoritzar Guardar Configurar Site Monitor: Administrar Hudson -> Configurar el sistema Podem configurar dues opcions: Success Response Codes (per defecte 200) Timeout in seconds (30) Quan executem el projecte es comprovarà que les webs que hem configurat siguin accessibles. Quan entrem a la vista principal del projecte veurem una icona nova anomenada "Site Monitor", i ens mostrarà l'estat de les webs. Si alguna web no és accessible el build fallarà per avisar-nos.
Testing on iPhone 73 7.3. Comparació amb altres camps de desenvolupament Treballar amb Objective-c per iOS és molt diferent a fer-ho en Java o C/C++, per diversos motius. El primer i més obvi és la quantitat de programadors que els han utilitzat, tant actualment com en el passat. Les eines més avançades sempre es desenvolupen pensant en els frameworks més utilitzats, de manera que no és estrany que els frameworks, biblioteques i altres eines estiguin molt més desenvolupades en entorns Java o C++ que no pas en Objective-c. Un altre motiu determinant és la globalitat del llenguatge i entorn. Quan un programa en Objective-c només té dos objectius: Mac OS X o iOS. En canvi, Java es pot utilitzar per sistemes operatius Unix, Windows, Android, per realitzar des d’aplicacions d’escriptori fins a servidors web, etc. Si hi afegim que aquests llenguatges són pràcticament els que tot programador aprèn des del principi de la seva formació, està clar que Objective-c no parteix amb gaires avantatges. Així doncs, mentre la oferta d’eines de code quality (inclouen code coverage, mètriques relacionades amb la qualitat del codi, etc.) és molt interessant, sobretot en Java, en Objectivec és pràcticament inexistent.
Testing on iPhone 80 Planificació real Com que en un principi allargaríem el conveni més enllà del mes de setembre, que era quan acabava el primer contracte, la planificació arribava fins al novembre. Per circumstàncies econòmiques de l’empresa, malauradament no vam poder renovar i això ha truncat les planificacions. Donat que vaig començar a treballar en un altre lloc, si a Mobivery una setmana es repartia entre temps dedicat al projecte i temps dedicat a treballar per clients, ara la feina del projecte s’ha de repartir entre els caps de setmana principalment. Com que han canviat les hores reals que hi dedicava durant una setmana, o deixava de planificar pensant en setmanes (si una setmana té cinc dies laborables i passo a treballar només els caps de setmana, podríem dir que la mateixa feina triga el doble de temps en acabar-se), o a efectes gràfics continuava planificant igual però tenint en compte que la data “final” no es correspondrà amb la de debò (perquè s’ha duplicat el temps necessari per acabar una tasca). En definitiva, des que vaig deixar Mobivery he continuat comptant la feina en setmanes per mantenir la mateixa unitat de temps, tot i que per mi “una setmana de feina” equival a “dues setmanes reals” i per tant no coincidirà la data en que acabi la feina de veritat amb la planificada. 8.1. Estudi econòmic En aquest projecte podem dues aproximacions al seu cost. D'una banda tenim els diners reals que s'han invertit en el projecte: Si multipliquem el sou de becari (incloent impostos) pel nombre de mesos que ha durat el projecte tenim, obtenim el cost global del projecte: 8.5 mesos * 1122.45€ = 9540€
Testing on iPhone 81 I això és una xifra prou representativa. Podríem tenir en compte altres costos com el lloguer de l'oficina, el consum d'electricitat, etc. però grosso modo, el cost total del projecte estarà al voltant dels 10.000€. Com ja hem vist en les planificacions, part d'aquestes hores s'han dedicat a treball per a clients, de manera que podríem no imputar-les directament al projecte tot i que han influït en la meva formació en Scrum i desenvolupament per iOS. Si restem les deu setmanes treballant per a clients, això són uns 2.5 mesos. Per tant: 6 mesos * 1122.45€ = 6735€ També hem comentat que a partir del 31 d'agost vaig deixar de treballar a Mobivery, així que des del punt de vista de l'empresa el cost del projecte (sense tenir en compte les hores treballades per a clients) ha estat de: 3.5 mesos * 1122.45€ = 3929€ Per ser del tot coherents caldria tenir en compte el temps dedicat per l'Àlex, l'Alejandro i en Kilian a aquest projecte, que incrementarien el seu cost. Tot i així, són difícils de quantificar perquè no han estat constants durant un període temps concret, sinó més aviat ajudes puntuals quan he necessitat informació o realitzar alguna de les tasques del projecte. Com no podem donar una xifra exacte, almenys podem intentar aproximar-la: entre 500€ i 1000€ que sumariem als valors anteriors. L'altra forma d'entendre el cost del projecte és quantificant les tasques que hauria realitzat un tester professional, però en aquest cas no tindria massa sentit comptabilitzar les hores dedicades a investigació i anàlisi de frameworks (o almenys no la majoria), ni a la escriptura de la memòria. Així doncs, on té més sentit aplicar-ho és a la segon fase, ja que inclou dues setmanes d'anàlisi, una d'instal·lació dels frameworks, dues setmanes i mitja dedicades a la configuració del servidor d'integració continua i tres i mitja més per a testejar l’aplicació FCBMobile. Això són nou setmanes, 360 hores en total. El sou d'un software tester depèn molt de la seva experiència, però assumirem uns 30€/hora: 360 hores * 30€/hora = 10800€ Segurament un expert en tècniques de testing hauria pogut fer la mateixa feina amb menys temps, però en definitiva aquesta és la aproximació més realista que podem fer basada en les dades d'aquest projecte.
Testing on iPhone 82 Resum gràfic 8.2. Gestió del projecte: Scrum Scrum és una metodologia de gestió de projectes emmarcada dins de les tècniques Agile. S’està implementant cada cop més en les empreses desenvolupadores de software ja que intenta donar solucions a problemes clàssics en aquesta indústria, com ara: Els requisits varien a mesura que es concreta l’aplicació, no és possible fer una planificació detallada en la fase inicial del projecte. Un excés de reunions deixa menys temps pel desenvolupament del projecte, i es perd molt temps fins que les decisions es comuniquen a les persones que han d’implementar-les. Total (10.000€) 93% Ajuda externa (750€en mitjana) 7% Cost global A projecte (6.735€) 67% A clients (3.265€) 33% Valor dedicat sobre els 10.000€
Testing on iPhone 83 Amb Scrum els treballadors es distribueixen en equips i passen a tenir la responsabilitat dels projectes, evitant crear estructures jeràrquiques que dificultin la comunicació interna. El client no coneix l’estat en que es troba el seu projecte i pot tenir la sensació de que anem massa lents. Scrum pretén que el client contacti directament amb l’equip de desenvolupament i que hi hagi entregues periòdiques. Scrum també es pot entendre a través dels valors del “Agile Manifesto” que promou: - Individus i interaccions per sobre de processos i eines - Software que funcioni per sobre d’una documentació extensiva - Col·laboració amb el client per sobre de la negociació d’un contracte - Respondre als canvis per sobre de seguir un pla 8.3. Així funciona Scrum El product owner crea una llista de tasques ordenades per prioritat que anomenem product backlog. La tasques es prioritzen en funció del valor que tenen pel client. Durant l’sprint planning, l’equip es reuneix amb el product owner i decideixen quines tasques creuen que es podran acabar, respectant l’ordre establert en el product backlog, i es pensa com s’implementaran. L’equip té un temps per acabar la feina, un sprint, que acostuma a durar entre 1 i 4 setmanes, i anomenem sprint backlog a les tasques que l’equip es va comprometre a acabar. Durant l’sprint, cada dia hi ha una reunió curta anomenada daily scrum, on l’equip comprova si l’sprint progressa com estava planificat o han sorgit dificultats. Al final de l’sprint, la feina acabada hauria de ser potencialment entregable al client. És a dir, s’haurà programat, testejat i es comprovarà que compleix amb els requisits. L’sprint finalitza amb una sessió de demo on els membres de l’equip mostren les tasques acabades i la retrospectiva, una reunió informal que té per objectiu avaluar com ha anat l’sprint: si s’ha complert amb la planificació o no, problemes que hagin afectat a l’equip, coses positives que calgui destacar, etc. El següent sprint comença amb un nou sprint planning, i es torna a repetir el procés. El cicle es repeteix fins que: S’acaba el product backlog i l’aplicació ja està finalitzada S’acaba el pressupost Arribem a una data límit En qualsevol dels tres casos, Scrum assegura que el treball mes valuós ja s’ha fet al acabar el projecte (recordem que el product backlog està prioritzat) i que a cada sprint hem entregat una part funcional del software.
Testing on iPhone 84 8.4. Scrum en detall Veiem algunes definicions de conceptes que han aparegut en l’apartat anterior. Product backlog: el product backlog és dinàmic, ja que s’introdueixen o s’eliminen tasques a mesura que es concreta el projecte i, com ja hem comentat, està prioritzat amb l’objectiu que les tasques prioritàries es defineixin i s’acabin abans. Sprint backlog: l’sprint backlog es negocia durant l’sprint planning, i es seleccionen les tasques que es faran durant l’sprint. Aquestes tasques es poden dividir en sub-tasques més petites que ajudin a concretar què és el que cal programar, i l’equip treballarà de manera col·laborativa per a completar l’sprint backlog.
Testing on iPhone 85 9. Objective-c Abans de començar a parlar en detall de la aplicació que hem testejat, és convenient conèixer una mica millor el llenguatge de programació en el qual estan escrits tant l’aplicació com els tests. A més a més, aprendre els detalls d’Objective-c ha estat una part fonamental per a poder realitzar la resta de tasques que ens vam proposar amb aquest PFC. Així doncs, repassarem els seus orígens, quines han estat les seves àrees d’influència i les principals característiques que ofereix als programadors. 9.1. Història Objective-c va ser creat al 1986 per Brad Cox i Tom Love. En aquella època, un dels principals problemes amb el que s’estaven topant els desenvolupadors era la dificultat de reutilitzar el codi, i començaven a aparèixer els primers llenguatges que implementaven la noció d’objecte per intentar solucionar-ho. Smalltalk-80 va aparèixer al 1980 i va ser un dels primers llenguatges orientat a objectes. Utilitzava una màquina virtual per a executar-se sobre diferents plataformes, i com a llenguatge va suposar una revolució que va influenciar molt notablement a Objective-c, entre altres com Java, Ruby o Python. Però el principal impediment per a ser adoptat massivament pels desenvolupadors va ser el baix rendiment de la seva màquina virtual: el codi escrit per a Smalltalk era molt menys eficient en temps d’execució que un programa equivalent per a C. Brad Cox va pensar en intentar unir la eficiència de C i la gran quantitat de desenvolupadors que hi estaven acostumats, amb les característiques més interessants de Smalltalk (orientació a objectes, reflexió, etc.). Aquest intent aviat va donar els seus fruits, i Cox va demostrar que realitzant unes poques modificacions al compilador de C es podia derivar un llenguatge amb orientació a objectes i el va anomenar OOPC (Object Oriented Programming in C). Aquest va ser la llavor que va impulsar a crear el que avui coneixem com Objective-c. 9.2. Ús d’Objective-c Seguint el treball realitzat amb el llenguatge OOPC, Objective-c també s’ha implementat aprofitant els compiladors ja existents de C. Així doncs, Objective-c està pensat com un superconjunt estricte de C de manera que qualsevol programa escrit en C és totalment vàlid per a un compilador d’Objective-c. D’aquesta manera Objective-c es beneficia de totes les eines i biblioteques existents per C, i també dels coneixements que ja tenien la majoria dels programadors: aprendre Objective-c era més fàcil que Smalltalk perquè simplement és una extensió de C. Tot i això, Objective-c mai ha estat un llenguatge de referència en el món comercial. L’any 1983 ja havia aparegut C++, un llenguatge també orientat a objectes (entre altres paradigmes)
Testing on iPhone 86 que s’ha convertit en un dels més populars de tots els temps, i probablement la seva gran difusió va frenar l’avenç de la resta de llenguatges similars a C. L’ús d’Objective-c s’ha limitat principalment als sistemes operatius de la companyia Apple, ja que bona part de les seves tecnologies s’havien programat en Objective-c: tant Mac OS X com iOS estan basats en NeXTSTEP, un sistema operatiu multitasca i orientat a objectes implementat en gran part amb Objective-c. 9.3. Característiques Objective-c és un llenguatge orientat a objectes basat en C i pertany a la categoria dels llenguatges reflexius, com ara Java, Ruby o C#, capaços de modificar la seva pròpia estructura i comportament en temps d’execució. La estructura bàsica d’un objecte es defineix en dos blocs diferents: la declaració de la interfície i la seva implementació. Normalment la interfície la especifiquem en un arxiu on definim: Les variables d’instància Les propietats de les variable d’instància Les capçaleres dels mètodes públics de la classe Els mètodes s'implementen en arxius amb la extensió .m. Donat que Objective-c no permet fer una distinció clara entre mètodes privats i públics (tal i com tenim en altres llenguatge com Java o C++), acostumem a declarar els mètodes que no volem que siguin accessibles des de l'exterior a dins de l’arxiu .m. Resum de sintaxis Tot i ser un llenguatge que deriva directament de C, la primera impressió al veure codi d’Objective-c és que són molt diferents. Bàsicament hi ha dos aspectes que són els que més sobten: La declaració i crida dels mètodes En Objective-c els paràmetres d’un mètode tenen nom i formen part de la seva signatura, mentre que en C i en la majoria de llenguatges els paràmetres són anònims i acostumem a identificar-los pel nom que li donem a la variable. Per exemple, si volem declarar en C la funció suma podríem fer-ho així: int suma (int a, int b); En Objective-c quedaria de la següent manera: (int) suma:(int)a amb:(int)b; El nom del primer paràmetre és normalment el nom del mètode, mentre que els successius paràmetres cal donar-los un altre nom. Encara que de primeres pugui semblar una mica enrevessat, facilita força la lectura del codi quan els mètodes tenen diversos paràmetres i no és obvi que fan cadascun.
Testing on iPhone 87 Si volem cridar a aquesta funció en C fem: x = suma (10, 13); I en Objective-c: x = [self suma:10 amb:13]; En aquest cas l’objecte que implementa el mètode és self , la referència a la instància actual de qualsevol objecte, però podria ser un objecte diferent o el nom d’una classe (si fos un mètode estàtic). En un exemple més complex s’aprecia millor quina és la utilitat de donar noms als paràmetres. Suposem que volem crear un missatge d’alerta que aparegui quan l’usuari entra a l’aplicació: //Reservem memòria i inicialitzem en el mateix pas UIAlertView *alertWelcome = [[UIAlertView alloc] initWithTitle:@"Benvingut!" //Amb l’@ i les cometes es defineix un String message:@"Esperem que t’agradi" //Missatge que apareix a la alerta delegate:self cancelButtonTitle:@"Gràcies" //El títol del botó que podrà primer l’usuari otherButtonTitles:nil]; //Només donem el botó de cancel·lar Si escrivim una funció similar en C o Java no podrem deduir si el string ”Benvingut!” fa referència al títol de la finestra o al nom del botó, encara que podríem solucionar-ho creant una variable per a cadascun dels paràmetres i passant-les al mètode en comptes d’escriure els valors directament. Els claudàtors L’altre particularitat de la sintaxis d’Objective-c és l’ús de claudàtors per a cridar a funcions, com ja hem vist als exemples anteriors, o accedir a les variables d’instància d’un objecte. Suposem que tenim una classe Persona amb un mètode que ens retorna el seu nom i cognoms. Aquesta classe també ens permet accedir als seus progenitors. Si volem conèixer el nom dels seus pares, en Java faríem: Persona joan = new Persona(); /* inicialitzacions, etc. */ joan.mare.nomAmbCongnoms(); A Objective-c: Persona *joan = [Persona new]; //new és l’abreviatura de [[Persona alloc] init] /* inicialitzacions, etc. */ [[joan mare] nomAmbCognoms] Mentre a Java queda clar quan estem accedint a una variable d’instància i quan cridem a una funció per l’ús dels parèntesis, en Objective-c queda amagat quan no hi ha paràmetres en les crides als mètodes.
Testing on iPhone 88 En Objective-c també es pot utilitzar la notació amb punt (joan.mare) per accedir a les variables d’instància, però no per a cridar les funcions, però s’acostuma a fer servir en casos molt puntuals. Propietats Les propietats són una sintaxis que ens ajuden a definir els mètodes de lectura i escriptura per a les variables d’instància (els típics get i set de cada variable). Si tenim una classe Persona amb una variable “nom” i li definim una propietat, automàticament totes les classes que podien utilitzar Persona ara poden cridar al mètode setNom:@”Joan” i obtenir el valor amb la funció nom (el mètode per obtenir el valor es diu igual que la variable, sense get, i a vegades pot crear confusió perquè també podem accedir directament a la variable sense passar pel mètode que genera la propietat). Vegem un exemple: @interface Persona:NSObject { NSString *nom; NSString *cognom1; NSString *cognom2; Persona *mare; //(etc.) } @property (nonatomic, retain) NSString *nom; @property (readonly) Persona *mare; @end Però les propietats no només generen els mètodes gets i sets, sinó que podem afegir certs paràmetres per modificar el seu comportament i la manera com accedeixen a les variables. Atomicitat nonatomic: per defecte les propietats són atòmiques, és a dir, quan un thread accedeixi a la variable es generarà un “lock” i bloquejarà a la resta de threads que hi intentin accedir al mateix temps. Aquest “lock” genera unes quantes instruccions extra que potser no necessitem, de manera que ens les podem estalviar utilitzant nonatomic. Comportament del setter assign: és el comportament per defecte, i l’únic que fa és assignar a la variable el valor que rep com a paràmetre. Típicament s’utilitza per a valors escalars. Si el nostre entorn disposa de Garbage Collector, assign i retain són equivalents, però aquest no és el cas d’iOS i és important entendre les diferències. retain: en aquest cas, s’allibera la memòria ocupada per l’anterior valor (es fa un release) i, abans d’assignar el nou valor, li apliquem un retain (incrementem el comptador de referències). D’aquesta manera ens assegurem de que podrem accedir a l’objecte encara que altres l’alliberin. Per tant, també tenim responsabilitat d’alliberar-lo quan es destrueixi l’objecte que conté aquesta propietat. Aquest comportament només es pot aplicar als
Testing on iPhone 89 objectes, ja que als valors escalars ni els hi assignem memòria manualment ni ens hem d’encarregar d’alliberar-los. copy: primer allibera la memòria ocupada amb un release, i després copia l’objecte que rep com a paràmetre. Té certes restriccions, ja que en Objective-c només es poden copiar els objectes que implementen un cert protocol (NSCopying). Copy també s’aplica exclusivament a objectes, ja que els valors escalars sempre es copien quan els assignem. Permís d’escriptura readonly: amb aquest valor definim que la propietat no generarà el mètode setter, obtenint així un mètode per llegir el valor de la variable des d’un objecte extern però sense poder modificar-lo. Per defecte el valor és readwrite. Herència, protocols i categories A Objective-c no hi ha herència múltiple com a C++, però hi ha mecanismes similars al “implements” de Java que permeten aconseguir el mateix: els protocols i les categories. Protocols Els protocols defineixen un conjunt de mètodes que han d’implementar els objectes que compleixen amb el protocol, tot i que alguns poden ser opcionals. Són molt semblants a les interfícies de Java. Per exemple, podríem definir un protocol que obligués a tenir un mètode per escriure per pantalla certa informació de tots els objectes que l’implementin. @protocol ConsolePrint -(void) printMyself; @end Un cop definit el protocol, la classe Persona que hem vist en exemples anteriors l’implementaria així: @interface Persona:NSObject <ConsolePrint> { NSString *nom; NSString *cognom1; NSString *cognom2; Persona *mare; //(etc.) } Ara la classe Persona compleix amb el protocol ConsolePrint, de manera que ara necessita implementar els seus mètodes: - (void) printMyself { NSLog(@”Nom complet: %@ %@ %@”, nom, cognom1, cognom2); } També podem comprovar si donat un objecte, aquest compleix algun protocol. Imaginem un mètode semblant als que tenim per realitzar tests que comprovés si un objecte qualsevol és “nil” i, si no ho és, imprimís algunes dades sobre ell per pantalla:
Testing on iPhone 96 SynthesizeSingleton Analitzarem aquesta classe a través d’una altra que l’implementi, com LocatorService. L’objectiu és comprovar que efectivament la classe és un singleton i sempre retorna la mateixa instància. - (void) testSynthesizeSingleton { LocatorService *locator = [LocatorService sharedLocatorService]; LocatorService *locator2 = [LocatorService sharedLocatorService]; STAssertEqualObjects(locator, locator2, @"Singleton class is not returning the same object, so it's not a singleton!"); [locator setLocating:YES]; [locator2 setLocating:NO]; STAssertEquals([locator locating], [locator2 locating], @"These values should be equals: when we modify one locator, the other one should be changed too"); [locator release]; [locator2 release]; } XMLReader Jugador, Classification, Scoreboard i Palmarés Aquestes quatre classes tenen un comportament molt similar: llegeixen uns xml (específics per a cada classe) i generen un conjunt d’objectes amb el que poblem les diferents vistes de l’aplicació. En el cas de XMLReaderJugador, el que fem és parsejar un jugador del qual ja coneixem la seva informació i comparar camp a camp si és correcta. - (void) testParseXMLFileWithData { /* INICIALITZACIONS */ Jugador *valdesParsed = [xmlParser jugador]; /* Jugador creat manualment amb la informació correcta */ Jugador *valdesGenerated = [Jugador new]; [valdesGenerated setDorsal:@"1 Valdes"]; [valdesGenerated setNom:@"Victor Valdés Arribas"]; [valdesGenerated setUrlFoto:@"http://www.fcbarcelona.cat/web/thumbnails/199_150/Imatges/20092010/futbol/primer_equip/plantilla/2009-07-20_CARA_VALDES_01.jpg"]; [valdesGenerated setPosicio:@"Porter"]; [valdesGenerated setLlocNaixement:@"L'Hospitalet de Llobregat (Barcelona)"]; [valdesGenerated setDataNaixement:@"14-01-1982"]; [valdesGenerated setAlsada:@"183"]; [valdesGenerated setPes:@"78"]; STAssertEqualStrings([valdesGenerated dorsal], [valdesParsed dorsal], @"Error dorsal"); STAssertEqualStrings([valdesGenerated nom], [valdesParsed nom], @"Error nom"); STAssertEqualStrings([valdesGenerated urlFoto], [valdesParsed urlFoto], @"Error urlFoto"); STAssertEqualStrings([valdesGenerated posicio], [valdesParsed posicio], @"Error posicio"); STAssertEqualStrings([valdesGenerated llocNaixement], [valdesParsed llocNaixement], @"Error llocNaixement"); STAssertEqualStrings([valdesGenerated dataNaixement], [valdesParsed dataNaixement], @"Error dataNaixement"); STAssertEqualStrings([valdesGenerated alsada], [valdesParsed alsada], @"Error alsada"); STAssertEqualStrings([valdesGenerated pes], [valdesParsed pes], @"Error pes"); }
Testing on iPhone 97 La resta de tests són força similars, canviant els camps a comprovar pels concrets de cada classe. 10.3. Tests Funcionals Com que hem realitzat els tests funcionals amb el framework FoneMonkey, no hi ha codi del test que puguem mostrar. Així doncs, utilitzarem un Test Case (molt semblant a un Cas d’Ús) per definir quin ha de ser el comportament de cada test i mostrarem amb algunes imatges quines són les accions que hem gravat. Resum: funcionalitats de l'aplicació Notícies · Es mostren les notícies de les diferents seccions (el picker horitzontal ha de funcionar correctament) · Es mostra el detall de les notícies · Es mostra la pantalla amb informació per comprar entrades el Club Plantilla · Es mostren les estadístiques dels jugadors Palmarès · Apareix el palmarès de les seccions Història · Historia Agenda · Els dos pickers horitzontals funcionen · Apareix el calendari Multimèdia Fotos · Apareix la taula de fotos però no es pot testejar la galeria d'imatges Himnes · Apareixen els 5 himnes i es poden reproduir / aturar Vídeos · Apareix la taula de vídeos però no es pot testejar la seva reproducció Samarreta · Puc modificar la meva samarreta · Es mostra l'ajuda de la samarreta GENERAL FCBarcelona · Es mostra la "Bola del Món" Ajuda · Es mostren les pantalles d'ajuda
Testing on iPhone 98 Test Cases Notícies Test Case Field Details Test Case Name Picker Horitzontal Purpose Comprovar que totes les seccions del picker es poden seleccionar, i les notícies són diferents. Initiation Criteria El picker horitzontal està al principi Execution Steps 1. Tocar en cadascuna de les seccions dins del picker Expected Results Les notícies són diferents per a cada secció del picker Test Case Field Details Test Case Name Comprar entrades Purpose Comprovar que es mostra la vista Canals de compra i que es pot trucar al Tocar els telèfons que hi apareixen. Initiation Criteria Cap Execution Steps 1. Tocar el botó COMPRA la teva entrada 2. Tocar el primer telèfon 2.1. Cancel·lar el alert view que sortirà 3. Tocar el segon telèfon 3.1. Cancel·lar el alert view que sortirà 4. Tocar el botó OK Expected Results Veurem les diferents opcions per comprar una entrada
Testing on iPhone 99 Test Case Field Details Test Case Name Veure notícia Purpose Comprovar que es pot navegar per la taula de notícies, llegir-ne una, que funciona la galeria d'imatges i que es pot compartir la notícia per mail, Facebook i Twitter Initiation Criteria Cap Execution Steps 1. Seleccionar la secció de "Barça" 2. Fer scroll en la taula de notícies fins al final 3. Seleccionar una notícia qualsevol 3.1. Tocar la imatge al costat del títol 3.1.1 Tocar el botó OK per tornar enrere 3.2. Fer scroll en la notícia fins al final 3.2.1. Tocar el botó Mail 3.2.1.1. Comprovar que el cos del mail és igual que la notícia (sense les imatges) 3.2.1.2. Tocar Cancelar per tornar enrere 3.2.2. Tocar el botó Facebook: apareix la vista de Facebook Connect 3.2.2.1. Tocar el botó Cancelar per tornar enrere 3.2.3. Tocar el botó Twitter: mostra la finestra de Twitter Login 3.3. Tocar el botó OK per tornar a la llista de notícies Expected Results Es mostra una notícia, es pot veure la galeria d'imatges i podem compartir-la
Testing on iPhone 100 el Club Test Case Field Details Test Case Name Plantilla Purpose Es mostren les estadístiques dels jugadors Initiation Criteria Cap Execution Steps 1. Tocar el botó el Club del menú inferior 2. Tocar el botó Plantilla 2.1. Moure el scroll horitzontal i seleccionar un jugador 3. Tocar el botó blau amb una fletxa groga 3.1. Tocar el botó blau amb una fletxa groga cap amunt 3.2. Utilitzar el "page control" per veure les diferents competicions 3.3. Tocar el botó blau amb una fletxa groga cap avall 3.4. Tocar el botó blau amb una fletxa groga cap avall 4. Tocar el botó Notícies del menú inferior per tornar a la llista inicial Expected Results Se selecciona un jugador de la plantilla i apareixen les seves estadístiques en les diferents competicions Test Case Field Details Test Case Name Palmarès Purpose Apareix el palmarès de les seccions Initiation Criteria Cap
Testing on iPhone 101 Execution Steps 1. Tocar el botó el Club del menú inferior 2. Tocar el botó Palmarès 2.1. Moure el scroll horitzontal i seleccionar la secció d'Handbol 3. Comprovem que existeixen les categories de "Campionat d'Espanya", "Campionat de Catalunya" i "Lliga Asobal" 3.1. Fer scroll del palmarès fins al final 4. Tocar el botó Notícies del menú inferior per tornar a la llista inicial Expected Results Se selecciona una secció i apareix el seu palmarès Test Case Field Details Test Case Name Història Purpose Es mostren les estadístiques dels jugadors Initiation Criteria Cap Execution Steps 1. Tocar el botó el Club del menú inferior 2. Tocar el botó Història 2.1. Tocar el botó "1899-1922" 2.2. Tocar el botó "1922-1957" 2.3. Tocar el botó "1957-1974" 2.4. Tocar el botó "1974-1992" 2.5. Tocar el botó "1992-2009" 3. Tocar el botó Notícies del menú inferior per tornar a la llista inicial
Testing on iPhone 102 Expected Results Es mostra la informació relacionada amb cadascuna de les categories Agenda Test Case Field Details Test Case Name Calendari Purpose Es mostra el Calendari i la Classificació Initiation Criteria Cap Execution Steps 1. Tocar el botó Agenda del menú inferior 2. Fer scroll en el Calendari fins al final 3. Utilitzar el "page control" per veure la Classificació 3.1 Fer scroll de la Classificació fins al final 4. Utilitzar el "page control" per tornar al Calendari 5. Tocar el botó Notícies del menú inferior per tornar a la llista inicial Expected Results Es veuen el Calendari i la Classificació de la secció i la divisió seleccionats per defecte.
Testing on iPhone 103 Multimèdia Test Case Field Details Test Case Name Himnes Purpose Comprovar que apareixen els cinc himnes Initiation Criteria Cap Execution Steps 1. Tocar el botó Multimèdia del menú inferior 2. Tocar la secció Himnes 3. Tocar la cel·la de l'Himne 1923 3.1. Activar i desactivar el botó que reprodueix l'himne 4. Tocar la cel·la de l'Himne 1949 4.1. Activar i desactivar el botó que reprodueix l'himne 5. Tocar la cel·la de l'Himne 1954 5.1. Activar i desactivar el botó que reprodueix l'himne 6. Tocar la cel·la de l'Himne 1974 6.1. Activar i desactivar el botó que reprodueix l'himne 7. Tocar la cel·la de l'Himne Centenari 7.1. Activar i desactivar el botó que reprodueix l'himne 8. Tocar el botó Notícies del menú inferior per tornar a la llista inicial Expected Results Tots els himnes es poden reproduir Samarreta Test Case Field Details Test Case Name Mostrar l'Ajuda de la secció Samarreta Purpose Comprovar que l'ajuda es mostra correctament
Testing on iPhone 104 Initiation Criteria Cap Execution Steps 1. Tocar el botó Samarreta 2. Tocar el botó Ajuda de la alerta que apareix 3. Usar el page control per veure les pantalles d'ajuda 3.1. Tocar el botó per tancar ("X") 4. Tocar la finestra d'alerta per amagar-la 5. Tocar el botó Notícies Expected Results L'usuari podrà veure l'Ajuda de les samarretes Test Case Field Details Test Case Name Modificar la samarreta Purpose Veure que els camps modificables de la samarreta s'actualitzen correctament Initiation Criteria Cap Execution Steps 1. Tocar el botó Samarreta 2. Tocar la finestra d'alerta per amagar-la 2.1. 3. Tocar el botó Notícies Expected Results L'usuari podrà veure l'Ajuda de les samarretes
Testing on iPhone 105 GENERAL Test Case Field Details Test Case Name Bola del món Purpose Comprovar que es pot accedir a la bola del món i tornar enrere després Initiation Criteria Cap Execution Steps 1. Tocar el botó FCBARCELONA més que un club - Es mostra la bola del món - 2. Tocar el botó FCBARCELONA més que un club - Tornem a la pantalla principal - Expected Results L'usuari podrà veure la bola del món Test Case Field Details Test Case Name Mostrar l'Ajuda de l'aplicació Purpose Comprovar que l'ajuda es mostra correctament Initiation Criteria Cap Execution Steps 1. Tocar el botó Ajuda 2. Usar el page control per veure totes les pantalles d'ajuda 3. Tocar el botó per tancar ("X") - Tornem a la pantalla principal - Expected Results L'usuari podrà veure l'Ajuda
Testing on iPhone 112 13. Bibliografia ProfessionalTester.com. 2010. Professional Tester Magazine, revistes 2, 3 i 4. Chin, Danton i altres. 2010. More iPhone Cool Projects. Apress, 2010. Watkins, John. 2009. Agile Testing. How to Succeed in an Extreme Testing Environment. Cambridge University Press, 2009. Sadun, Erica. 2009. The iPhone Developer’s Cookbook: Building Applications with the iPhone 3.0 SDK. Addison-Wesley, 2009.
Testing on iPhone 113 14. Més informació Google Toolbox for Mac Tutorial sobre com utilitzar la llibreria: http://code.google.com/p/google-toolbox-for-mac/wiki/iPhoneUnitTesting FoneMonkey Fòrum on hi ha la informació més actualitzada: http://www.gorillalogic.com/forum/8 Command Reference: http://www.gorillalogic.com/books/fonemonkey-doc/commands/command-reference UISpec Documentació i instruccions per utilitzar UISpec: http://code.google.com/p/uispec/ UIAutomation UIAutomation Reference Collection: http://developer.apple.com/library/ios/#documentation/DeveloperTools/Reference/UIAutom ationRef/_index.html How to use UIAutomation to create iPhone UI tests (tutorial): http://answers.oreilly.com/topic/1646-how-to-use-uiautomation-to-create-iphone-ui-tests/