Episodix: Integration Test & Unit Test – Iteración 1

A continuación se mostrarán los tests diseñados para la Iteración 1 de Episodix.

Integration Test

Se ha diseñado un Integration Test llamado Test_EndOfStreet que comprobará que FPSController (el personaje) y CheckEnd interactúan correctamente. Cuando FPSController atraviese CheckEnd, OnTriggerEnter lo detectará.

Para este test se ha creado una Scene que constará del suelo de Street, nuestro FPSController y el objeto CheckEnd. También habrá una cámara y una Directional Light. A FPSController se le ha desactivado el script FirstPersonController, encargado de mover al personaje en función de las entradas introducidas por el usuario mediante los controles de movimiento asignados, y se le ha añadido el script TestMove, que simplemente desplazará hacia adelante al personaje:

using UnityEngine;
using System.Collections;

public class TestMove : MonoBehaviour
{
// El FPSController del test se movera hacia adelante
//
void Update ()
{
transform.Translate (Vector3.forward*Time.deltaTime*10);
}
}

Al objeto CheckEnd se le añadirá un script CallTesting que dará por válido el test si otro objeto atraviesa (OnTriggerEnter) al propio CheckEnd.

IntegrationTest-CallTesting

El test tendrá una duración máxima de 5 segundos. Si en ese tiempo no se obtiene un resultado (válido o no) se tomará el test como fallido. En nuestro caso, al ejecutar el test, FPSController atravesará CheckEnd antes de que finalice la duración máxima de 5 segundos, por lo que el test tendrá un resultado positivo.

IntegrationTest-TimeOut

Unit Test

Se ha elaborado un Unit Test para comprobar que no hay errores a la hora de calcular los porcentajes de acierto en función de las respuestas acertadas y las respuestas totales contestadas para el caso de 3 preguntas. El código del Unit Test correspondiente estará en CalScoresTest.cs y su contenido es el siguiente:

using System;
using NUnit.Framework;

namespace CalScoresTest
{
public class CalScores
{
// Calcula el porcentaje de acierto dependiendo del numero de respuestas
// correctas y del numero de respuestas totales, parametros que se le
// pasaran como argumentos a la funcion.
//
public decimal GiveScores(decimal rightAnswers, decimal totalAnswers)
{
decimal scr = (rightAnswers / totalAnswers) * 100;
decimal scores = Math.Round(scr,2);
return scores;
}
}


public class TestScores
{
// Test que comprobara que los porcentajes calculados son los esperados
// dependiendo del numero de respuestas correctas y del numero de respuestas
// totales.
//
[Test]
public void Test()
{
CalScores scr = new CalScores();
Assert.AreEqual (100, scr.GiveScores (3, 3));
Assert.AreEqual (66.67M, scr.GiveScores (2, 3));
Assert.AreEqual (33.33M, scr.GiveScores (1, 3));
Assert.AreEqual (0, scr.GiveScores (0, 3));
}
}
}

Al ejecutar el test el resultado será positivo, de modo que el cálculo de los porcentajes de acierto realizado en nuestro juego es correcto.

UnitTest

Episodix: Iteración 1

A continuación se describirá la Iteración 1 del proyecto Episodix.

El estructura de la Iteración 1 será la siguiente:

  • Learning Phase: El usuario camina (al estilo FPS) por una calle vacía. En el lado derecho aparecen sólidos tridimensionales sencillos (cubo, esfera, cilindro).
  • Recall Phase:  Cuando llega al final de la calle el juego le presenta preguntas sí/no para comprobar si recuerda qué sólidos tridimensionales ha visto mientras caminaba por la calle.
  • Scores: Finalmente el juego presenta la puntuación obtenida. No se guarda en ningún lado.
Scene

La Scene iteration_1 es la única existente y se compondrá de los siguientes objetos:

  • Directional light: alumbrará nuestra Scene.
  • Street: será la calle vacía con los sólidos tridimensionales en su lado derecho. Además tendrá cuatro paredes invisibles para evitar que el jugador caiga al vacío. Se almacenará como prefab.

Scene-street

  • FPSController: personaje con vista en primera persona que manejará el usuario para moverse por la calle y observar el entorno. Está sacado de los Standard Assets de Unity. Se desplazará con las teclas WASD y empleará el ratón para mover la vista.

Scene-FPSController

  • Recall Phase: es una UI (User Interface) en la que se mostrarán las preguntas al usuario, que tendrá que responder haciendo clic el el botón Sí/No (Question 1, Question 2, Question 3). Una vez finalizado el test podrá elegir si quiere reiniciar la prueba o salir del juego (Score_txt). Esta interfaz aparecerá cuando el usuario llegue al final de la calle. Cada pregunta constará de un texto con la pregunta y dos botones (Sí/No) para responderla.

