Full text
ESCUELATÉCNICASUPERIORDEINGENIERÍAINFORMÁTICA GRADOENINGENIERÍAINFORMÁTICA SISTEMADEINFORMACIÓNPARAPELÍCULASCON ANGULARJS INFORMATIONSYSTEMFORMOVIESWITHANGULARJS Realizadopor JoseAntonioDíazGonzález Tutorizadopor AntonioJesúsNebroUrbaneja Departamento LenguajesyCienciasdelaComputación UNIVERSIDADDEMÁLAGA MÁLAGA,septiembre2016 Fechadefensa: ElSecretariodelTribunal
E.T.S. DE INGENIERÍA INFORMÁTICA, UNIVERSIDAD DE MÁLAGA Resumen En este Trabajo Fin de Grado se llevará a cabo el desarrollo de una aplicación Web que muestre información sobre películas y series de televisión, así como de las personas que intervienen en las mismas. El sistema se conforma de una serie de controladores y servicios que, atendiendo al patrón de arquitectura MVC (Modelo Vista Controlador), se encargan de proporcionar la información necesaria para las vistas. De tal forma que estos controladores son el nexo de unión entre las vistas de la aplicación y la API REST de Trakt.tv. Las vistas permiten a los usuarios acceder a las distintas secciones de información necesarias para cubrir los requisitos propuestos. Trakt.tv tiene a disposición de los desarrolladores una API con información recopilada sobre películas y series. De modo que los usuarios puedan consultar cualquier tipo de datos referente a la temática de la aplicación a desarrollar, con la garantía de que estos datos irán en aumento conforme va surgiendo nuevo contenido en la actualidad. Palabras clave AngularJS, Consultar, Películas, Series, Actores, Información, aplicación cinéfila, API, REST, Trakt.tv
II E.T.S. DE INGENIERÍA INFORMÁTICA, UNIVERSIDAD DE MÁLAGA Abstract In this Final Degree Project, a Web application will be developed. It shows information about movies and television shows to the user, as well as participant in that kind of content. The system has a set of services and controllers, which follows the software architectural pattern MVC (Model View Controller), that has the duty to populate the views with information. So, controllers are the union point between application views and Trakt.tv API. The views allow the users to access different sections of information so as to guarantee the application requirements. The API is given by Trakt.tv to developers with a huge amount of information about movies and shows. In such a way that users can seek any data about application’s topics. This content is likely to become bigger as well as new content appears in order to provide the user with the possibility to access current information. Keywords AngularJS, Search, Films, Shows, Characters, Information, cinephile application, API, REST, Trakt.tv
Índice 1 Introducción 1 1.1 Motivación................................ 1 1.2 Objetivos ................................ 2 1.3 Tecnologías............................... 2 1.4 Estructura de la memoria . . . . . . . . . . . . . . . . . . . . . . . 4 2 Planificación 7 3 Análisis de requisitos 9 3.1 Requisitos funcionales . . . . . . . . . . . . . . . . . . . . . . . . . 9 3.2 Requisitos no funcionales . . . . . . . . . . . . . . . . . . . . . . . 10 3.3 Requisitos de la API . . . . . . . . . . . . . . . . . . . . . . . . . . 11 4 Diseño 15 4.1 Organización de la funcionalidad . . . . . . . . . . . . . . . . . . . 15 4.2 Diseño de interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.3 Casos de uso y comportamiento del sistema . . . . . . . . . . . . 16 5 Desarrollo 23 5.1 Herramientas.............................. 23 5.2 AngularJS................................ 24 5.3 Gestión de controladores . . . . . . . . . . . . . . . . . . . . . . . 26 5.4 Gestióndevistas............................ 30 5.5 Comunicaciones ............................ 34 5.6 Otroselementos ............................ 39 III
6 Pruebas 43 6.1 KarmayJasmine............................ 43 6.2 Pruebasunitarias............................ 45 Conclusiones 51 Bibliografía 55 A Anexos técnicos 57 A.1 VistaMovies.html............................ 57 A.2 VistaMovie.html ............................ 58 A.3 VistaShows.html............................ 60 A.4 VistaShow.html ............................ 61 A.5 VistaSeason.html ........................... 63 A.6 VistaPerson.html............................ 64
Capítulo 1 Introducción En este capítulo se expondrá la temática principal del Trabajo Fin de Grado (en adelante, proyecto) a realizar, así como los objetivos que se buscan alcanzar, las tecnologías que se plantean utilizar para lograr dicho fin y la estructura de la memoria en sí. De este modo se le ofrece al lector una visión general sobre el contenido que conformará el cuerpo de la memoria y establecemos las bases de la materia del proyecto en cuestión. 1.1. Motivación El auge de las nuevas tecnologías es algo evidente hoy en día y entre ellas las aplicaciones Web han ganado bastante popularidad e importancia en nuestras vidas. Tanto es así que vivimos rodeados de aplicaciones que usamos en distintos aspectos del día a día y que no existían hace no muchos años (redes sociales, banca electrónica, comercio electrónico, etc). Una característica clave es que las aplicaciones Web permiten el acceso a distintos servicios a través de un navegador Web e Internet. Este crecimiento tecnológico ha permitido que los sistemas de información tradicionales pasen a formar parte de la Web, a través de aplicaciones que permiten el tratamiento de la información, así como el acceso a la misma para su uso. 1
La consecución de estas etapas no requiere un desarrollo independiente y cronológico, ya que existirán distintos puntos del proyecto en los que se trabaje de manera paralela en algunas de estas fases, intercalando trabajo de ambas entre sí. Durante la etapa de desarrollo se contará con un pool de requisito, desde el que se elige que requisito implementar durante el sprint de duración variable. De tal forma que finalizado el sprint el requisito se evalua si se ha conseguido completar o por el contrario podemos continuar desarrollando en un nuevo sprint, en cuyo caso volvería al pool de requisitos. Siendo posible dedicar el próximo sprint a otro requisito o volver a continuar con los requisitos no completos Siguiendo esta planificación se espera lograr la realización del proyecto para el día 1 de Septiembre, contando con un margen de maniobra de 6 días hasta la fecha tope establecida del 7 de septiembre.
Capítulo 3 Análisis de requisitos En este capítulo procedemos a detallar los distintos requisitos que debe satisfacer la aplicación que se pretende desarrollar, para ello los clasificaremos en tres grupos, requisitos funcionales, requisitos no funcionales y requisitos de la API. De este modo se establecen los objetivos que se buscan alcanzar en el desarrollo de la aplicación y se establece de manera formal los límites del proyecto. Los requisitos de información no tienen lugar en este proyecto debido a que no se guarda ningún tipo de información en nuestro sistema, toda la información procede de la API de Trakt.tv, página encargada de recopilar y almacenar todo tipo de información referente a películas y series de televisión. 3.1. Requisitos funcionales Los requisitos funcionales son los que describen la funcionalidad o lógica que debe tener nuestro sistema. De este modo se establecen las posibles acciones que puede llevar a cabo un usuario en nuestra aplicación, definiendo a su vez el comportamiento de la misma. Los requisitos funcionales que se establecen para el proyecto son los que se pueden ver a continuación: RF0 - Consultar peliculas por popularidad. 9
10 3.2. Requisitos no funcionales RF1 - Consultar información de una película. RF2 - Consultar series por popularidad. RF3 - Consultar información sobre una serie. RF4 - Consultar información sobre una persona que participa en una serie o película. RF5 - Consultar información sobre una temporada de una serie. RF6 - Realizar una búsqueda por nombre de películas, series, o personas. RF7 - Filtrar películas. RF8 - Filtrar series. 3.2. Requisitos no funcionales Los requisitos no funcionales son aquellos que describen características o condiciones que debe de cumplir el software, sin llegar a especificar ningún tipo de funcionalidad adicional, ya que esto se recoge en los requisitos funcionales, ni describen información a guardar, ya que eso es objetivo de los requisitos de información. Los requisitos no funcionales que se establecen para el proyecto son: RNF0 - Facilidad de uso. RNF1 - Interfaces intuitivas. RNF2 - Acceso multiplataforma. RNF3 - Tiempo de respuesta corto. RNF4 - Tolerancia a fallos. RNF5 - Sistema en tiempo real. RNF6 - Manejo de errores. 10
Capítulo 3. Análisis de requisitos 11 3.3. Requisitos de la API Como se ha comentado anteriormente, para poblar de información nuestro sistema usaremos una API externa perteneciente a Trakt.tv. De esta forma la aplicación Web a desarrollar contará con una gran cantidad de información recopilada a lo largo de los años y que día tras día este contenido va en aumento con la adición de nuevos datos. Figura 3.1: Trakt.tv La información que podemos encontrar se estructura en objetos bajo el formato JSON, a los que podemos acceder a través de la URL de la propia API https://api.trakt.tv, donde todas las operaciones que se realicen deben realizarse sobre SSL (Secure Sockets Layer, en español Çapa de puertos seguros"). Los objetos estándar de información que nos podemos encontrar son Movie, Show,Season,Episode,Person. Un ejemplo del objeto Movie puede ser como el que aparece en el Listado 3.1. Listing 3.1: Ejemplo de Objeto Movie { "title" : "Batman v Superman: Dawn of Justice" , "year" : 2016, "ids" : { "trakt" : 129583, "slug" : "batman-v-superman-dawn-of-justice-2016" , "imdb" : "tt2975590" , "tmdb" : 209112 } } 11
12 3.3. Requisitos de la API Por defecto, las consultas que se realicen sobre la API devolverán la mínima información del objeto, como se muestra en el Listado 3.1. Pero también es posible indicar a la API que requerimos más datos acerca de un objeto en cuestión. Trakt.tv define dos tipos de categorías para la información adicional, que son full eimages, lo que significa que realizando una petición con full nos devolvería toda la información referente al objeto, mientras que con images se nos proporcionaría todas las imagenes asociadas al mismo, ofreciendo a su vez distintos tipos de foto, de las que cuenta con distintos tamaños para adaptarse a las necesidades del desasrrollador. Para indicar una de estas categorías o ambas, solo debemos añadir a la URL de la consulta la siguiente expresión "?extended=categoría", de modo que si lo que se desea es pedir toda la información e imagenes de la película que aparece en el Listado 3.1, la URL necesaria para realizar la consulta de información extendida quedaría del siguiente modo "https://api.trakt.tv/movies/1?extended= full,images", donde el número 1 hace referencia al identificador de la película en cuestión, como aparece en el Listado 3.1. En algunas situaciones las consultas pueden resultar no ser sobre un objeto en cuestión, sino que por el contrario se requiere una lista de objetos, como por ejemplo las series más populares, por tanto lo que se recibirá no será un objeto JSON, en su lugar será un array (lista de objetos) de objetos. Por defecto, este tipo de consultas nos enviará una lista con 10 objetos, ya que Trakt.tv así lo define. Sin embargo, si se desea obtener más objetos y de hecho dividir la lista por páginas, que por defecto es 1, se le puede indicar a la API del mismo modo que se realizaba anteriormente en las consultas de información adicional. Para el caso de requerir objetos adicionales debemos añadir como parámetros a la URL la siguiente expresión "?page=npage&limit=nlimit", donde npage es el número de páginas en la que se desea dividir la lista y nlimit el número de elementos por página. Así por ejemplo, si se desease recibir dos páginas con 20 elementos de las series más populares, la URL resultante sería del siguiente modo "https://api.trakt.tv/shows/popular?page=2&limit=20". 12
Cabe destacar que tanto los parámetros de limit ypage, como el parámetro extended, se pueden combinar en consultas en las que la API especifiqué su validez. Para más información sobre la API de Trakt.tv puede consultar la documentación [1] oficial en el siguiente sitio Web "http://docs.trakt.apiary.io/#". Además debemos tener en cuenta que todas las consultas que se realicen sobre la API deben contar con una serie de cabeceras requeridas por Trakt, donde las variables necesarias son: Content-type Indica el tipo de contenido que se espera recibir, por tanto en este caso su valor ha de ser .aplication/json", ya que todos los objetos de la API están bajo el formato JSON. Trakt-api-version Como indica la API debe ser el valor 2. Trakt-api-key Donde se asocia la clave de la API que proporciona Trakt a cada desarrollador que la solicita. De este modo se realiza una identificación de la aplicación que está realizando las operaciones sobre la API. Por último, para resumir y expresar de manera más formal los requisitos derivados de la API, mostramos un pequeño listado de los mismos. RA0 - Consultas sobre SSL. RA1 - Uso de parámetros, si se requiere. RA3 - Uso de cabeceras requeridas. 1 1Nota para el lector: En este capítulo se ha empleado una nomenclatura especial para referirnos a los distintos requisitos, por ejemplo RF0, esto simplemente quiere decir Requisito Funcional 0, de modo que podamos identificarlos fácilmente en caso de ser necesario referirnos a un requisito en cuestión. Lo mismo ocurre con RNF0 yRA0, Requisito No Funcional 0 y Requisito Api 0, respectivamente.
Capítulo 4 Diseño En este capítulo trataremos los aspectos más destacables de la fase de diseño para la ejecución del proyecto, desde la estructura que se desea seguir para organizar la funcionalidad, hasta los patrones de diseño que se seguirán en la creación de interfaces de la aplicación, seguido de los casos de uso junto con el comportamiento esperado del sistema. Esta fase resulta una parte muy importantes del proyecto ya que establece las bases del trabajo a realizar a lo largo del desarrollo de la aplicación Web, ya que expondremos los distintos puntos que conciernen al diseño, mostrando las decisiones tomadas sobre cómo organizar la aplicación, de acuerdo al aspecto visual y funcional marcados como objetivos. 4.1. Organización de la funcionalidad Al tratarse de una aplicación de una sola página, ya que hablamos de un sistema desarrollado con AngularJS, contaremos con una página de elemento central (index.html), en la que estará asociado el módulo de nuestra aplicación que contendrá todo el apartado de funcionalidad (myApp.js). Aprovechando los servicios que nos ofrece este framework de desarrollo Web, organizaremos la funcionalidad referente a cada una de las vistas en un controlador independiente para cada uno de las interfaces. De este modo contamos con 15
16 4.2. Diseño de interfaces una serie de vistas con su controlador asociado, lo que nos permite el desarrollo y prueba de cada una de estas vistas de forma aislada a las restantes partes del sistema. De esta manera también podemos organizar las distintas configuraciones que debemos establecer sobre el servicio $resource para realizar las peticiones oportunas de información, requerida por cada vista y cada consulta en particular. 4.2. Diseño de interfaces En cuanto a las interfaces, se ha optado por un diseño simple e intuitivo, para que permita a cualquier usuario usar el sistema de manera sencilla. Para ello contamos con un diseño que se divide en parte estática (común a todas las vistas) y parte dinámica que cambia en función de la interacción del usuario con la aplicación. En la parte estática nos encontramos con la barra de navegación, que debe permitir el movimiento del usuario a las distintas secciones que ofrece la Web, como son página principal, películas, series, contacto y breve resumen acerca de la aplicación. Mientras que en la parte dinámica nos encontramos con un cuerpo central que cambia en función de las vistas requeridas, de modo que cada una de las vistas está almacenada en un archivo HTML independiente, donde se encuentra la parte de código que se requiere inyectar en la página principal, así mantenemos un control independiente de cada una de las vistas, facilitando su desarrollo y testeo. 4.3. Casos de uso y comportamiento del sistema Los casos de uso presentes son compartidos por cualquier usuario que utilice la aplicación, ya que no se realiza una distinción de roles de acceso, por el hecho de que no se encuentra ningún tipo de funcionalidad presente en el sistema que 16
Capítulo 4. Diseño 17 requiera de ello. Figura 4.1: Casos de uso Consultar Películas Cuando el usuario acceda al apartado de películas el sistema deberá mostrar un listado con las películas que están de moda, siendo posible la petición de películas populares o películas que están siendo vistas. Filtrar Películas Si el usuario lo desea podrá realizar un filtrado de las películas mostradas en el sistema en función de su género, además en la categoría watched se puede pedir un filtrado por períodos de tiempo, como pueden ser semana, mes, año. Consultar Película Cuando el usuario quiera consultar información de una película, ya sea porque realiza una búsqueda, porque selecciona una película de la sección películas o por hacer clic sobre una película en la que apareció un actor, debe mostrar toda la información posible sobre la misma, presentando la información de manera organizada. Consultar Series Cuando un usuario accede al apartado de series el sistema deberá mostrar un listado con las series que están de moda, siendo posible la petición de series más populares o series que están siendo vistas. 17
24 5.2. AngularJS Esta aplicación nos ha brindado la posibilidad de testear y analizar las operaciones implementadas en la aplicación y comprobar su correcto funcionamiento antes de ser implantadas en la misma LiveReload Aplicación de escritorio (aunque también existe un plugin para Sublime Text) encargada de registrar los cambios realizados sobre un archivo y auto refrescar las páginas asociadas al archivo editado en el navegador Web. Esta herramienta ha resultado especialmente útil en la elaboración de vistas de la aplicación. Git Herramienta para el control de versiones. El control de versiones ha resultado muy útil para la etapa de desarrollo, permitiendo llevar un mejor control de los distintos hitos alcanzados. Al igual que con otras partes del aprendizaje que nos resultaron más interesantes, nos hubiese gustado profundizar más en este aspecto, pero la planificación preestablecida para la realización de cada fase del proyecto no permitía emplear más tiempo para profundizar en mayor detalle. Deployd Herramienta para la creación de APIs REST de forma fácil y práctica. Con esta herramienta se pudo crear una API propia con la que realizar pruebas, con el fin de comprender el funcionamiento de los servicios $http y$resource durante la fase de aprendizaje. 5.2. AngularJS Ya hemos comentado qué es Angular, pero aún no hemos hablado de los conceptos básicos de este framework. Esta sección está dedicada a realizar un pequeño paseo por Angular, por lo que si el lector ya conoce cómo trabajar con esta tecnología, puede saltarse este apartado y continuar con la sección de "Gestión de controladores". Angular ofrece una serie de etiquetas o directivas que permiten extender el uso de HTML, de tal forma que podemos incluir mayor funcionalidad o usar elementos que antes no eran posibles. Las directivas en Angular, como ya hemos 24
Capítulo 5. Desarrollo 25 comentado, son etiquetas, etiquetas especiales que indican al compilador que su funcionamiento proviene de las librerías de Angular. Además otro aspecto importante es la no necesidad de manipulación del DOM (Document Object Model) para enviar información desde el modelo a la vista a través de los controladores. Simplemente se emplea el objeto $scope que se encarga de hacer accesible la información de las variables o funciones que declaremos en su ámbito. Para ilustrar algunas de las partes fundamentales de toda aplicación en Angular podemos ver el Listado 5.1, donde se muestra un código de ejemplo de una aplicación sencilla. Listing 5.1: Código de aplicación simple <html ng−app= "myApp" > <head> <title> Ejemplo </ title> <script src= "js/angular.min.js" >< / script> <script> var app = angular . module ( "myApp" , [ ] ) ; app . controller ( "MyCtrl" , function ( $scope ) { var someData = { firstName : ’ JENNA ’ , surname : ’ GRANT ’ } ; $scope . data = someData ; } ) ; </ script> < / head> <body ng−controller= "MyCtrl" > <p> <strong> First Name </ strong> : { { data . firstName } } <br /> <strong> Surname :< / strong> { { data . surname } } </p> </body> </ html> Uno de los primeros pasos en Angular es crear un módulo en el que desarrollar, para ello se emplea el método angular.module, asignándole un nombre e inyectando dependencias de módulos independientes al principal con el que cuenta Angular. Como es un ejemplo sencillo no es necesario módulo adicional y por tanto queda vacío este argumento "[]". Por ejemplo, en caso de querer usar 25
26 5.3. Gestión de controladores ngResource, como explicaremos en secciones posteriores, solo habría que añadir ["ngResource"]. Una vez contamos con el módulo, podemos crear controladores asignados a ese módulo con el método .controller, y a partir de ahí podemos comenzar a desarrollar funcionalidad para nuestro sistema. La manera de asociar módulos y controladores a las páginas HTML es mediante las etiquetas ng-app yngcontroller, de tal forma que el ámbito de acción del mismo se extenderá desde el elemento en el que la hemos incluido hasta sus sucesivos hijos. Para acceder a la información enviada a través del objeto $scope hacia la vista solo tenemos que usar la expresión {{ }}, estas dobles llaves indican al compilador la presencia de variables o funciones de Angular. Además de crear controladores en nuestro módulo, se pueden crear constantes con el método .constant( identificador , valor ), o crear servicios personalizados con .factory( identificador , función )o.service( identificador , función ). 5.3. Gestión de controladores En esta sección hablaremos sobre los distintos controladores que se han utilizado para ofrecer funcionalidad en las distintas páginas que componen la aplicación final, como del modelo de los datos. Como comentamos en apartados anteriores, la idea principal de Angular está centrada en el patrón de arquitectura software MVC (Modelo Vista Controlador). Dentro de estas tres partes diferenciadas, lo que se busca es separar de manera visible la representación final de los datos hacia el usuario, de la lógica de la aplicación, facilitando de esta manera el desarrollo de la presentación y la funcionalidad. Inicialmente hablaremos de los componentes Modelo yControlador, ya que están más relacionados con la temática de la sección actual y posteriormente se tratará el elemento de la Vista, para así completar el recorrido por el patrón de 26
Capítulo 5. Desarrollo 27 diseño MVC. Atendiendo a la definición de MVC, el Modelo es la representación de la información usada en el sistema, por lo tanto gestiona todos los accesos a los datos en cuestión y envía a la Vista la información que en cada momento se necesita para que sea mostrada hacia el usuario final. Mientras que el Controlador es el encargado de responder a los eventos que se originan en la aplicación por parte de la interacción del usuario o cualquier otro tipo de evento relacionado. Estas peticiones se envían al Modelo cuando se requiere alguna parte de la información con la que este cuenta (por ejemplo, listar las películas más populares). Al ser el nexo de unión entre el Modelo y la Vista, se encarga de enviar la información a la Vista asociada y si fuese necesario también puede enviar cierta información de control que se encargue de adaptar la Vista para determinados tipos de representación de la información. Durante el desarrollo del proyecto se han creado un total de 11 controladores, cada uno de ellos asociado a su vista. Un listado de los controladores con su vista asociada sería como el que sigue: Figura 5.1: Asociación de controladores y vistas 27
28 5.3. Gestión de controladores Para la asociación de controladores a sus vistas y el cambio dinámico de uno a otro, conforme el usuario interacciona con la aplicación, aparece el módulo de Angular ngRoute. NgRoute es un módulo de Angular que provee de una serie de servicios y directivas para la creación y configuración de rutas en aplicaciones desarrolladas con este framework. Uno de estos servicios que lo conforman es $routeProvider, es un servicio que nos permite establecer las relaciones entre controladores y vistas, así como los cambios de uno a otro en función de la URL actual. Un ejemplo de la configuración que se realiza con ngRoute es el que podemos ver en el Listado 5.2. De igual modo se procede a declarar todos los cambios posible de la URL, asociando así cada controlador a su vista. Listing 5.2: Código parcial de la configuración de ngRoute app . config (function ($routeProvider) { // configure the routes $routeProvider . when ( "/" , { // route for the home page templateUrl : "pages/home.html" , controller : "homeController" } ) . when ( "/movies" , { // route for the movies page templateUrl : "pages/movies.html" , controller : "moviesController" } ) } ) ; En concreto, como ya se mencionó en anteriores capítulos, las aplicaciones desarrolladas con Angular son aplicaciones de una sola página o SPA (Single Page Applications), de forma que cuando cambiamos de una página a otra, la aplicación no recarga por completo la página, sino que el contenido cambia dinámicamente. Al igual que ocurre con el controlador y la URL, la Vista, cambia su contenido en función de la URL actual mediante el uso de ngRoute. Para esto interviene 28
Capítulo 5. Desarrollo 29 una directiva presente en el módulo ngRoute, la directiva ngView, de tal forma que en la página principal de nuestra aplicación (página que contendrá el contenido estático de la aplicación, como por ejemplo barra de navegación) se indica el lugar donde se inyectará el código HTML requerido por cada vista. Un ejemplo de ello es el que se puede apreciar en el Listado 5.3. Listing 5.3: Código parcial de index.html <div id= "main" > <! -- La directiva ngView indica donde se inyectará el código -- > <div ng−view>< / div> </ div> El código del Listado 5.3, se encuentra presente en nuestra página principal, index.html, donde podemos encontrar el código de la barra de navegación y el código del Listado 5.3, que indica el lugar exacto donde el contenido se cambia dinámicamente. Una vez asociados los controladores a las vistas, cada uno de ellos actuará cuando su vista sea la vista actual. De tal forma que de manera independiente desarrollamos y controlamos la funcionalidad de cada página de la aplicación. En el Listado 5.4 podemos ver un ejemplo de uno de los controladores que forman parte del sistema desarrollado. Listing 5.4: Código parcial de moviesController app . controller ( "moviesController" ,function ($scope ,$http ,$resource , baseUrl , ,→$location , ShareFactory , SearchFactory ) { var trendingResourceImages =$resource ( baseUrl + "movies/trending?extended=images& ,→ page=1&limit=20" ) ; var resourceFilmFullImages =$resource ( baseUrl + "movies/:trakt?extended=full,images ,→ " ,{ trakt : "@trakt" } ) ; $scope. getTrending =function ( ) { var aux = trendingResourceImages . query ( ) ; aux . $promise . then (function ( data ) { $scope. peliculas = data ; $scope. view = "normal" ; $scope. message = "Trending" ; } ) .catch(function ( error ) { $location. path ( "error" ) ; 29
30 5.4. Gestión de vistas } ) . finally (function ( ) { $scope. spinner =false; } ) ; } $scope. mostrarPelicula =function ( film ) { var item = resourceFilmFullImages . get ( film . movie . ids ) ; item . $promise . then (function ( data ) { ShareFactory . setShareItem ( data ) ; $location. path ( "movie" ) ; } ) .catch(function ( error ) { $location. path ( "error" ) ; } ) ; } $scope. lookUp =function ( search ) { SearchFactory . lookUp ( search ) ; $location. path ( "search" ) ; } } ) ; 5.4. Gestión de vistas Esta sección se centra en describir las distintas vistas a las que tiene acceso el usuario y cómo se organizan entre sí, para completar así el recorrido del MVC, donde el elemento objeto de explicación es la Vista. Atendiendo nuevamente a la definición del patrón MVC, la Vista presenta de forma adecuada el Modelo (información del sistema) en un formato adecuado para el usuario de manera que este pueda interactuar con los datos y con la aplicación de la forma que marca la funcionalidad del sistema. En todo momento las acciones que se realicen serán enviadas hacia el Controlador que será el encargado de gestionar los cambios necesarios para que la Vista se adapte a las necesidades del usuario en cada momento, atendiendo a la lógica existente en el sistema. Como vimos en la sección anterior, el módulo ngRoute se encarga de asociar controladores a vistas y establecer cuál será el actual en función de la URL del momento. Para que esto ocurra deben de registrarse cambios en la URL y es 30
Capítulo 5. Desarrollo 31 en ese momento cuando aparece otro servicio de angular denominado $location. Antes de describir este servicio vamos a explicar el sistema de URL de Angular, ya que se utiliza una URL en modo hashbang, como podemos apreciar en la Figura 5.2. Figura 5.2: AngularJS URL En este tipo de URL las partes de la misma se mantienen, añadiendo un separador. Como muestra la Figura 5.2, el primer hashtag ("#") muestra la separación entre la URL base de la aplicación, en nuestro caso sería la página index.html, y la parte que se modifica dinámicamente, que en nuestro caso solo será la parte de path. El uso de otras secciones de la URL se encuentra disponible en este tipo de enlace, al igual que en los normales, pero para nosotros no ha sido necesario usarlas. $location es un servicio de angular que se encarga de parsear la URL del navegador y hacerla accesible desde la aplicación. Cualquier cambio que se produzca en la URL se verá reflejado en el servicio y viceversa. Por lo que existen dos formas de cambiar la URLs usadas en nuestro sistema. En primer lugar mediante la barra de navegación, como podemos apreciar en el Listado 5.5, mientras que la otra forma es usar el servicio $location, para ello desde los métodos del controlador en los que se requiere un cambio de vista 31
32 5.4. Gestión de vistas se emplea el método path, de tal forma que si queremos cambiar a la vista movie.html, se escribiría $location.path("movie"). Listing 5.5: Código parcial de la barra de navegación <ul class= "nav navbar-nav navbar-right" > <l i ><a href= "#/" >< i class= "fa fa-home" aria−hidden= "true" >< / i> Home < / a></ l i > <l i ><a href= "#movies" >< i class= "fa fa-film" aria−hidden= "true" >< / i> Films < / a></ l i > <l i ><a href= "#shows" >< i class= "fa fa-ticket" aria−hidden= "true" >< / i> Shows < / a></ l i > </ ul> El método path sin argumentos, es un método del tipo GET, o dicho de otro modo es un método que nos devuelve el valor de la parte path de la URL, como vimos en la Figura 5.2, mientra que path con argumento, es un método del tipo SET, que quiere decir, que se cambia el valor del apartado path de la URL por la cadena que se pasa por argumento. En cuanto a las vistas, nos encontramos con un total de 11 páginas HTML, donde index.html es nuestra página principal. De hecho es la página que define aspectos como la inyección dinámica de contenido HTML según la vista que requiera el usuario. Además se encarga de designar el ámbito del controlador y el ámbito del módulo creado para ofrecer funcionalidad en el sistema, que en nuestro caso se llama app. Entre el conjunto de vistas de la aplicación Web nos encontramos con una serie de páginas HTML enfocadas a satisfacer los requisitos establecidos durante el capítulo 3, además de algunas páginas adicionales para mostrar información adicional. El conjunto de páginas HTML está formado por: Index Se trata de la página principal de la aplicación, en ella se define el contenido estático del sistema y además se incluyen las directivas para definir el ámbito de módulos y controladores, así como el lugar donde se producirá la inyección de código HTML en función de las vistas requeridas. Movies En esta vista el usuario verá por defecto las películas que están de moda o dicho de otro modo películas trending. 32
Capítulo 5. Desarrollo 33 Aparecen en la parte superior tres botones que nos permiten cambiar el contenido del listado por las películas más populares a lo largo de los años o simplemente ver que películas se están viendo últimamente. Además a estos listados podemos realizar un filtrado por géneros y en la categoría watched también por período de tiempo. Si el usuario desea ver información sobre una película en cuestión, solo tiene que hacer clic sobre la imagen de la película que desea conocer o sobre su título. Movie Esta página ofrece información sobre una película, desde su valoración, título, sinopsis, ... , hasta información sobre las personas que han participado en la misma y comentarios sobre el largometraje. Si el usuario lo desea se incluye el trailer de la película que puede ser reproducido en la propia página de la aplicación. Además si quiere conocer más detalles acerca de algún actor en particular, puede realizar clic sobre la imagen o nombre de la persona deseada. Shows Al igual que ocurre con la página movies.html, en esta vista podemos consultar los listados de series en función de la categoría popular, trending o watched, filtrar la información. Así como consultar más información acerca de una serie en cuestión. Show La página show.html es la encargada de mostrar toda la información relevante de una serie, ofreciendo datos similares a los que oferta la página movie.html, incluyendo en esta las distintas temporadas con las que cuenta una serie, por lo tanto la consulta sobre una temporada en cuestión se encontrará también accesible a través de su imagen o título, como ocurre con los actores participantes. Season En la vista season.html el usuario puede ver los datos referentes a la temporada seleccionada de una serie e información sobre los capítulos de la misma, datos que incluyen una descripción del capítulo, la valoración de los usuarios, entre otros. Person La página person.html se encarga de presentar los datos referentes a una persona, donde nos podremos encontrar su información básica, una 33
40 5.6. Otros elementos también suponen de bastante importancia, facilitando el desarrollo del sistema. En primer lugar vamos a tratar la directiva ng-repeat, este es uno de los más usados, ya que su finalidad es tomar un array o lista desde el objeto $scope y mostrarlo en la vista. Para ello lo que realiza es una copia de todo el código HTML que se encuentra entre el elemento donde se introduce la etiqueta, hasta su último hijo. En el Listado 5.10 podemos ver un ejemplo de presentación de las películas más populares, para ello ng-repeat copia el mismo código HTML para todos los elementos de la lista, y muestra para cada uno de ellos la información de su ítem asociado. Listing 5.10: Código parcial de movies.html <div class= "col-md-3" ng−repeat = "item in peliculas" ng−show = "view == 'popular'" > <a href= "" ng−click= "mostrarPeliculaPopular(item)" > <img ng−src= "{{item.images.poster.thumb}}" width= "100 %" height= "300px" id= " ,→ photoBorder" >< /img> < / a> <div class= "text-center" > <h4> <a class= "text-muted" href= "" ng−click= "mostrarPeliculaPopular(item)" > { { item ,→. title } } < / a> </ h4> <p><small> { { item . year } } < / small>< / p> </ div> </ div> Otra directiva interesante que podemos apreciar en el Listado 5.10 es ngshow, la funcionalidad de ng-show evalúa la expresión que se le pasa como argumento, siendo posible la utilización de variables de Angular, como en ese caso, view que es una variable declarada en el ámbito de $scope. Esta variable puede tomar los valores popular o normal, cuando esta toma el valor popular la expresión se evaluará al valor true, todo el contenido que hereda de esa etiqueta se mostrará por pantalla, mientras que en caso contrario esa porción de código HTML quedaría oculta en la vista. Una directiva similar a ng-show, que también hemos usado es ng-if, donde se evalúa la expresión asociada a los valores true o false, como si de una sentencia if (sentencia con la que cuentan numerosos lenguajes de programación para evaluar condiciones lógicas) se tratase y en función del resultado se muestra el 40
Capítulo 5. Desarrollo 41 contenido del código HTML que se encuentra bajo la influencia de la directiva ng-if. En el Listado 5.10, podemos observar también la aparición de otra directiva llamada ng-click, esta directiva se encarga de controlar la acción de clicado sobre objetos por parte del usuario, de tal forma que cuando detecta ese evento, se lanza la ejecución del método o función que le hayamos asignado. En este caso se pide al controlador que la aplicación muestre la vista de movie.html con la información de la película que se pasa por argumento. La última directiva que podemos ver en el Listado 5.10 es ng-src, su función es la misma que el atributo src presente en HTML, con la salvedad de que ngsrc permite incluir variables de Angular procedentes del ámbito de $scope. Del mismo modo existe una directiva llamada ng-href, que cumpliría con el mismo propósito en sustitución de href, atributo perteneciente a HTML. Por último comentar la directiva ng-style que permite establecer directamente a los elementos HTML reglas de estilo CSS. Como podemos ver en el Listado 5.11, al igual que funciona la etiqueta style, en los elementos HTML, la directiva ng-style permite la asignación de reglas de estilo a los elementos en los que se incluye. Listing 5.11: Código parcial de movies.html <div class= "col-md-4 col-md-offset-1" ng−style= "{'padding-bottom':'40px'}" > <img class= "img-responsive" ng−src= "{{item.images.screenshot.thumb}}" id= " ,→ photoBorder" > </ div> También nos encontramos con la necesidad de crear dos servicios que nos permitieran llevar a cabo ciertas acciones necesarias para el desarrollo. En primer lugar nos encontramos con ShareFactory (presente en el Listado 5.12), que es un servicio que creamos para enviar datos de un controlador a otro, ya que en determinadas vistas realizamos una consulta y esperamos a su resolución para acceder a la siguiente interfaz con todos los datos cargados. Para ello entra en juego ShareFactory que nos permite almacenar hasta dos objetos a los que podemos acceder desde otro controlador en el que se haya inyectado como de41
pendencia este servicio. El segundo de los servicios creados recibe el nombre de SearchFactory, cuyo papel es parecido al de ShareFactory con la salvedad de que este recopila la información necesaria para realizar una consulta, desde el formulario de la barra de navegación y la prepara de manera adecuada para que desde la vista search.html se realice la consulta hacia la API y se muestren los resultados hacia el usuario. Para ilustrar la creación de un servicio en Angular podemos ver el Listado 5.12, donde se encuentra el código perteneciente al servicio ShareFactory. Listing 5.12: Código del servicio ShareFactory //############ Servicio para intercambiar información de una vista a otra ############ app . factory ( "ShareFactory" ,function ( ) { var myShareFactory = { } ; myShareFactory . shareItem =null ; myShareFactory . auxShareItem =null ; myShareFactory . setShareItem =function ( value ) { th is . shareItem = value ; } ; myShareFactory . setAuxShareItem =function ( aux ) { th is . auxShareItem = aux ; } ; return myShareFactory ; } ) ;
Capítulo 6 Pruebas En este capítulo nos centraremos en el apartado de pruebas, donde describiremos las herramientas empleadas para el diseño y desarrollo de las mismas, así como comentar las distintas pruebas que se han realizado sobre el código de la aplicación. Como indica la documentación de AngularJS [2], JavaScript es un lenguaje de tipado dinámico, donde los objetos cambian constantemente sobre una misma variable junto con sus valores y tipos, lo que proporciona una gran expresividad, con la carencia de la falta de ayuda por parte del compilador. Por esta razón desde Angular piensan que el código escrito en JavaScript debe estar minuciosamente probado, para favorecer esto, proporciona una serie de herramientas que facilitan el desarrollo de pruebas sobre aplicaciones desarrolladas con Angular, para que no existan excusas por parte del programador a la hora de testear su sistema. 6.1. Karma y Jasmine En esta sección hablaremos sobre las herramientas empleadas para el desarrollo y ejecución de las pruebas, entre las que se encuentran Karma, Jasmine y ngMock. Karma es una aplicación desarrollada en NodeJS, que proporciona una herramienta de línea de comandos JavaScript. Su objetivo principal es generar 43
44 6.1. Karma y Jasmine un servidor Web donde ejecutar el código de la aplicación y realizar las pruebas. Karma cuenta con una serie de opciones de configuración, que nos permite por ejemplo ejecutar las pruebas sobre distintos buscadores, para asegurarnos de que nuestro sistemas funcionan correctamente en cualquier navegador Web. Nosotros hemos optado por realizar las pruebas sobre Chrome y Firefox, dos de los navegadores más extendidos en cuanto a uso por parte de los usuarios. Para la ejecución de Karma se emplea la línea de comandos del sistema operativo, donde se muestran los distintos resultados obtenidos. En la siguiente sección se mostrarán las salidas obtenidas tras la ejecución de las pruebas realizadas sobre el sistema desarrollado. Jasmine es un framework para pruebas sobre JavaScript. Resulta ser el más popular para realizar pruebas sobre aplicaciones desarrolladas con Angular, ya que proporciona una serie de funciones que ayudan a estructurar las pruebas y a realizar asertos para crear casos de prueba. Además Jasmine se puede combinar con el módulo de Angular ngMock. ngMock es un módulo perteneciente a Angular que permite la inyección y simulación, en pruebas unitarias, de los distintos elementos existentes en Angular. Gracias a este módulo se facilita en gran medida la fase de pruebas de este tipo de aplicaciones, permitiendo testear las distintas partes del sistema. Figura 6.1: Karma y Jasmine para pruebas unitarias sobre AngularJS 44
Capítulo 6. Pruebas 45 6.2. Pruebas unitarias Las pruebas unitarias tienen como objetivo aislar distintas partes del código de una aplicación, para someterlas de manera independiente a una serie de pruebas. De esta manera se puede comprobar que los distintos módulos de una aplicación realizan la tarea que se espera que lleven a cabo. Por ello en esta sección explicaremos y mostraremos los distintos casos que se han configurado para testear nuestro sistema. Durante el análisis de las pruebas, antes de su desarrollo y ejecución, se decidió que al contar con distintas partes del código donde se emplean los mismos servicios de Angular, o que en su lugar cuentan con un desarrollo similar en busca de un mismo objetivo, se realizarán pruebas en base al servicio común, de tal forma que se aprecie el correcto funcionamiento del elemento que interviene, simplificando y reduciendo el número de pruebas. Por ello se determinó que las pruebas deben abarcar: Comprobar la correcta carga del módulo de la aplicación Comprobar la correcta carga de un controlador • Correcta creación de variables Comprobar el correcto funcionamiento del servicio $http, con petición de un objeto • Correcta petición • Objeto existente • Objeto recibido igual al esperado Comprobar el correcto funcionamiento del servicio $http, con petición de varios objetos • Correcta petición • Objetos existentes 45
46 6.2. Pruebas unitarias • Objetos recibidos igual a esperados • Orden de objetos recibidos igual a orden esperado Comprobar el correcto funcionamiento de un servicio personalizado • Correcta inyección • Correcta creación de variables • Correcta asignación de valores • Correcta petición de valores Para ello se han estructurado las pruebas en dos archivos distintos, donde agrupamos las pruebas del controlador en controllerTest.js por un lado, mientras que por otro tenemos las pruebas sobre el servicio, en el archivo serviceTest.js. Para agrupar un conjunto de pruebas y estructurar así los asertos que se aplican sobre un elemento común, se usa el método describe({nombre}, function () { {cuerpo de pruebas} }, donde {nombre} sería la cadena que identifica a este conjunto, mientras que {cuerpo de pruebas} sería todas las sentencias que recoge este conjunto, con las distintos casos de prueba que se hayan incluido. Para comprobar la correcta carga de un módulo solo debemos emplear el método angular.mock.module({nombre módulo})y si además queremos inyectar dependencias con algún servicio de angular o servicio que hayamos definido previamente, se puede emplear el método angular.mock.inject(function ({servicio}) { {cuerpo} }), un ejemplo de ambos se encuentra en el Listado 6.1. Como se puede apreciar ambos están incluidos en el método beforeEach, que indica que la carga e inyección se realizará antes de cada prueba unitaria. Listing 6.1: Código parcial de serviceTest.js //Definimos variables var myService ; //Cargamos el módulo beforeEach ( angular . mock . module ( "myApp" ) ) ; //Inyectamos el servicio 46
Capítulo 6. Pruebas 47 beforeEach ( angular . mock . inject (function ( ShareFactory ) { myService = ShareFactory ; } ) ) ; Al igual que ocurre en el Listado 6.1, además de inyectar servicios para dependencias, también es posible realizar la carga de los controladores con sus respectivas dependencias. Como podemos ver en el Listado 6.2, se realiza la carga de un controlador, así como la inyección de los servicios que utilizará el mismo. Listing 6.2: Código parcial de controllerTest.js //Cargamos el controlador e inyectamos dependencias beforeEach ( angular . mock . inject (function ( $controller , $rootScope ,$http ) { mockScope = $rootScope . $new ( ) ; controller = $controller ( "defaultCtrl" ,{ $scope: mockScope , $http:$http } ) ; backend . flush ( ) ; } ) ) ; Una vez se realiza la carga de modulos, controladores e inyección de servicios, se procede con la realización de las pruebas, para ello se definen asertos que conforman los distintos casos de uso. Para definir cada caso de prueba se utiliza el método it(nombre prueba, function () { {cuerpo prueba} }) que ejecuta una función para realizar la prueba. Un ejemplo de casos de prueba se puede apreciar en el Listado 6.3. Listing 6.3: Código parcial de controllerTest.js it ( "Creación de variables" ,function ( ) { expect ( mockScope . pelicula ) . toBeDefined ( ) ; expect ( mockScope . peliculas ) . toBeDefined ( ) ; } ) ; it ( "Realizar peticiones" ,function ( ) { backend . verifyNoOutstandingExpectation ( ) ; } ) ; it ( "Procesar la respuesta de peliculas" ,function ( ) { expect ( mockScope . peliculas ) . toBeDefined ( ) ; expect ( mockScope . peliculas . length ) . toEqual (4) ; } ) ; it ( "Comprobar orden elementos peliculas" ,function ( ) { expect ( mockScope . peliculas [0]. movie . ids . trakt ) . toEqual (193079); 47
48 6.2. Pruebas unitarias expect ( mockScope . peliculas [1]. movie . ids . trakt ) . toEqual (176503); expect ( mockScope . peliculas [2]. movie . ids . trakt ) . toEqual (167397); expect ( mockScope . peliculas [3]. movie . ids . trakt ) . toEqual (129583); } ) ; Cabe destacar el empleo de la función verifyNoOutstandingExpectation(), proveniente del servicio $httpBackend, que se encarga de lanzar todas las peticiones HTTP que tengamos definidas para los casos de prueba, controlar su recepción y finalización correcta, de modo contrario nos mostrará una excepción en el caso de prueba correspondiente. El servicio $httpBackend nos permite simular las distintas acciones HTTP que realiza nuestra aplicación con lo que sería, por ejemplo en nuestro caso, la API de Trakt.tv, de tal forma que podemos definir las URL a usar, junto con su respuesta asociada y el método HTTP que se espera recibir en las peticiones a cada URL. Existen una gran cantidad de funciones pertenecientes a Jasmine para la comprobación de los resultados del test. Como vemos en el Listado 6.3, el método expect identifica el resultado de una prueba y a este se le pueden aplicar funciones de comprobación, entre las que están: expect(x).toEqual(val) x y val deben ser iguales (pero no quiere decir que sean el mismo objeto). expect(x).toBe(obj) x y obj deben ser el mismo objeto. expect(x).toMatch(regexp) x coincide con la expresión regular regexp expect(x).toBeDefined() x ha sido definido expect(x).toBeUndefined() x no ha sido definido expect(x).toBeNull() x es null expect(x).toBeTruthy() x es true o es evaluado a true expect(x).toBeFalsy() x es false o es evaluado a false 48
expect(x).toContain(y) la cadena x contiene a la cadena y expect(x).toBeGreaterThan(y) x es mayor que y Además de esto, a cada una de estas funciones se puede aplicar not para obtener el inverso de cada uno de ellas, un ejemplo sería expect(x).not.toBeNull(), donde se espera que x no sea null. Una vez se definen todos los casos de prueba podemos ejecutar karma, para conocer los resultados en cada uno de los navegadores y saber si estos se completan satisfactoriamente con lo esperado. Para ello se ejecuta el comando karma start karma.config.js desde la línea de comandos, como se ve en la Figura 6.2. Figura 6.2: Inicializando pruebas con Karma y Jasmine Ambos navegadores se inician, junto con karma para ejecutar las pruebas, comprobar los resultados e indicar por consola si se superan o no los distintos casos definidos. Como podemos ver en la Figura 6.3, todos los casos se superaron satisfactoriamente en cada uno de los navegadores.
Apéndice A Anexos técnicos A.1. Vista Movies.html 57
58 Apéndice A.2. Vista Movie.html 58
A.2. Vista Movie.html 59 59
60 Apéndice A.3. Vista Shows.html 60
A.4. Vista Show.html 61 A.4. Vista Show.html 61
62 Apéndice 62
A.5. Vista Season.html 63 A.5. Vista Season.html 63
A.6. Vista Person.html