Ir al contenido principal

Test 2: Concatenación de Strings


Bienvenidos de nuevo. Hemos creado la nueva categoría c# basic tips para poner a prueba aquellas buenas prácticas de programación cuyos beneficios, aunque son de sobra conocidos, no acertamos a  cuantificar con exactitud.

E inauguro la sección con una sencilla prueba que pone en contraposición la concatenación recursiva de cadenas de texto con el operador +, frente a la correcta forma de programar dichas concatenaciones mediante el objeto StringBuilder. Por fin sabremos con números sobre la mesa cuánta performance ganamos en nuestro software codificando este tipo de operaciones  adecuadamente.

Les dejo en este punto un buen artículo que explica las distintas opciones de concatenación: c-sharpcorner - 6 effective ways to concatenate strings in c-sharp. Y como siempre les recuerdo que pueden clonar la plataforma de pruebas en GitHub y realizar con ella sus propios test. ¡Estaremos encantados de recibir vuestras aportaciones!  Enigma Software - ZM LABS

Bien, para hacer el test he elegido el clásico por excelencia de la literatura española, Don Quijote de la Mancha. He obtenido el texto de esta web, he tomado los primeros 26 párrafos del insigne libro y los he metido cada uno en una variable tipo string.



Lo que vamos a hacer es concatenar 100 veces estas 26 variables, en el primer caso con el operador +, y en el segundo caso con el StringBuilder.

Es obvio que funcionará mejor el segundo caso, pero, ¿cuánto mejor? ¿Adivinan? Vamos a ello:

CASE 1:

        public void ConcatQuijotePlusOperator()
        {
            string res = "";

            for (int cont = 0; cont < 100; cont++)
            {
                res += a + Environment.NewLine +
                       b + Environment.NewLine +
                       c + Environment.NewLine +
                       d + Environment.NewLine +
                       e + Environment.NewLine +
                       f + Environment.NewLine +
                       g + Environment.NewLine +
                       h + Environment.NewLine +
                       i + Environment.NewLine +
                       j + Environment.NewLine +
                       k + Environment.NewLine +
                       l + Environment.NewLine +
                       m + Environment.NewLine +
                       n + Environment.NewLine +
                       o + Environment.NewLine +
                       p + Environment.NewLine +
                       q + Environment.NewLine +
                       r + Environment.NewLine +
                       s + Environment.NewLine +
                       t + Environment.NewLine +
                       u + Environment.NewLine +
                       v + Environment.NewLine +
                       w + Environment.NewLine +
                       x + Environment.NewLine +
                       y + Environment.NewLine +
                       z + Environment.NewLine;
            }
        }