Scene-Recall_Phase_pregunta

  • EventSystem: se creará automáticamente al crear un Canvas ( un elemento usado para el renderizado de pantalla de la UI , el objeto Recall Phase en nuestro caso) para controlar los eventos del mismo.
  • CheckEnd: pared invisible con el collider puesto en modo Trigger que se usará para detectar el momento en el que el jugador llega al final de la calle.

Scene-CheckEnd

  • Scores: objeto vacío que recogerá y almacenará las respuestas del usuario mediante un script asociado.
Scripts

En la Iteración 1 se hará uso de un total de cuatro C# scripts:

  • EndMenu.cs : constará de dos funciones. Una para reiniciar el nivel y otra para cerrar el juego. Este script estará asociado a FPSController.

using UnityEngine;
using System.Collections;

public class EndMenu : MonoBehaviour
{
// Esta funcion reinicia el nivel.
public void ResetLevel ()
{
Application.LoadLevel ("iteration_1");
}

// Esta funcion cierra el juego.
public void ExitGame ()
{
Application.Quit ();
}
}

Script-EndMenu

  • Scores.cs : constará de dos funciones. La primera recogerá la respuesta del usuario a una pregunta. La otra comprobará si es correcta y almacenará los resultados; si es la última pregunta calcula el porcentaje de acierto del usuario. El script estará asociado al objeto Scores.

using UnityEngine;
using System.Collections;

public class Scores : MonoBehaviour {

public float rightAnswers; // Mostrara el numero de respuestas correctas en inspector.
float rA; // Numero de respuestas correctas con las que trabajaran las funciones.
public float totalAnswers; // Mostrara el numero de respuestas totales en inspector.
float tA; // Numero de respuestas totales con las que trabajaran las funciones.
public float totalQuestions; // Numero de preguntas totales. Se establecera su valor en Inpector.
public float results; // Almacenara el porcentaje de acierto de las respuestas.

bool answer; // Almacenara la respuesta a una pregunta (Si o No)

// Recogera la respuesta del usuario a una pregunta.
//
public void CollectAnswer(bool ans)
{
answer = ans;
}

// Comprobara si el usuario ha acertado con su respuesta a la pregunta, añadira el resultado y lo almacenara con el resto.
// Si era la ultima pregunta del test, calculara el porcentaje de acierto del usuario a todas las respuestas.
//
public void AddScore (GameObject solid)
{
rightAnswers = rA;
totalAnswers = tA;

if (solid.activeInHierarchy==answer)
{
++rA;
rightAnswers = rA;
++tA;
totalAnswers = tA;
} else
{
++tA;
totalAnswers = tA;
}

if (totalQuestions == tA) {
results = (rA / tA) * 100;
rA = 0;
tA = 0;
}
}
}

Script-Scores

  • ShowScores.cs : almacenará en el componente Text del objeto al que está asociado el porcentaje de aciertos del usuario a las preguntas, basándose en el script Scores.cs que se le pase por Inspector del que tomará los resultados. El script estará asociado al objeto Scores_txt.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

// Almacena en el componente Text los resultados obtenidos de las respuestas del test.
//
public class ShowScores : MonoBehaviour
{

Text text; // Almacenara el texto que mostrara el porcentaje de acierto a las preguntas.

float scores; // Almacenara el porcentaje de acierto del usuario.

public Scores scr; // Almacenara el componente Scores(Script) que se le pase en Inspector.

void Awake ()
{
text = GetComponent<Text> ();
}

void Update ()
{
scores = scr.results;
scores *= 100;
scores = Mathf.Round(scores);
scores /= 100;
text.text = "Porcentaje de aciertos: " + scores +"%.";
}
}

Script-ShowScores

  • StartRecallPhase.cs : comprueba si el personaje atraviesa el objeto CheckEnd que marca el fin de la calle. De ser así finaliza la Learning Phase y lanza la Recall Phase. El script estará asociado al objeto CheckEnd.

using UnityEngine;
using System.Collections;

public class StartRecallPhase : MonoBehaviour
{
public GameObject q1; // Almacenara el objeto de la primera pregunta del test.
public GameObject fpsc; // Almacenara el objeto del personaje.
public GameObject cam; // Almacenara el objeto de la camara de la Recall Phase.

// Comprueba si el personaje atraviesa al objeto que marca el fin de la calle y de
// ser asi comienza la Recall Phase.
//
void OnTriggerEnter(Collider other)
{
if (other.CompareTag ("Player"))
{
q1.SetActive (true);
fpsc.SetActive (false);
cam.SetActive (true);
}
}
}

