Ir al contenido principal

Si no hay dudas, no hay progreso

¡No abandonamos la tecnología para pasarnos a la filosofía!

Titulo este nuevo artículo con una linda frase de Darwin, para hablarles de EVOLUCIÓN, pero no en el terreno de la biología ni de la filosofía, sino en asuntos más pequeños, en este caso, la evolución de nuestra pequeña plataforma de test Open Source, que como saben, pueden clonar, adaptar y  manipular a su antojo.

Es pequeña, pero está creciendo. Y conforme crece se hace mejor. Ojalá pudiéramos decir lo mismo de todos los desarrollos ¿cierto?

"La  vida del hombre es interesante principalmente si ha fracasado. Eso indica que trató de superarse"
Georges Benjamin Clemenceau

Hoy toca repasar y refactorizar. Repasemos en primero lugar, la filosofía de la plataforma.

La plataforma de test se compone de un contexto gráfico donde definir, consultar y ejecutar tests. La definición de un test y sus entidades relacionadas podría ser considerado nuestro dominio. Para ello desplegamos un conjunto de librerías, que definen nuestros objetos de dominio (test, testcases, executions) y sus funcionalidades asociadas básicas (Insert, Get...).

El test en si mismo (el código implementado que se desea probar y normalmente, contraponer a otro y otros fragmentos de código de igual funcionalidad), está en otro conjunto de clases a las que consideramos como de lógica de negocio.

El proyecto va de menos a más, a propósito, para que sean Uds. partícipes de cada cambio, de cada mejora, de cada añadidura. Cada pequeño paso es un pretexto para un nuevo artículo que describa en qué hemos avanzado y cómo lo hicimos.

Hemos tocado ya puntos importantes para la elaboración ordenada de una solución de software. En cada artículo, en cada commit, siempre quedan tareas por hacer: generalizar el ejemplo usado para la publicación, resolver bugs, y siempre con la siguiente nueva técnica a implementar en la cabeza.

Les he querido hacer partícipes de todas aquellas tareas inconclusas y habrán comprobado, si ya han tenido ocasión de usar el proyecto como su juguete de pruebas, que las posibilidades de la plataforma y su función pedagógica y de investigación son amplias y divertidas.

Hoy toca recogerlo todo, hacer retrospectiva y ordenarlo; con el fin de ampliar homogéneamente las técnicas (de acceso a datos, de control de errores, etc.) que hemos ido aplicando; con el fin de revisar las dependencias entre proyectos y clases y en general, para alcanzar un estado de coherencia sobre el que poder seguir creciendo.

1. El acceso a datos

Para ilustrar la herencia y redefinición de métodos usamos en su día el ejemplo del acceso a datos, donde una clase base derivaría en dos implementaciones, una usando ADO.NET y otra usando Entity Framework.

Tras crear un test específico para realizar comparaciones entre estas dos técnicas, que usan clases de negocio AdHoc asociadas al test (Parte de horas), mantener la implementación de ADO.NET deja de tener sentido, ya que me obliga a desarrollar cada cambio en el diseño de BBDD por duplicado. En cambio decido decantarme por adoptar EF Code First como estrategia única para la gestión del dominio, y depreco la implementación con ADO.NET tanto de las funciones de dominio (GetTest, InsertTest, InsertTestCase, etc...) como de las funciones de creación y actualización de la base de datos mediante ADO.NET.

Dejo en el proyecto el código deprecado Business_Test_Functions_ADO_ISOLATED , que no tiene referencia alguna, por si desean Uds. recuperarlo. Pero dejo de mantenerlo.

A propósito de este asunto tenía abierta una incidencia en GitHub. Relacionada con la función UpdateDatabase en la implementación con ADO.NET que, en base a las últimas novedades quedará oficialmente cerrada sin resolver. Nuestro primer bug cerrado sin resolver. ¡Qué ilusión!

2. La inversión de dependencias

