Showing posts with label unit test. Show all posts
Showing posts with label unit test. Show all posts

Tuesday, January 3, 2017

Proceso de Unit Testing

Hablemos de Unit Testing. Este tema reaparece cada par de semanas en grupos de Android, por lo que parece interesante hablar de él.

Cuando se habla de Unit Testing existen varias opciones, como unit testing, integration testing, UI testing… todas son opciones válidas, pero en este post voy a hablar de qué es Unit Testing en general, y el por qué quieres tener unit testing en tu aplicación.

Voy a explicar esto con escenarios de experiencias que me han ocurrido en mi carrera.

Tu nueva aplicación - Dia 1.

Digamos que comienzas a hacer una aplicación nueva, y tu eres el único desarrollador en el proyecto. Esta aplicación es una aplicación bancaria, que solo te deja crear una cuenta nueva en el banco, y ver el saldo de tu cuenta.

La aplicación tiene una serie de requisitos, pero uno de esos es que cuando alguien cree una cuenta nueva, la aplicación revisa si la persona es mayor de 18 años. Si la persona tiene 18 años, o más, la cuenta puede ser creada. Si la persona tiene 17 años, o menos, la cuenta no puede ser creada. Super simple, pero es un requisito legal que es muy importante para tu empresa.

Como la aplicación es pequeña, y tu eres el único desarrollador, tú sabes de que en alguna parte de tu código, existe una cosa que revisa la edad. Digamos que la edad es verificada con algo así:

public boolean puedeCrearCuenta(Usuario usuario) {
if (usuario.edad < 17) {
return true;
}
return false;
}


La forma específica de cómo se revisa la edad es irrelevante. Lo que nos importa es que si pasamos un objeto de tipo Usuario, podemos ver si ese usuario puede crear una cuenta.

La aplicación es lanzada al público, y todo sale bien. Tu sabes que esa parte del código es importante, pero es una verificación tan pequeña que no es documentada ni probada.

Tu aplicación - 6 meses después

Tu aplicación está siendo utilizada por muchos usuarios, y el banco le quiere agregar muchas otras habilidades. Lamentablemente tu no puedes hacer todo, por lo que el banco contrata 2 desarrolladores más, o contrata con otra empresa para que ellos puedan crear las habilidades nuevas de la aplicación.

Tu verificacion de edad, que escribiste hace 6 meses, continúa en el código, pero como nunca fue documentada, y está escrita en una forma muy básica, los desarrolladores nuevos modifican el código.

Como los desarrolladores nuevos ven el nombre de “puedeCrearCuenta” piensan de que esta verificación puede tener muchas opciones...por lo que ellos le agregan mas cosas.

public boolean puedeCrearCuenta(Usuario usuario) {
if (usuario.edad < 17 && usuario.sueldo > 100000) {
return true;
}
return false;
}


Ahora tu verificacion de edad no solo revisa la edad, sino que también revisa el sueldo.

La aplicación compila, y nadie se da cuenta de la diferencia...hasta que tienes usuarios quejándose.

Unit Test al rescate

Si hubiéramos usado unit test, tal vez hubiéramos creado una par de pruebas como éstas:

public void testCrearCuentaAdulto() {
Usuario usuario = new Usuario();
usuario.nombre = "Eduardo Flores";
usuario.edad = 18;
assertTrue(puedeCrearCuenta(usuario));
}


public void testCrearCuentaMenorDeEdad() {
Usuario usuario = new Usuario();
usuario.nombre = "Eduardo Flores";
usuario.edad = 17;
assertFalse(puedeCrearCuenta(usuario));
}


Estas 2 pruebas son super simples. Lo único que estamos haciendo es crear un nuevo objeto de tipo Usuario, le damos un nombre al usuario y le damos una edad.

La primera prueba válida a el usuario de 18 años, y por lo tanto puede crear una cuenta. La segunda prueba revisa de que un usuario de 17 años no pueda crear una cuenta.

Ambas pruebas deberían pasar, sin importar el sueldo.