Script-StartRecallPhase

También debemos tener en cuenta que cuando hacemos clic en uno de los botones que saldrán en la pantalla, se llamará a ciertas funciones de los scripts para realizar su acción correspondiente. En los botones de respuesta Sí/No de las preguntas siguen siempre el mismo esquema.

  1. Cuando pulsamos uno de ellos se desactivará el objeto correspondiente a esa pregunta y se activará el objeto de la siguiente pregunta. En caso de ser la última pregunta activará el objeto que muestra el porcentaje de éxito de las respuestas del usuario.
  2. Lo siguiente que hará será llamar a la función CollectAnswer del script Scores, pasándole como parámetro el valor correspondiente de la respuesta (true para Sí, false para No), para recoger la respuesta del usuario a la pregunta.
  3. Por último, se llamará a la función AddScore, a la que se le pasará como parámetro el objeto de la Hierarchy por el cual se pregunta si ha visto el usuario.  La función comprobará si coincide la respuesta del usuario con la existencia en la Scene del objeto en cuestión (dependerá de si está activado o desactivado) y almacenará el resultado. Si es la última pregunta calculará el porcentaje de acierto final.

BotonSiPreguntaBotonNoPregunta

Cuando se muestra el porcentaje de acierto también aparecerán dos botones: «Reiniciar Test» y «Salir». El primero cargará la Scene de nuevo llamando a la función ResetLevel del script EndMenu para realizar la Iteración 1 de nuevo. El segundo cerrará el juego llamando a la función ExitGame del script EndMenu.

BotonRestartBotonExit

Coding conventions

A continuación se muestran las convenciones a tener en cuenta en el desarrollo del proyecto Episodix:

Uso de llaves

  • La llave de apertura debe situarse al principio de la línea después de la sentencia que inicia el bloque. A su contenido se le añadirá una sangría correspondiente a 1 Tab o 4 Espacios. Ejemplo:
if (someExpression)
{
   DoSomething();
}
  • En el caso de switch y sus ‘case’:
    switch (someExpression) 
    {
     
       case 0:
          DoSomething();
          break;
     
       case 1: 
          {
             int n = 1;
             DoAnotherThing(n);
          }
          break;
    }
  • Las llaves no deben ser opcionales. Deben ser usadas incluso en bloques de sentencias de una única línea.
    for (int i=0; i<100; i++) { DoSomething(i); }

Sentencias de una línea

  • Las sentencias de una línea pueden tener llaves que empiezan y terminan en la misma línea.
    public class Foo
    {
       int bar;
     
       public int Bar
       {
          get { return bar; }
          set { bar = value; }
       }
     
    }

Comentarios

  • Es aconsejable el uso de comentarios que describan el funcionamiento del código al que hacen referencia.

Estilo de los comentarios

  • El estilo de // (doble barra) será el usado en la mayoría de ocasiones, aunque también existe /*…*/ (para abarcar varias líneas). Siempre que sea posible, se situarán encima del código. Ejemplo:
    // This is required for Controller access for hit detection
    FPSController controller = hit.GetComponent<FPSController>();
  • Los comentarios también pueden ser situados al final de la línea si hay espacio suficiente.
    public class SomethingUseful 
    {
        private int          itemHash;            // instance member
        private static bool  hasDoneSomething;    // static member
    }
  • Lo comentarios empezarán por letra mayúscula.
  • Se insertará un espacio en blanco entre el comentario y  // .

Espaciado

El espaciado mejora la legibilidad mediante la disminución de la densidad del código.

  • Usar un único espaciado después de cada coma entre argumentos de una función.
  • No usar espacios después de los argumentos o paréntesis de funciones.
    Console.In.Read(myChar, 0, 1);
  • No usar espacios entre el nombre de una función y los paréntesis.
    CreateFoo()
  • No usar espacios dentro de los corchetes.
     x = dataArray[index];
  • Utilizar un único espacio antes de las sentencias de control de flujo.
    while (x == y)
  • Utilizar un espacio antes y después de los operadores de comparación.
    if (x == y)

