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

Proyecto GEOW. Implementando el patrón CQRS. 2ª parte

En el anterior post ( Proyecto GEOW. Implementando el patrón CQRS ) nos adentramos en el funcionamiento del proyecto GEOW que nos va a servir de base para implementar un patrón arquitectónico, CQRS , pensado para dar respuesta a sistemas con alta exigencia de lecturas y escrituras simultáneas. Para ello hemos creado una interfaz gráfica con una serie de figuras geométricas en movimiento. Ahora vamos a ir a la parte de EL DATO. Cada vez que uno de estos cuadrados cambia de posición envía una trama con sus propias características, y sus nuevas coordenadas. Cada uno de los cuadrados realiza un movimiento cada 300 milisegundos, y he llegado a probar con hasta unas 700 figuras. En estos niveles el software empieza a sufrir, pero más la parte gráfica. Aparentemente el sistema de grabación de coordenadas se mantiene en buena forma. Vamos a ver, precisamente, este sistema de grabación: Grabando lotes de coordenadas en BBDD En el objeto PointObj que representa cada una de las figuras, en el

Primeros pasos en Enigma Software Labs

En pocas palabras: vamos a probar y estresar distintas soluciones de software para un mismo problema, en busca de realidades matemáticas. Para ello, lo primero que necesitamos en averiguar el modo de publicar código aquí en Blogger . Para ello voy a usar un sistema súper sencillo y eficiente. Ahí va un ejemplo de código C# private void frmMonitor_Load ( object sender, EventArgs e) { TreeNode _tr = new TreeNode( "SQL Server Tips" ); TreeNode _tr2 = new TreeNode( "C# Code" ); treeCatalogo.Nodes.Add(_tr); treeCatalogo.Nodes.Add(_tr2); //splitContainer.Panel1.Focus(); //lstMonitor.Focus(); } Hilarante ¿verdad?   hilite.me  lo hace posible. Bien, paso 2, fácil y sencillo: vamos a crear una plataforma de testeo para C# en una bonita solución que compartiré en el repositorio GIT, para disfrute de todos. Enigma Software - ZM LABS Como posteriormente me dive