Qué logramos con nuestro Unit Test?

Es entendible de que en este momento tu digas “es obvio de que ambas pruebas van a pasar. Yo escribí el código, y yo escribí las pruebas. Y ambas pruebas son extremadamente simples!”
Y tendrías toda la razón, y es muy bueno que pase las pruebas.

La verdad es que Unit Testing no tiene nada de magia ni nada específico de código que nos importe. Comúnmente las pruebas de Unit Test son así de simples: creas un objeto, y pruebas un elemento específico de tu código.

En donde Unit Test es increíblemente poderoso es en el proceso de cómo desarrollamos la aplicación.

Es decir, Unit Testing es 90% proceso y 10% código...y el que escribas es comúnmente irrelevante.

Si tu aplicación hubiera tenido unit tests, como las 2 propuestas en este post, cuando los desarrolladores nuevos modifiquen el código del método puedeCrearCuenta(), una de las 2 pruebas va a fallar porque no estamos pasando un sueldo. Esto hubiera sido una prueba muy simple y rápida para ver que el código agregado a puedeCrearCuenta() no deberia ir ahi, o simplemente no funciona.

Como crear el proceso

Depende de tu empresa, existen 2 formas en cómo verificar tu código con tus pruebas de unit test:
  1. Puedes hacer correr tu código de unit test arbitrariamente. Un ejemplo de esto es hacer correr las pruebas de unit test cada cierta cantidad de días, o antes de lanzar una actualización (o aplicación nueva)
  2. Puedes usar una aplicación a nivel de creación de tu build para correr tus pruebas de unit test. Nosotros usamos Jenkins, y cada vez que alguien escribe código nuevo a el repositorio, jenkins corre la aplicación y las pruebas de unit test. Si alguna prueba falla, le manda un email a todo el equipo.
El único cuasi-requisito de unit test es de que prueben 1 sola cosa específica. No te sirve de nada si la prueba de unit test revisa la edad, un nombre valido, una dirección correcta, etc… ya que si la prueba falla, el reporte enviado a todos es muy vago.

Conclusion

Espero de que este post te sirva para entender de que Unit Test no es requerido, pero es parte de un proceso que te puede ayudar a eliminar errores lógicos.
Unit testing tambien sirve mucho para verificar de que las cosas que funcionaron hace un año sigan funcionando hoy.

Te entendería de que pensaras de que puedes evitar el uso de Unit Test si usas mejor nombre para tu código, o comentarios, o documentación, pero lamentablemente eso comúnmente no sirve a largo plazo. Piensa de que si tus comentarios son en español, y tu empresa contrata con una empresa Alemana o Rusa o Inglesa, los contratistas no van a entender nada de tu documentación.

Aun así, si tu documentación fuera válida, no dice nada de cómo implementar el método. Que pasaria si en vez de revisar un integer (17) revisaramos en contra de Date() o en milliseconds? Es posible de que cambiemos la implementación, y tengamos un error lógico de cual es el límite de edad con la nueva implementación.

Eduardo.

Sunday, February 28, 2016

Why bother with Unit Testing, JUnit?

One of "my" students came asking me a very important question that I think it is very hard to explain at a classroom level.

His question was "what is this JUnit thing, and why should I even bother with it?"

See, this makes perfect sense for this student to question this concept to himself. In a classroom environment, specially on low level classes, all of your information, classes and data models are created in a very small scale, and they are all created by you. So if the app compiles and runs, you don't have an error. Because of this Unit Testing, or JUnit in Java, makes almost no sense.

Let's talk about this concept to hopefully clear out for you what Unit Testing is, and why you actually want it.

What is JUnit?

JUnit is a Unit Testing framework for Java. For the rest of this blog post I will stop calling this concept JUnit, and we'll talk about unit testing in general so it can be applied to other languages.

What is Unit Testing?

"Unit testing is a software development process in which the smallest testable parts of an application, called units, are individually and independently scrutinized for proper operation. Unit testing is often automated but it can also be done manually."