En cuanto a la inversión de dependencias, la he generalizado en unos casos, y acotado en otros. Esta tarea ha dejado huérfanos algunos ejemplos empleados en la publicación sobre esta materia.

Revisemos la estructura final de cada uno de los proyectos, empezando por la CAPA DE DATOS:

ADO: La clase estática data_labs resuelve algunas operaciones muy específicas, relacionadas con el proceso de creación y actualización de la base de datos en tiempo de ejecución


La clase data_test_test_info contiene las funciones de dominio implementadas con ADO.NET. Esta implementación, como acabamos de señalar,  está obsoleta y ninguna línea de ejecución hace ya referencia a ella. Implementa la interface ITestRepository, implementada y funcionando en el repositorio labs_repos.


La clase data_tests_partehoras contiene el acceso a datos vía ADO.NET, vigente y usado en el contexto del test3 - ¿Son eficientes los ORM?


Implementa la clase IParteHorasRepository, que en su versión implementada para Entity Framework se encuentra en sql_test_repos_partehoras.

Resumiendo, los dos repositorios de datos activos quedan expuestos mediante abstracciones.
  • El repositorio de entidades de dominio (test, testcases...) implementado una única vez con Entity Framework.
  • El repositorio del modelo Parte de Horas, en el esquema test, que servirá como base para ejecutar varios test, de momento el test3. El mismo se encuentra doblemente implementado por métodos implementados con ADO vs métodos implementados con EF.
  • El contexto de Entity Framework.
  • Contracts: Definición de las interfaces IParteHorasRepository e ITestRepository. Abstracciones de los repositorios de datos en uso.
  • EFModels: pocas variaciones en los modelos EF excepto por la inclusión de alguna que otra propiedad navigacional que he venido necesitando.
  • Migrations: en la clase Configuration quedan centralizadas las tareas de (i) creación de base de datos, (ii), actualización de modelo de base de datos en tiempo de ejecución, y (iii) carga inicial de datos.
  • Repos: en las clases labs_repos sqltest_repos_partehoras reside la implementación de los repositorios de datos empleando EF, implementación de las abstracciones ITestRepository e IParteHorasRepository respectivamente.
LA LÓGICA DE NEGOCIO
  • data/Business_Test_Functions_base es la clase de base para la manipulación de los objetos de dominio. Implementa ITestFunctionsDomain, que define las acciones básicas implementadas para estos objetos, a saber test, testcases, etc.
  • data/Business_Test_Functions_EF y data/Business_Test_Functions_ADO_ISOLATED son clases derivadas de Business_Test_Functions_base. Sobre-escriben los métodos con implementaciones para EF (activa) y para ADO (en desuso).
El repositorio de datos expresado en la abstracción ITestRepository es inyectado como dependencia.

Los tipos de datos manejados en este conjunto de funciones son clases de dominio (test, testcases, etc.).
  • Static functions: El conjunto de clases bajo la carpeta functions, a saber fibo_functionsparte_horas_functionsquijote_functions reflections pasan a ser clases estáticas, fuertemente acopladas a las clases de cada uno de los test desde donde son invocadas.
  • Test Library en la carpeta con este nombre se engloban las clases referidas al contenido del propio test, el código fuente que debe ser ejecutado.
  • Tests/objects/test_base es la clase base de toda implementación de un test. Aquí el lenguaje nos traiciona un poco. Es más sencillo observar la estructura para entenderla. Implementa la Interface ITest definida en la capa de dominio. Los métodos StartTest, StartTestCase, EndTestCase, etc. se sobre-escriben en las clases derivadas, que son las implementaciones de los test en si mismos, es decir test1_multithreading_vs_singlethreadingtest2_basicos_concatstrings test3_sql_loaddata hasta la fecha.

La extensión Tests/objects/test_base será la que determine la clase test concreta, que irá inyectada como propiedad en la clase de dominio ExecutionPropertiesDomain.


EL DOMINIO

De entrada, les voy a dejar el código completo de nuestras entidades de dominio, porque de verdad creo que se explica por si solo:

namespace ZmLabsObjects
{
    public enum enumTestTypes
    {
        test1_multithreading_vs_singlethreading,
        test2_basicos_concatstrings,
        test3_sql_loaddata
    }

    public class TestDomain
    {
        private contracts.ITestFunctionsDomain TestFunctions;

        //constructores
        public TestDomain() {  }

        public TestDomain(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            TestFunctions = p_TestFunctions;
        }

        //propiedades
        public Int64 id { get; set; }
        public string Test { get; set; }
        public string Description { get; set; }
        public string Classname { get; set; }
        public string Url_blog { get; set; }
        public string Url_git { get; set; }
        public int idCategorie { get; set; }

        public CategoriesDomain Categorie { get; set; }
        public ExecutionPropertiesDomain Execution { get; set; } = new ExecutionPropertiesDomain();
        public List<TestCasesDomain> TestCases { get; set; }

        //métodos
        public List<TestDomain> Get()
        {
            return TestFunctions.getTests();
        }

        public bool Insert()
        {
            return TestFunctions.insertTest(this);
        }
    }

    public class CategoriesDomain
    {
        private contracts.ITestFunctionsDomain TestFunctions;

        //constructores
        public CategoriesDomain() {  }

        public CategoriesDomain(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            TestFunctions = p_TestFunctions;
        }

        //propiedades
        public int id { get; set; }
        public string Categorie { get; set; } = "";

        public CategoriesDomain Categorie_dad { get; set; }

        //métodos
        public List<CategoriesDomain> Get(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            TestFunctions = p_TestFunctions;
            return TestFunctions.getCategories();
        }
    }

    public class ExecutionPropertiesDomain
    {
        public ExecutionPropertiesDomain() {  }

        public enumTestTypes TestType;

        public contracts.ITest OBJ;
        //public Object OBJ;
    }

    public class TestCasesDomain
    {
        private static contracts.ITestFunctionsDomain MyTestFunctions;

        //constructores
        public TestCasesDomain() {  }

        public TestCasesDomain(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            MyTestFunctions = p_TestFunctions;
        }

        //propiedades
        public Int64 id { get; set; }
        public int Orden { get; set; }
        public string Function { get; set; }
        public string Description { get; set; }

        public TestDomain Test { get; set; }
        public TestCaseExecutionsDomain TestCaseExecution { get; set; } = new TestCaseExecutionsDomain(MyTestFunctions);

        public Int64 idTest { get; set; }

        //métodos
        public TestCasesDomain Insert()
        {
            return MyTestFunctions.insertTestCase(this);
        }
    }

    public class TestCaseExecutionsDomain
    {
        private contracts.ITestFunctionsDomain TestFunctions;

        //constructores
        public TestCaseExecutionsDomain() {  }

        public TestCaseExecutionsDomain(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            TestFunctions = p_TestFunctions;
        }

        //propiedades
        public Int64 id { get; set; }
        public Int64 idTestCase { get; set; } 

        public DateTime dtBegin { get; set; }
        public DateTime dtEnd { get; set; }

        public double Miliseconds
        {
            get
            {
                TimeSpan _ts = this.dtEnd - this.dtBegin;
                return _ts.TotalMilliseconds;
            }
        }

        //métodos
        public bool Insert(contracts.ITestFunctionsDomain p_TestFunctions)
        {
            TestFunctions = p_TestFunctions;
            return TestFunctions.InsertExecution(this);
        }
    }
}

Cabe destacar el cambio relevante en la clase ExecutionPropertiesDomain, propiedad OBJ que ya no será más un Object genérico, sino una implementación de ITest, que en la práctica será un test_base.


Además hemos incorporado las funciones básicas InsertTest, InsertTaseCase, etc... como métodos que se pueden invocar dentro de la propia clase de dominio, para aprovechar precisamente el contexto de la misma. Aquí se lo muestro con el ejemplo CategoriesDomain.Get(), y con TestDomain.Insert() y TestDomain.Get(). Estos métodos en su conjunto implementan la interfaz ITestFunctionsDomain.