CASE 2:

        public void ConcatQuijoteStringBuilder()
        {
            string res = "";

            StringBuilder _strBuilder = new StringBuilder("");

            for (int cont = 0; cont < 100; cont++)
            {
                _strBuilder.AppendLine(a);
                _strBuilder.AppendLine(b);
                _strBuilder.AppendLine(c);
                _strBuilder.AppendLine(d);
                _strBuilder.AppendLine(e);
                _strBuilder.AppendLine(f);
                _strBuilder.AppendLine(g);
                _strBuilder.AppendLine(h);
                _strBuilder.AppendLine(i);
                _strBuilder.AppendLine(j);
                _strBuilder.AppendLine(k);
                _strBuilder.AppendLine(l);
                _strBuilder.AppendLine(m);
                _strBuilder.AppendLine(n);
                _strBuilder.AppendLine(o);
                _strBuilder.AppendLine(p);
                _strBuilder.AppendLine(q);
                _strBuilder.AppendLine(r);
                _strBuilder.AppendLine(s);
                _strBuilder.AppendLine(t);
                _strBuilder.AppendLine(u);
                _strBuilder.AppendLine(v);
                _strBuilder.AppendLine(w);
                _strBuilder.AppendLine(x);
                _strBuilder.AppendLine(y);
                _strBuilder.AppendLine(z);
            }


Aquí les muestro el resultado de la ejecución:



Tal y como imaginábamos el StringBuilder es significativamente más efectivo. Espero que disfrutasen con este pequeño y muy sencillo test que no pretende sino poner en práctica aquello que ya conocíamos en la teoría.

Breves apuntes sobre el estado de la plataforma de pruebas:

Permítanme comentarles brevemente que, tras las últimas mejoras de diseño la plataforma admite ya con suma facilidad la creación de nuevos test. Éstos son los pasos a seguir, para que puedan Uds. mismos elaborar y probar los suyos propios:

1. Con el interfaz gráfico de la plataforma se puede ya crear en BBDD la información de un nuevo test (colgando de un rama de categoría existente), e informar sus testcases.

2. Una vez grabada la información del nuevo test hemos de codificarlo. Para ello debemos crear, como ya hemos visto en ocasiones anteriores, una clase en la carpeta y proyecto creados a tal efecto, cuyo nombre coincida con el campo Classname informado previamente. La nueva clase debe heredar la clase base test_exec.


3. El contenido de la clase ha de tener la siguiente estructura:
  • El método Start(), que debe cumplir ciertos requisitos. Hacer una primera llamada al método InitTest() y una última llamada al método EndTest(). Luego veremos estos métodos, que lo que hacen es establecer el estado de la ejecución, y generar los correspondientes mensajes que irá recogiendo el contenedor para mostrárselos al usuario.
        public override void Start()
        {
            //inicia test
            this.InitTest();

            //recorre y ejecuta testcases
            int cont = 0;

            while (cont < _testobject.TestCases.Count)
            {
                TestCases _test = _testobject.TestCases[cont];

                switch (_test.Function)
                {
                    case "Concat_PlusOperator":

                        _testobject.TestCases[cont] = Concat_PlusOperator(_test);
                        break;

                    case "Concat_StringBuilder":

                        _testobject.TestCases[cont] = Concat_StringBuilder(_test);
                        break;
                }

                cont++;
            }

            //finaliza test
            this.EndTest();
        }
  • Tantos métodos como testcases hubiéramos informado asociados a la entidad test, cuyos nombres coincidan con el campo Function que informásemos al grabar el testcase. Al igual que el método anterior, la estructura de cada testcase debe contener los elementos que se muestran a continuación. La creación de un objeto TestCaseExecution, y las llamadas a los métodos InitTestCase y EndTestCase. Éste último recibe como parámetro el objeto TestCaseExecution con la finalidad de almacenar en BBDD los resultados de la ejecución del testcase.
        public TestCases Concat_PlusOperator(TestCases _test)
        {
            TestCaseExecutions _testexec = new TestCaseExecutions() { idTestCase = _test.id };

            //registra inicio
            _testexec.dtBegin = DateTime.Now;
            InitTestCase(_test.Function, _testexec.dtBegin);

            //ejecuta testcase
            functions.quijote quijoteObject = new functions.quijote();
            quijoteObject.ConcatQuijotePlusOperator();

            //registra fin
            _testexec.dtEnd = DateTime.Now;
            EndTestCase(_test.Function, _testexec);

            return _test;
        }

Métodos de inicio y fin de test, y de inicio y fin de test-case:
Los mismos se encuentran en la clase base test_exec, por lo que todos los test derivados podrán disponer de ellas:

        public void InitTest()
        {
            this.Estado = test_types.enumEstadoProceso.Ejecutando;

            SetMsg("- - - - -");
            SetMsg(_testobject.Test + " iniciado a las " + DateTime.Now.ToLongTimeString());
        }

        public void EndTest()
        {
            SetMsg("- - - - -");
            SetMsg(_testobject.Test + " finalizado a las " + DateTime.Now.ToLongTimeString());

            this.Estado = test_types.enumEstadoProceso.Finalizado;
        }

        public void InitTestCase(string casename, DateTime dtBegin)
        {
            SetMsg("- - - - -");
            SetMsg(casename + " Case iniciado a las " + dtBegin.ToShortTimeString() + " " + dtBegin.ToLongTimeString());
        }

        public void EndTestCase(string casename, TestCaseExecutions _testexec)
        {
            SetMsg(casename + " Case finalizado a las " + _testexec.dtEnd);
            SetMsg(casename + " Case ejecutado en " + _testexec.Miliseconds + " milisegundos");

            _testobject.InsertExecution(_testexec);
        }

4. Por último, para poder asociar en la propiedad OBJ la clase derivada correcta, debemos incluir nuestro test en el enumerado enumTestTypes del espacio de nombres ZmLabsObjects,

    public enum enumTestTypes
    {
        test1_multithreading_vs_singlethreading,
        test2_basicos_concatstrings,
        test3_sql_loaddata
    }

...y añadir el valor de nuestro nuevo test en el método GetObject()

_treeelem.TestObject.Execution.OBJ = test_types.GetObject(_test_functions, _type);

        public static Object GetObject(test_info.test_functions_base _functions, ZmLabsObjects.enumTestTypes _type)
        {
            test_exec res = new test_exec(_functions);

            switch (_type)
            {
                case ZmLabsObjects.enumTestTypes.test1_multithreading_vs_singlethreading:

                    res = new test1_multithreading_vs_singlethreading(_functions);
                    break;

                case ZmLabsObjects.enumTestTypes.test2_basicos_concatstrings:

                    res = new test2_basicos_concatstrings(_functions);
                    break;

                case ZmLabsObjects.enumTestTypes.test3_sql_loaddata:

                    res = new test3_sql_loaddata(_functions);
                    break;

            }

            return res;
        }

¡Hecho! Ya tenemos nuestro nuevo test funcionando. ¡¡ Fantástico trabajo !!


Futuras mejoras:
Los métodos que acabamos de ver nos ayudan a agudizar el ojo a la hora de identificar qué código fuente es susceptible de mejora. Vemos dos aspectos que enseguida nos tienen que llamar la atención:

 1. Código hardcodeado: me estoy refiriendo al código contenido en la función Start(), aquel que recorre los testcases para llamar a su correspondiente método. ¿Se animan a proponer la mejor solución para esta necesidad?

2. Código repetido: siempre que advirtamos piezas de código que se repiten en distintos métodos deben sonar nuestras alarmas de buen programador. En este caso vemos cómo estamos repitiendo las invocaciones a los métodos de inicio y fin, tanto del test como de los testcases. ¿Podremos evitar estas repeticiones? Seguro que sí amigos, seguro que sí.



Comentarios

Entradas populares de este blog

Test 3: ¿Son eficientes los ORMs?

Bienvenidos amigos. Me complace anunciar que por fin estrenamos la categoría " SQL Server Tips ", y lo hacemos por todo lo alto, entrando de lleno en un aspecto altamente polémico entre programadores. ¿Es eficiente un ORM en los accesos a datos? Ya conocen la filosofía de nuestros Tests, no vamos a teorizar demasiado, pero sí una pequeña base va a ser necesaria para conseguir una buena respuesta a nuestra pregunta. He leído un interesante  artículo de nuestros súper amigos de Deloitte ( cuando usar ORM ) argumentando que el uso o no de un ORM hay que decidirlo en relación a la complejidad de nuestro modelo de datos, y al rendimiento que requeriremos en nuestras soluciones, pero, ¿cuándo no deseamos el mejor rendimiento para nuestro software? Lo cierto es que, como ya hemos visto, el ORM facilita mucho las cosas, aporta claridad al código, de eso no cabe duda, pero, ¿es eficiente? He ahí la cuestión. Sobre este asunto vamos a poner a funcionar nuestros apreciados Test. C...

Trazabilidad y control de errores - 2ª parte: trazabilidad estructurada

En esta nueva entrega de la publicación  Trazabilidad y control de errores vamos a centrarnos en el que es quizá el más interesante aspecto de la trazabilidad.  Structured Logging es la técnica que nos permitirá realizar análisis automatizados de nuestra trazabilidad, mediante software de detección de eventos. En la primera parte de la publicación incorporamos a nuestra plataforma de test Open Source la librería NLog , y configuramos la generación de dos ficheros de texto plano para trazas, uno para dejar la información de los posibles errores no controlados, y otro para los avisos, o warnings . Además asociamos la consola para crear trazas de información para la depuración. Hoy vamos a configurar la creación de un tercer fichero, que almacenará igualmente información de los errores, pero en este caso guardará la información no en texto plano, sino estructurada, con notación JSON que después podría ser procesada. Para este fin NLOG nos proporciona el JSON Layout . Vamos a ...

Proyecto GEOW. Implementando el patrón CQRS

Me siento muy feliz de poder ofrecerles esta nueva publicación y este nuevo proyecto Open Source. Naturalmente seguiremos evolucionando el proyecto de tests de c#, pero he querido hacer este paréntesis para hablarles del patrón CQRS , o lo que es lo mismo,  Command Query Responsibility Segregation . Una primera versión plenamente operativa del proyecto GEOW está ya disponible para todos Uds. en  GitHub . Les recomiendo encarecidamente que la clonen y jueguen con ella. No solo tiene un elevado potencial pedagógico , además sus propiedades visuales son altamente hipnóticas, por lo que les pido mucha precaución a la hora de ponerlo a funcionar :)  Por esta misma razón me he decidido a crear un canal en YouTube para irles mostrando videitos con los resultados de los dos proyectos que nos traemos entre manos. Espero lo disfruten. Son muchos los detalles técnicos que se desprenden en esta publicación, además de la propia implementación del patrón...