What does this mean, and why should I care about it?

This brings up back to the original question.
See, Unit Testing is often (read: 99.9% of the time) done automatically, so you should think of Unit Testing as a code you create to validate how you expect your code to work.
This makes more sense in a real-world type of example.

Let's say we are developing an app that downloads the data of all of the students at your school from some school server. This server gives us the first name, last name and age of each student.
For Java, we would create a student object like this:
public class Student {

    private String firstName;
    private String lastName;
    private int age;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

And the server would return data like this:
{
    "students": [{
 "firstName": "Eduardo",
 "lastName": "Flores",
 "age": 20
 }, {
 "firstName": "Mary",
 "lastName": "Johnson",
 "age": 21
 }, {
 "firstName": "Mike",
 "lastName": "Pascal",
 "age": 22
 }, {
 "firstName": "John",
 "lastName": "Smith",
 "age": 25
 }
        // continue for 50000 students
        ]
}
This is a JSON string, but don't worry about that means right now. What matters is that you can see that every student has a firstName and lastName as String, and an age as a int.
We would assume your school also has something like 50,000 students...not just 4. You get the idea.

You create your app, it downloads and parses the data, everything works and you release your app. Life is good!

But then, one day, you hear your app crashed that morning. Then it crashed again when you tried it!

You debug your app, and you find out that the crash is cause by the app parsing the data that comes from the server. In other words, something the server is sending you is making the app crash.

Fixing this scenario without Unit Testing

Let's say you didn't do Unit Testing, and now you need to make sure the data coming from the server is valid. Well, unfortunately for you, the data is valid in general terms (there are no invalid characters) so since you don't have unit testing setup in your app, nothing created by a third party website or service can help you find the issue.

All you get is a crash report from the stack trace saying something about "not of type Integer (int)"

So...you'll have to loop through every single one of the students...all 50,000 of them...one...by...one...until you find the problem.

What's worse is that you created this app 6 months ago, and by now you have completely forgotten how the app works.

Fixing this scenario with Unit Testing

If you would've done unit testing on your application (and done it thoroughly), you can now run the unit test, and within 2 minutes you will know that a particular student has an age of 20.9 (no idea why, but I've seen it happen)

How would you know this?
Your unit test would fail when validating the age field of every student object downloaded, but it will tell you (again, if setup properly) that a student object coming from the server has an age of a double instead of an int.
This one student object is what is causing the crash in your app.

So now instead of having to go one-by-one on the data coming from the server, you can see that student "Jimmy Smith" of age 20.9 is causing the problem. And you did all of this within 2 minutes.

When you should write your Unit Test

Since your unit test is an individual test over a specific piece of data, you should write your unit tests when your app is running and it is stable. You should have all of your tests pass (unless you're intentionally creating failed tests) when your application and data is working properly.

Writing a unit test when the app is having a problem is very risky because it can give you false positives, but it can be done if you know exactly what you should expect out of your app.

What Unit Testing doesn't do for you


This seems to be the area where students struggle the most with Unit Testing, so here are the main 2 things Unit Testing does not do for you:

1. Unit testing can help you find a problem, and it can do it very fast and easy. However, it will not tell you how to fix the problem.
But think about it, in the example above, what would the solution be?
Should we modify our app?
Should we complain to the server to provide us with valid data?
Should the server remove that problem object?

In every situation the solution would be different, so Unit Testing can't tell you how to fix this.

2. Unit testing will not create any new end-user functionality to your app. Unit testing is basically a tool within an app created by developers for developers. This means that you can write unit testing cases for your app for a whole month, and your users won't see anything new (except possibly less issues). This gives new developers a feeling of writing code for nothing.

So there you have it. I hope a real-world example helps you.

Yes there could also be a better debugging environment, but the exaggerated scenario described here is very possible and it should highlight the benefits of Unit Testing.

Please let me know if you still have questions about Unit Testing, or provide any comments you may have about the concept of Unit Testing.