Finalmente, la inyección de dependencias: llegamos a la capa de presentación, donde por milagro de la inversión de dependencias, todas las capas van a reunirse, presentarse e inyectarse unas con otras. Las abstracciones van a concretarse:


3. El control de errores

Queda próxima la publicación de un artículo extendido sobre control de errores.

En este punto vamos a dar por bueno el control de errores ya implementado en Trazabilidad y control de errores, y limitarnos a extenderlo hasta asegurar una cobertura próxima al 100%. Nos resultará más fácil después del repaso que le hemos dado a la estructura y dependencias del proyecto.

Capa de datos: quitamos todos los try {} catch {} que hubiéramos dejado en la capa de datos. Vamos a delegar el control de errores a las funciones de negocio. Los métodos de acceso a datos quedan 100% cubiertos ya que nunca llamamos directamente a un método en la capa de datos, si no es invocando antes a un método de negocio.

Tengan en cuenta que un try {} catch {} sin su correspondiente entrada de información en el fichero de log no es más que un error que queda oculto.

Capa de negocio: revisamos todos los métodos públicos de negocio y aseguramos la existencia de try {} catch {} con entrada en el log en el catch, mientras que en los métodos privados los suprimo, ya que quedarán a la fuerza inmersos en los públicos.

La revisión de las primeras clases de negocio afecta a Business_Data_Functions y Business_Test_Functions_EF.

Respecto de las clases estáticas fibo, quijote, etc. suprimo control de errores, ya que éste ha debido quedar totalmente cubierto por las clases de los test, test1_, test2_, etc...

En las clases test1, test2, etc. referidas al código de los propios test, aseguramos control de errores con entrada en el log en los métodos tanto públicos (Start) como privados (TestCases), y aseguramos también mensaje al usuario.

Afecta a test1_multithreading_vs_singlethreading, test2_basicos_concatstrings y test3_sql_loaddata

También aseguramos los métodos comunes de la clase base test_base: InitTest(), EndTest(), InitTestCase() y EndTestCase(), así como SetMsgLeido() y SetMsg() aunque hay una propabilidad de fallo muy escasa, que quedaría igualmente cubierto en el ámbito del test.

Capa de dominio: aquí tenemos los contenedores o entidades de dominio, y la lógica asociada a éstos bien separada. La lógica de dominio (los métodos Test.Insert, TestCase.Insert, etc.) son en realidad meras instancias a funciones en la capa de negocio, que ya se encuentran securizadas. De modo que podemos optar por delegar el control de errores a la capa de negocio:

public class Domain_Test_Functions_EF : contracts.ITestFunctionsDomain
{
    private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

    private contracts.ITestFunctionsDomain TestFunctions;

    public Domain_Test_Functions_EF(contracts.ITestFunctionsDomain p_TestFunctions)
    {
        //data.data_functions _df = new data.data_functions();
        //_datatestrepository = new labs_repos(_df.GetLabsCnx());

        TestFunctions = p_TestFunctions;
    }

    public List<CategoriesDomain> getCategories()
    {
        List<CategoriesDomain> res = TestFunctions.getCategories();
        return res;
    }

    public List<TestDomain> getTests()
    {
        List<TestDomain> res = new List<TestDomain>();
        res = TestFunctions.getTests();

        return res;
    }

    public bool insertTest(TestDomain _test)
    {
        bool res = TestFunctions.insertTest(_test);
        return res;
    }

    public TestCasesDomain insertTestCase(TestCasesDomain _testcase)
    {
        TestCasesDomain res = TestFunctions.insertTestCase(_testcase);
        return _testcase;
    }

    public bool InsertExecution(TestCaseExecutionsDomain _testCaseExec)
    {
        bool res = TestFunctions.InsertExecution(_testCaseExec);
        return res;
    }
}

4. Acoplamientos en el modelo de parte de horas

En el artículo Principios SOLID: La inversión de dependencia les lancé queridos programadores el reto siguiente:

