Como comentábamos en el post sobre los principios SOLID, vamos a empezar esta serie de artículos hablando sobre el Principio de Responsabilidad Única (SRP), el que para mi, es uno de los principios más importantes.
Este principio establece que una clase debe tener una única razón para cambiar, es decir, una sola responsabilidad.
Cuando combinamos el SRP con las pruebas unitarias, creamos una sinergia que mejora significativamente la calidad de nuestro código y facilita su evolución a largo plazo. En este artículo, exploraremos en profundidad cómo estos dos conceptos se complementan y cómo podemos aplicarlos de manera efectiva en nuestros proyectos Flutter.
¿Por qué el SRP es crucial para las pruebas unitarias?
- Pruebas aisladas: Al tener clases con una única responsabilidad, es mucho más fácil aislarlas y probarlas de forma independiente. Esto significa que podemos simular su entorno con datos de prueba y verificar su comportamiento sin preocuparnos por los efectos secundarios en otras partes del sistema.
- Mayor cobertura de código: El SRP nos permite descomponer nuestro código en unidades más pequeñas y enfocadas, lo que facilita la escritura de pruebas unitarias para cada una de ellas. Esto resulta en una mayor cobertura de código y una mayor confianza en la calidad de nuestro software.
- Detección temprana de errores: Las pruebas unitarias nos permiten identificar errores de manera temprana en el ciclo de desarrollo. Al aplicar el SRP, es más probable que los errores se manifiesten en pruebas unitarias aisladas, lo que facilita su localización y corrección.
- Refactorización segura: Las pruebas unitarias actúan como una red de seguridad cuando realizamos cambios en nuestro código. Si refactorizamos una clase que cumple con el SRP, podemos ejecutar las pruebas unitarias para asegurarnos de que los cambios no hayan introducido nuevos errores.
Ejemplo Práctico: Un Widget de contador
Este widget combina la lógica de presentación, la lógica de negocios y la gestión de estado:
class MyWidget extends StatelessWidget { final String title; final int count; MyWidget({required this.title, required this.count}); @override Widget build(BuildContext context) { if (count == 0) { return Text('No hay elementos'); } else { return Column( children: [ Text(title), Text('Cantidad: $count'), ElevatedButton( onPressed: () { setState(() { count++; }); }, child: Text('Aumentar'), ), ], ); } } }
Este widget no cumple con el principio de Single Responsibility porque:
- Presenta la información: Muestra el título, la cantidad y un botón.
- Gestiona la lógica de negocios: Incrementa la cantidad al presionar el botón.
- Gestiona el estado: Utiliza
setState
para actualizar la cantidad.
Refactorización para cumplir con el principio de Single Responsibility
Se puede refactorizar el widget para que cada responsabilidad esté separada:
class MyWidget extends StatelessWidget { final String title; final int count; final Function onIncrement; MyWidget({required this.title, required this.count, required this.onIncrement}); @override Widget build(BuildContext context) { if (count == 0) { return Text('No hay elementos'); } else { return Column( children: [ Text(title), Text('Cantidad: $count'), ElevatedButton( onPressed: onIncrement, child: Text('Aumentar'), ), ], ); } } } class MyStatefulWidget extends StatefulWidget { @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState(); } class _MyStatefulWidgetState extends State<MyStatefulWidget> { int count = 0; void incrementCount() { setState(() { count++; }); } @override Widget build(BuildContext context) { return MyWidget( title: 'Mi título', count: count, onIncrement: incrementCount, ); } }
En esta refactorización:
- MyWidget: Se encarga de la presentación de la información y recibe la cantidad y la función para incrementarla como parámetros.
- MyStatefulWidget: Se encarga de gestionar el estado y la lógica de negocios.
De esta manera, cada widget tiene una única responsabilidad, lo que facilita la comprensión, la prueba y el mantenimiento del código.
Siguientes pasos
Intenta siempre descomponer tus clases, sean widgets o clases de negocio en las unidades más pequeñas posibles, no te preocupes si tienes 100 clases diferentes, no te las van a cobrar, esto te permitirá tener un mayor control sobre tu código y hacer tests más pequeños, rápidos y mantenibles.
Nos vemos en el siguiente artículo.