Diseño

  • Escribir una única sentencia por línea.
  • Escribir una única declaración por línea.
  • Si las líneas de continuación no son automáticamente sangradas, añadir la sangría correspondiente mediante tab (o 4 espacios).
  • Añadir al menos una línea en blanco entre métodos de definición y propiedades de definición.
  • Usar paréntesis para hacer cláusulas en una expresión como la del siguiente ejemplo:
    if ((val1 > val2) && (val1 > val3))
    {
        // Take appropriate action.
    }
  • Al final de cada línea de código se finalizará con punto y coma (» ; «), excepto para la declaración de funciones y sentencias ( por ejemplo if ).
  • Las líneas de código que se encuentren dentro de una misma función, sentencia, etc, tendrán la misma sangría. Si existen otras función,sentencia, etc, las líneas de código correspondientes compartirán sangría, la cual será mayor o menor dependiendo de si forman parte de las anteriores o si las anteriores están dentro de ellas.

Naming

  • Se usará el inglés para los nombres de las clases, assets y demás elementos dentro del proyecto.
  • No se usarán prefijos con variables.
  • Se usará el formato camelCasing para las variables y parámetros.
  • Se usará el formato PascalCasing para funciones, propiedades, eventos y clases.
  • Se usará el prefijo «I» para los nombres de interfaces.

Organización de archivos

  • Los archivos fuente contienen un único public type, sin embargo pueden tener múltiples clases internas.
  • Los archivos fuente deben tener el nombre del public class en el archivo.
  • Clases miembro deben ser ordenadas alfabéticamente y agrupadas en secciones (Fields, Constructors, Properties, Events, Methods, Private interface implementations, Nested types).

Ejemplo

using System;
using UnityEngine;
 
public class MyClass : MonoBehavior
{
    // fields
    int foo;
 
    // properties
    public int Foo { get {} set {} }
 
    // methods
    void MyMethod(int number)
    {
        int value = number + 2;
        Debug.Log(value);
    }
}

La mayoría de estas convenciones las realizará el propio editor (MonoDevelop o VisualStudio) la primera vez que escribamos el código al finalizar cada línea. En caso de editar el código ya no será así, salvo que simulemos finalizar la línea otra vez (por ejemplo, borrar y reescribir el punto y coma de final de línea ).

Batería de pruebas

Batería de tests de aceptación para el trabajo a realizar:

1.- Test para comprobar que se presentan las preguntas cuando el personaje llega al final de la calle.

  • Given<personaje en la calle vacía con los sólidos tridimensionales (esfera, pirámide, cubo) en el lado derecho>
  • When<personaje llega al final de la calle (fin de la learning phase)>
  • Then<se presentan las preguntas para comprobar si el usuario recuerda qué figuras tridimensionales aparecían en la calle (comienzo de la recall phase)>

2.- Test para comprobar que se actualizan los puntos del usuario correctamente según su respuesta con respecto a la presencia de la pirámide.

  • Given<aparecía la pirámide en la leaning phase>
  • When<el usuario responde en la recall phase a la pregunta de si recuerda haber visto la pirámide>
  • Then<se actualiza correctamente la puntuación del usuario>

3.- Test para comprobar que se actualizan los puntos del usuario correctamente según su respuesta con respecto a la presencia de la esfera.

  • Given<aparecía la esfera en la leaning phase>
  • When<el usuario responde en la recall phase a la pregunta de si recuerda haber visto la esfera>
  • Then<se actualiza correctamente la puntuación del usuario>

4.- Test para comprobar que se actualizan los puntos del usuario correctamente según su respuesta con respecto a la presencia del cubo.

  • Given<aparecía el cubo en la leaning phase>
  • When<el usuario responde en la recall phase a la pregunta de si recuerda haber visto el cubo>
  • Then<se actualiza correctamente la puntuación del usuario>

5.- Test para comprobar que se presenta scores (los resultados) una vez el usuario ha contestado a todas las preguntas.

  • Given<se plantean las preguntas al usuario>
  • When<el usuario responde a todas las preguntas>
  • Then<se muestra la puntuación final obtenida por el usuario>

Unirse a un proyecto en Bitbucket

En este post explicaremos de manera breve cómo unirse a un grupo de trabajo de Bitbucket:

1.- Crear cuenta en Bitbucket.

2.- Facilitar la dirección de correo electrónico al administrador del grupo.

3.- Llegará una invitación por email o un aviso de que ahora pertenecéis al grupo.

4.- Conectarse a nuestra cuenta y aceptar la invitación (de ser necesario) dentro del propio Bitbucket.

Haciendo simplemente esto ya podréis entrar en un grupo de Bitbucket. Podéis ver los grupos a los que pertenecéis en la pestaña Teams.

grupos

Si en el grupo existen repositorios, podréis acceder a ellos. También podréis crear otros nuevos si disponéis de permisos de administrador (estos permisos dependerán del administrador que os haya invitado).