"Les invito a visitar los cambios en el cálculo del parte de horas anual, correspondiente al TEST3 ¿Son eficientes los ORM? ya que se trata, por su construcción, de un caso particular que no ha quedado completamente resuelto. He implementado una interfaz, he inyectado dependencias y aun así, las clases no han quedado plenamente desacopladas. Amigos, les propongo el reto de averiguar como es esto posible y, si encuentran la solución, no duden en compartirla."

Hemos recibido como era de esperar, una avalancha de respuestas impecables.

En el transcurso de la publicación actual hemos establecido como premisa, que damos por aceptable el acoplamiento entre las clases que son implementaciones finales de test (test1_, test2_, etc) con aquellas recogidas en la carpeta functions, muchas de las cuales son clases estáticas como fibo o quijote. Pero sí se observa en la clase parte_horas_functions alguna particularidad. Para empezar no es estática. Maneja el objeto de dominio ParteHorasDomain, y contiene los métodos precisamente para producir información de este tipo.

Damos por tanto por bueno un acoplamiento entre test3_sql_loaddata parte_horas_functions, que se manifiesta a través de la declaración parte_horas_functions _phfunctions = new parte_horas_functions(); que me lo encuentro en el método Start de test3_sql_loaddata, y que voy a proceder a mover al constructor.

No hemos creado abstracciones de las clases de dominio. Damos por buena por tanto la definición del objeto List<ParteHorasDomain> listahoras = _phfunctions.Generate(100, 2020); dentro del método Start.

Cosa distinta es lo que ocurre en los métodos EFBulkData ADOBulkData_Datatable de la clase test3_sql_loaddata. El primero invoca la grabación de la info. en BBDD de esta manera:

sqltest_repos_partehoras _testrepos = new sqltest_repos_partehoras(DataFunctions.GetLabsCnx());   


     
Mientras que el segundo lo hace así:

data_test_partehoras _data_test_sql = new data_test_partehoras(DataFunctions.GetLabsCnx());


Podrán observar que hace caso omiso de la interfaz IParteHorasRepository, implementada por las clases sqltest_repos_partehoras  y data_test_partehoras. En este punto hemos de inyectar IParteHorasRepository y usar la abstracción del repositorio adecuadamente.


Tampoco queremos invocar directamente un repositorio desde una clase de tipo testX_ como es  test3_sql_loaddata. Parece más adecuado hacerlo a través de sendos métodos InsertParteAnualADOInsertParteAnualEF ubicados en la clase de negocio parte_horas_functions. Donde además ya habremos inyectado la dependencia IParteHorasRepository.


6. Fibo Vs Fibo Linq

Este es un pequeño ejercicio para desarrollar soltura en el uso de la plataforma, cuyo objetivo es añadir -muy rápidamente- un nuevo testcase en un test existente. Para ello vamos a emplear el ejemplo propuesto en el artículo Principios SOLID. La inversión de dependencias, en concreto las dos implementaciones de que disponemos del cálculo de la serie fibo. 

public static class fibo_functions 
{
    public static List<ulong> CalcFibo(int numelements)
    {
        List<ulong> lstelements = new List<ulong>();

        int cont = 0;

        ulong anterior = 0;
        ulong anterioranterior = 0;

        while (cont < numelements)
        {
            if (cont == 0)
            {
                lstelements.Add(0);
                anterioranterior = 0;
            }
            else if (cont == 1)
            {
                lstelements.Add(1);
                anterior = 1;
            }
            else
            {
                ulong newelem = anterioranterior + anterior;

                anterioranterior = anterior;
                anterior = newelem;

                lstelements.Add(newelem);
            }

            cont++;
        }

        return lstelements;
    }

    public static List<ulong> CalcFiboLinq(int numelements)
    {
        List<ulong> fibonacciNumbers = new List<ulong>();

        Enumerable.Range(0, numelements)
            .ToList()
            .ForEach(k => fibonacciNumbers.Add(k <= 1 ? 1 : fibonacciNumbers[k - 2] + fibonacciNumbers[k - 1]));

        return fibonacciNumbers.Take(numelements).ToList();
    }
}

Y vamos a usar nuestro querido y recurrente Test 1: Multithreading vs Singlethreading, donde por ejemplo en el contexto del SinglethreadingCase invocamos al método CalcFibo. Pero hasta ahora solo hemos podido llamar a uno u otro, ahora queremos llamar a ambos para contraponerlos. Para eso crearemos un caso nuevo al que llamaremos SinglethreadingLinqCase.

SinglethreadingCase  y SinglethreadingLinqCase solo se diferencian en el método usado para calcular la serie fibo, por lo que entendemos que la comparación es válida.

        public void SinglethreadingLinqCase(ref TestCasesDomain _testcase)
        {
            try
            {
                _testcase.TestCaseExecution.idTestCase = _testcase.id;

                //registra inicio
                _testcase.TestCaseExecution.dtBegin = DateTime.Now;
                InitTestCase(_testcase.Function, _testcase.TestCaseExecution.dtBegin);

                //500 iteraciones calculando 200 elementos de la serie fibo
                int cont = 0;

                while (cont < 500)
                {
                    fibo_functions.CalcFiboLinq(200);
                    Thread.Sleep(55);

                    cont++;
                }

                //registra fin
                _testcase.TestCaseExecution.dtEnd = DateTime.Now;
                EndTestCase(_testcase.Function, _testcase.TestCaseExecution);
            }
            catch (Exception ex)
            {
                _testexec.SetMsg("Error ejecutando SinglethreadingCase");
                _logger.Error(ex, "Error ejecutando SinglethreadingCase");
            }
        }

Hecho lo cual, (i) añadimos la invocación al testcase en el método Start de test1_multithreading_vs_singlethreading

public override void Start()
{
    try
    {
        //inicia test
        this.InitTest();

        //recorre y ejecuta testcases
        int cont = 0;

        while (cont < Test.TestCases.Count)
        {
            TestCasesDomain _testcase = Test.TestCases.Where(ord => ord.Orden == cont + 1).First();

            switch (_testcase.Function)
            {
                case "MultithreadingCase":

                    MultithreadingCase(ref _testcase);
                    break;

                case "MultithreadingCaseWithErrors":

                    MultithreadingCaseWithErrors(ref _testcase);
                    break;

                case "SinglethreadingCase":

                    SinglethreadingCase(ref _testcase);
                    break;

                case "HybridCase":

                    HybridCase(ref _testcase);
                    break;

                case "SinglethreadingLinqCase":

                    SinglethreadingLinqCase(ref _testcase);
                    break;
            }

            cont++;
            Thread.Sleep(1000);
        }

        //finaliza test
        this.EndTest();
    }
    catch (Exception ex)
    {
        _testexec.SetMsg("Error ejecutando test1_multithreading_vs_singlethreading - Start");
        _logger.Error(ex, "Error ejecutando test1_multithreading_vs_singlethreading - Start");
    }
}

...un código que habrá que mejorar también, pero eso ya será otro día, que... ¡mire que horas nos han dado! (ii) A continuación incorporamos la información del testcase en BBDD. Para ello directamente tocaremos el método Seed, de la clase Migrations.Configuration prestando atención especial al campo Orden.


tCases - Test1
EFModels.TestCasesModel _test1_case1 = new EFModels.TestCasesModel()
{
    id = 1,
    Orden = 1,
    Function = "MultithreadingCase",
    Description = "Cálculo simultáneo de la serie fibo (500 hilos, 200 elementos por hilo)",

    Test = _test1,
    idTest = _test1.id
};
EFModels.TestCasesModel _test1_case1WithErrors = new EFModels.TestCasesModel()
{
    id = 8,
    Orden = 2,
    Function = "MultithreadingCaseWithErrors",
    Description = "igual que MultithreadingCase, pero provocando una excepción en la ejecución de cada hilo, y desencadenando 500 entradas en fichero log",

    Test = _test1,
    idTest = _test1.id
};
EFModels.TestCasesModel _test1_case2 = new EFModels.TestCasesModel()
{
    id = 2,
    Orden = 3,
    Function = "SinglethreadingCase",
    Description = "Cálculo secuencial de la serie fibo (500 iteraciones, 200 elementos por iteración) con instrucción one-line de linq",

    Test = _test1,
    idTest = _test1.id
};
EFModels.TestCasesModel _test1_caseLinq = new EFModels.TestCasesModel()
{
    id = 9,
    Orden = 4,
    Function = "SinglethreadingLinqCase",
    Description = "Cálculo secuencial de la serie fibo (500 iteraciones, 200 elementos por iteración)",

    Test = _test1,
    idTest = _test1.id
};
EFModels.TestCasesModel _test1_case3 = new EFModels.TestCasesModel()
{
    id = 3,
    Orden = 5,
    Function = "HybridCase",
    Description = "20 hilos calculan 25 veces cada uno la serie fibo",

    Test = _test1,
    idTest = _test1.id
};

//TestCases - Test2
EFModels.TestCasesModel _test2_case1 = new EFModels.TestCasesModel()
{
    id = 4,
    Orden = 1,
    Function = "Concat_PlusOperator",
    Description = "Concatenación con operador + Concatena 100 veces 26 variables string con el operador",
                
    Test = _test2,
    idTest = _test2.id
};
EFModels.TestCasesModel _test2_case2 = new EFModels.TestCasesModel()
{
    id = 5,
    Orden = 2,
    Function = "Concat_StringBuilder",
    Description = "Concatenación con StringBuilder: Concatena 100 veces 26 variables string con un StringBuilder",

    Test = _test2,
    idTest = _test2.id
};

//TestCases - Test3
EFModels.TestCasesModel _test3_case1 = new EFModels.TestCasesModel()
{
    id = 6,
    Orden = 1, 
    Function = "EFBulkData",
    Description = "Grabación de parte de horas anual para 100 trabajadores con Entity Framework",

    Test = _test3,
    idTest = _test3.id
};
EFModels.TestCasesModel _test3_case2 = new EFModels.TestCasesModel()
{
    id = 7,
    Orden = 2,
    Function = "ADOBulkData_Datatable",
    Description = @"Grabación de parte de horas anual para 100 trabajadores con ADO.NET convirtiendo con reflection la clase Parte_Horas en un DataTable que recibe un procedimiento almacenado como parámetro",

    Test = _test3,
    idTest = _test3.id
};

context.TestCases.AddOrUpdate(tc => tc.id, _test1_case1);
context.TestCases.AddOrUpdate(tc => tc.id, _test1_case1WithErrors);
context.TestCases.AddOrUpdate(tc => tc.id, _test1_case2);
context.TestCases.AddOrUpdate(tc => tc.id, _test1_caseLinq);
context.TestCases.AddOrUpdate(tc => tc.id, _test1_case3);

En la siguiente ejecución de la plataforma podemos observar que los datos nuevos se graban en BBDD y se reflejan adecuadamente en el panel de información del TEST. Todo está listo para darle al PLAY.



Aunque la sintaxis en Linq pueda resultar ciertamente más bonita, se ejecuta en tiempos casi idénticos. 

Próximos artículos

La plataforma se encuentra en un momento dulce de madurez. Ello nos permitirá centrarnos en implementar bonitos tests cada vez más elaborados, ampliar categorías y abordar todo tipo de problemáticas. Siéntase libre de sugerirnos aguas que navegar, por ejemplo, a través de nuestra página en Facebook.

En materia de evolución de la plataforma siempre quedarán cositas por hacer. De momento está comprometida y en proceso la segunda parte de Trazabilidad y control de errores

También en breve verá la luz la segunda parte de ¿Son eficientes los ORM?, publicación que ha generado gran controversia en los medios internacionales.

Gracias por seguirnos.

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