udistrital / core_documentacion

0 stars 0 forks source link

Desarrollar pruebas unitarias. #202

Closed diagutierrezro closed 1 month ago

diagutierrezro commented 1 month ago

Se requiere realizar el desarrollo de pruebas unitarias en el sistema para realizar distintas pruebas del comportamiento del sistema.

Sub Tareas

Criterios de aceptación

Requerimientos

No aplica

Definition of Ready - DoR

Definition of Done - DoD - Desarrollo

JulianAvella commented 1 month ago

Se hace una función test para cada función que se usa en la api, y se tienen dos casos de prueba, uno donde los datos esten completos y de forma correcta y responda de la forma que se espera, y el otro caso es donde los datos no esten completos y falle la respuesta de la función de la forma que se espera.

En las funciones que no tienen métodos http, la función de prueba queda así y hace el test correctamente:

func TestExisteDependencia(t *testing.T) { t.Log("//////////////////////////////////") t.Log("Inicio TestExisteDependencia") t.Log("//////////////////////////////////")

t.Run("Caso 1: La dependencia existe", func(t *testing.T) {
    dependencias := []models.RespuestaBusquedaDependencia{
        {Dependencia: &models.Dependencia{Id: 1}},
    }
    if result := services.ExisteDependencia(dependencias, 1); result {
        t.Log("La dependencia con ID 1 existe")
    } else {
        t.Errorf("No existe la dependencia con ID 1")
    }
})

}

Pero se esta presentando un error en las funciones que tienen métodos http, porque se esta ejecutando el método http de la función que se prueba, para eso se esta buscando la manera con mocks se crear el objeto de respuesta o envio que simularia la petición http y una forma para evitar que no se ejecute la petición de la función a probar, ya que genera el error:

runtime error: invalid memory address or nil pointer dereference [recovered] panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x38 pc=0xc03840]

goroutine 10 [running]: testing.tRunner.func1.2({0xcf0dc0, 0x1747cb0}) /root/programas/go/src/testing/testing.go:1389 +0x24e testing.tRunner.func1() /root/programas/go/src/testing/testing.go:1392 +0x39f panic({0xcf0dc0, 0x1747cb0}) /root/programas/go/src/runtime/panic.go:838 +0x207 github.com/aws/aws-xray-sdk-go/xray.(*Segment).DownstreamHeader(0x0) /root/go/pkg/mod/github.com/aws/aws-xray-sdk-go@v1.8.1/xray/segment_model.go:142 +0x40 github.com/udistrital/utils_oas/xray.BeginSegmentSec(0xc00013ec00) /root/go/pkg/mod/github.com/udistrital/utils_oas@v0.0.0-20240829190820-0bdc14dd8544/xray/xray.go:309 +0x3d
github.com/udistrital/utils_oas/request.GetJson({0xc0001de1a0?, 0x0?}, {0xc849e0, 0xc000118e40}) /root/go/pkg/mod/github.com/udistrital/utils_oas@v0.0.0-20240829190820-0bdc14dd8544/request/request_tools.go:211 +0xfc github.com/udistrital/gestion_dependencias_mid/services.CrearRespuestaBusqueda({0x1, {0x0, 0x0}, {0x0, 0x0}, {0x0, 0x0}, 0x1, {0x0, 0x0}, ...})

El test que esta generando el error es:

func TestCrearRespuestaBusqueda(t *testing.T) { t.Log("//////////////////////////////////") t.Log("Inicio TestCrearRespuestaBusqueda") t.Log("//////////////////////////////////")

httpmock.Activate()
defer httpmock.DeactivateAndReset()

t.Run("Caso 1: La dependencia tiene los datos son completos", func(t *testing.T) {
    dependencia := models.Dependencia{
        Id:     1,
        Activo: true,
        DependenciaTipoDependencia: []*models.DependenciaTipoDependencia{
            {
                Activo: true,
                TipoDependenciaId: &models.TipoDependencia{
                    Id:     1,
                    Nombre: "Tipo 1",
                },
            },
        },
    }

    urlDependencia := beego.AppConfig.String("OikosCrudUrl") + "dependencia?query=Id:" + strconv.Itoa(dependencia.Id)
    httpmock.RegisterResponder("GET", urlDependencia,
        httpmock.NewJsonResponderOrPanic(http.StatusOK, []models.Dependencia{dependencia}),
    )

    urlDependenciaPadre := beego.AppConfig.String("OikosCrudUrl") + "dependencia_padre?query=HijaId:" + strconv.Itoa(dependencia.Id)
    httpmock.RegisterResponder("GET", urlDependenciaPadre,
        httpmock.NewJsonResponderOrPanic(http.StatusOK, []models.DependenciaPadre{
            {
                Id:     1,
                PadreId: &dependencia,
                Activo: true,
            },
        }),
    )
    t.Log("111111111111111111")
    resultado := services.CrearRespuestaBusqueda(dependencia)
    t.Log("22222222222222222222222")
    /*if resultado.Estado != true {
        t.Errorf("Se esperaba que el estado fuera true, pero se obtuvo %v", resultado.Estado)
    } 

    if resultado.TipoDependencia == nil || len(*resultado.TipoDependencia) == 0 {
        t.Errorf("Se esperaba que el tipo de dependencia no fuera nil o vacío")
    } 

    if resultado.DependenciaAsociada == nil || resultado.DependenciaAsociada.Id != 1 {
        t.Errorf("Se esperaba que la dependencia asociada tuviera el ID 1, pero se obtuvo %v", resultado.DependenciaAsociada)
    } */

    if resultado.Dependencia == nil || resultado.Dependencia.Id != 1 {
        t.Errorf("Se esperaba que el ID de la dependencia fuera 1, pero se obtuvo %v", resultado.Dependencia)
    } else {
        t.Log("Se encontro el ID de la dependencia con sus datos completos")
    }
    t.Log("termino caso 1")
})
t.Log("empieza caso 2")
t.Run("Caso 2: Falta la dependencia o el tipo de dependencia", func(t *testing.T) {
    dependencia := models.Dependencia{
        Id:     2,
        Activo: true,
    }

    urlDependencia := beego.AppConfig.String("OikosCrudUrl") + "dependencia?query=Id:" + strconv.Itoa(dependencia.Id)
    httpmock.RegisterResponder("GET", urlDependencia,
        httpmock.NewJsonResponderOrPanic(http.StatusOK, []models.Dependencia{}), // Sin dependencias
    )

    urlDependenciaPadre := beego.AppConfig.String("OikosCrudUrl") + "dependencia_padre?query=HijaId:" + strconv.Itoa(dependencia.Id)
    httpmock.RegisterResponder("GET", urlDependenciaPadre,
        httpmock.NewJsonResponderOrPanic(http.StatusOK, []models.DependenciaPadre{}), // Sin dependencia padre
    )

    defer func() {
        if r := recover(); r == nil {
            t.Errorf("Se esperaba que la función panic cuando los datos no son completos, pero no ocurrió")
        }
    }()

    services.CrearRespuestaBusqueda(dependencia)
})

}

JulianAvella commented 1 month ago

Después de hacer varias pruebas y probrar diferentes librerias, con la libreria "bou.ke/monkey", se pudieron crear los mock para sustituir y simular las peticiones http que se ejecutan dentro de las diferentes funciones, de esta forma se pudieron desarrollar los tests para probrar dichas funciones.

El siguiente test:

func TestCrearRespuestaBusqueda(t *testing.T) { t.Log("//////////////////////////////////") t.Log("Inicio TestCrearRespuestaBusqueda") t.Log("//////////////////////////////////")

  t.Run("Caso exitoso", func(t *testing.T) {
      // Generación dinámica de datos de prueba
      dependenciaID := 223
      nombreDependencia := "Dependencia de prueba"

      // Mock para el caso exitoso
      monkey.Patch(request.GetJson, func(url string, target interface{}) error {
          if strings.Contains(url, fmt.Sprintf("dependencia?query=Id:%d", dependenciaID)) {
              *target.(*[]models.Dependencia) = []models.Dependencia{
                  {
                      Id:                      dependenciaID,
                      Nombre:                  nombreDependencia,
                      TelefonoDependencia:     "321654987",
                      CorreoElectronico:       "info@desarrollo.com",
                      Activo:                  true,
                      FechaCreacion:           "2023-01-10T09:00:00Z",
                      FechaModificacion:       "2023-01-10T09:00:00Z",
                      DependenciaTipoDependencia: []*models.DependenciaTipoDependencia{
                          {
                              Activo: true,
                              TipoDependenciaId: &models.TipoDependencia{
                                  Id:     1,
                                  Nombre: "Tipo 1",
                              },
                          },
                      },
                  },
              }
              return nil
          } else if strings.Contains(url, fmt.Sprintf("dependencia_padre?query=HijaId:%d", dependenciaID)) {
              *target.(*[]models.DependenciaPadre) = []models.DependenciaPadre{
                  {
                      Id:              1,
                      PadreId:         &models.Dependencia{Id: 2, Nombre: "Dependencia Padre", Activo: true},
                      HijaId:          &models.Dependencia{Id: dependenciaID},
                      Activo:          true,
                      FechaCreacion:   "2024-01-01T00:00:00Z",
                      FechaModificacion: "2024-01-02T00:00:00Z",
                  },
              }
              return nil
          }
          return errors.New("URL no esperada")
      })
      defer monkey.UnpatchAll()

      // Datos para el caso exitoso
      dependencia := models.Dependencia{
          Id:                      dependenciaID,
          Nombre:                  nombreDependencia,
          TelefonoDependencia:     "321654987",
          CorreoElectronico:       "info@desarrollo.com",
          Activo:                  true,
          DependenciaTipoDependencia: []*models.DependenciaTipoDependencia{},
      }

      resultado := services.CrearRespuestaBusqueda(dependencia)

      // Validaciones para el caso exitoso
      if resultado.Dependencia.Id != dependenciaID {
          t.Errorf("Se esperaba Id %d, pero se obtuvo %d", dependenciaID, resultado.Dependencia.Id)
      }
      if resultado.Dependencia.Nombre != nombreDependencia {
          t.Errorf("Se esperaba Nombre '%s', pero se obtuvo '%s'", nombreDependencia, resultado.Dependencia.Nombre)
      }
      if resultado.DependenciaAsociada == nil || resultado.DependenciaAsociada.Id != 2 {
          t.Errorf("Se esperaba una DependenciaAsociada con Id 2, pero se obtuvo %+v", resultado.DependenciaAsociada)
      }
  })

  t.Run("Caso fallido", func(t *testing.T) {
      // Generación dinámica de datos de prueba
      dependenciaID := 999
      nombreDependencia := "Dependencia inexistente"

      // Mock para el caso fallido
      monkey.Patch(request.GetJson, func(url string, target interface{}) error {
          if strings.Contains(url, fmt.Sprintf("dependencia?query=Id:%d", dependenciaID)) {
              *target.(*[]models.Dependencia) = []models.Dependencia{
                  {
                      Id:                      dependenciaID,
                      Nombre:                  nombreDependencia,
                      TelefonoDependencia:     "000000000",
                      CorreoElectronico:       "inexistente@desarrollo.com",
                      Activo:                  true,
                      DependenciaTipoDependencia: []*models.DependenciaTipoDependencia{},
                  },
              }
              return nil
          } else if strings.Contains(url, fmt.Sprintf("dependencia_padre?query=HijaId:%d", dependenciaID)) {
              *target.(*[]models.DependenciaPadre) = []models.DependenciaPadre{}
              return nil
          }
          return errors.New("URL no esperada")
      })
      defer monkey.UnpatchAll()

      // Datos para el caso fallido
      dependenciaFallida := models.Dependencia{
          Id:                      dependenciaID,
          Nombre:                  nombreDependencia,
          TelefonoDependencia:     "000000000",
          CorreoElectronico:       "inexistente@desarrollo.com",
          Activo:                  true,
          DependenciaTipoDependencia: []*models.DependenciaTipoDependencia{},
      }

      resultadoFallido := services.CrearRespuestaBusqueda(dependenciaFallida)

      // Validación para el caso fallido: se espera que no haya DependenciaAsociada
      if resultadoFallido.DependenciaAsociada != nil {
          t.Errorf("Se esperaba que no hubiera Dependencia Asociada, pero se obtuvo %+v", resultadoFallido.DependenciaAsociada)
      }

      // Validar específicamente si no se encontró una dependencia hija asociada
      if resultadoFallido.DependenciaAsociada == nil {
          t.Logf("El test falló porque no se encontró una dependencia hija asociada para el ID %d", dependenciaID)
      }
  })

}

Prueba la siguiente función:

func CrearRespuestaBusqueda(dependencia models.Dependencia) models.RespuestaBusquedaDependencia { var resultado models.RespuestaBusquedaDependencia resultado.Dependencia = &dependencia resultado.Estado = dependencia.Activo

  if len(dependencia.DependenciaTipoDependencia) == 0 {
      var dependenciaAux []models.Dependencia
      fmt.Println("ejecuta la funcion")
      url := beego.AppConfig.String("OikosCrudUrl") + "dependencia?query=Id:" + strconv.Itoa(dependencia.Id)
      if err := request.GetJson(url, &dependenciaAux); err != nil {
          logs.Error(err)
          panic(err.Error())
      }

      dependencia = dependenciaAux[0]
  }
  tiposDependencia := make([]models.TipoDependencia, 0)
  for _, dt := range dependencia.DependenciaTipoDependencia {
      if dt.Activo {
          tipoDependencia := models.TipoDependencia{
              Id:     dt.TipoDependenciaId.Id,
              Nombre: dt.TipoDependenciaId.Nombre,
          }
          tiposDependencia = append(tiposDependencia, tipoDependencia)
      }
  }
  resultado.TipoDependencia = &tiposDependencia

  var dependenciaPadre []models.DependenciaPadre
  url := beego.AppConfig.String("OikosCrudUrl") + "dependencia_padre?query=HijaId:" + strconv.Itoa(dependencia.Id)
  if err := request.GetJson(url, &dependenciaPadre); err != nil {
      logs.Error(err)
      panic(err.Error())
  }
  if len(dependenciaPadre) > 0 {
      resultado.DependenciaAsociada = dependenciaPadre[0].PadreId
  }
  fmt.Println("finaliza la funcion")
  return resultado

}

Entonces lo que hacen los mocks es sustituir mientras se ejectua el test que ese mismo ejecuta la función, es que estas sentencias "request.GetJson" se sustituyan con el fin de no hacer la petición HTTP para no modificar la BD.

Se tienen dos inconvenientes, el primero son las funciones que no se exportan de los archivos y que solo se pueden llamar dentro del mismo archivo que fueron escritas, estas funciones empiezan con letra minúscula, como la siguiente función:

func nuevoTipoDependencia(tipo int, dependenciaId models.Dependencia, tiposRegistrados *[]int, dependenciaOriginal models.Dependencia) { var tipoDependencia models.TipoDependencia url := beego.AppConfig.String("OikosCrudUrl") + "tipo_dependencia/" + strconv.Itoa(tipo) if err := request.GetJson(url, &tipoDependencia); err != nil || tipoDependencia.Id == 0 { rollbackDependenciaTipoDependencia(dependenciaOriginal,tiposRegistrados) logs.Error(err) panic(err.Error()) }

  nuevaDependenciaTipoDependencia := models.DependenciaTipoDependencia{
      TipoDependenciaId: &tipoDependencia,
      DependenciaId:     &dependenciaId,
      Activo:            true,
      FechaCreacion:     time_bogota.TiempoBogotaFormato(),
      FechaModificacion: time_bogota.TiempoBogotaFormato(),
  }

  url = beego.AppConfig.String("OikosCrudUrl") + "dependencia_tipo_dependencia"
  var res map[string]interface{}
  if err := request.SendJson(url, "POST", &res, nuevaDependenciaTipoDependencia); err != nil || res["Id"] == nil{
      rollbackDependenciaTipoDependencia(dependenciaOriginal,tiposRegistrados)
      logs.Error(err)
      panic(err.Error())
  }

  *tiposRegistrados = append(*tiposRegistrados, int(res["Id"].(float64)))

}

La anterior función no se puede llamar para hacerle un test, porque no se puede exportar a otro archivo, solo tiene dos soluciones, que se refactorice el código para que se pueda exportar, crear otra función dentro del mismo archivo para que se pueda exportar y que esa llame a la función que no se puede exportar y la función que se puede exportar llamarla del archivo que se le va a hacer el test o la otra sería hacer la función test dentro del mismo archivo que esta escrita dicha función.

Entonces del inconveniente anterior surge el otro inconveniente que son las funciones que empienzan con letra mayúscula y que se pueden exportar, que dentro de su código llaman a una que otra función que no se puede exportar y no se sabría como sustituir, ya que debido a eso, esa función que no se puede exportar ejecuta la petición http que tiene dentro de su código lo cual genera error, como la siguiente función:

func RegistrarDependencia(transaccion *models.NuevaDependencia) (alerta []string, outputError map[string]interface{}){ defer func() { if err := recover(); err != nil { outputError = map[string]interface{}{"funcion": "RegistrarDependencia", "err": err, "status": "500"} panic(outputError) } }() alerta = append(alerta, "Success")

var tiposDependencia []*models.TipoDependencia
for _, tipo := range transaccion.TipoDependenciaId {
    var tipoDependencia models.TipoDependencia
    tipoDependencia = **verificarExistenciaTipo**(tipo)
    tiposDependencia = append(tiposDependencia, &tipoDependencia)
}

var dependenciaAsociada models.Dependencia
url := beego.AppConfig.String("OikosCrudUrl") + "dependencia/" + strconv.Itoa(transaccion.DependenciaAsociadaId)
if err := request.GetJson(url,&dependenciaAsociada); err != nil || dependenciaAsociada.Id == 0{
    logs.Error(err)
    panic(err.Error())
}

var creaciones models.Creaciones
var dependenciaNueva models.Dependencia
dependenciaNueva.Nombre = transaccion.Dependencia.Nombre
dependenciaNueva.CorreoElectronico = transaccion.Dependencia.CorreoElectronico
dependenciaNueva.TelefonoDependencia = transaccion.Dependencia.TelefonoDependencia
dependenciaNueva.Activo = true
dependenciaNueva.FechaCreacion = time_bogota.TiempoBogotaFormato()
dependenciaNueva.FechaModificacion = time_bogota.TiempoBogotaFormato()
var resDependenciaRegistrada map[string]interface{}
url = beego.AppConfig.String("OikosCrudUrl") + "dependencia"
if err := request.SendJson(url,"POST",&resDependenciaRegistrada,dependenciaNueva); err != nil{
    logs.Error(err)
    panic(err.Error())
}
fmt.Println("FUNCIONO REGISTRO DE DEPENDENCIA")
fmt.Println(resDependenciaRegistrada["Id"])
dependenciaNueva.Id = int(resDependenciaRegistrada["Id"].(float64))
creaciones.DependenciaId =  int(resDependenciaRegistrada["Id"].(float64))
for _, tipoDependencia := range tiposDependencia{
    var dependenciaTipoDependenciaNueva models.DependenciaTipoDependencia
    dependenciaTipoDependenciaNueva.TipoDependenciaId = tipoDependencia
    dependenciaTipoDependenciaNueva.DependenciaId = &dependenciaNueva
    dependenciaTipoDependenciaNueva.Activo = true
    dependenciaTipoDependenciaNueva.FechaCreacion = time_bogota.TiempoBogotaFormato()
    dependenciaTipoDependenciaNueva.FechaModificacion = time_bogota.TiempoBogotaFormato()
    url = beego.AppConfig.String("OikosCrudUrl") + "dependencia_tipo_dependencia"
    var resDependenciaTipoDependenciaRegistrada map[string]interface{}
    if err := request.SendJson(url,"POST",&resDependenciaTipoDependenciaRegistrada,dependenciaTipoDependenciaNueva); err != nil{
        if (len(creaciones.DependenciaTipoDependenciaId) >= 1){
            **rollbackDependenciaTipoDependenciaCreada**(&creaciones)
        }else{
            rollbackDependenciaCreada(&creaciones)
        }
        logs.Error(err)
        panic(err.Error())
    }
    creaciones.DependenciaTipoDependenciaId = append(creaciones.DependenciaTipoDependenciaId, int(resDependenciaTipoDependenciaRegistrada["Id"].(float64)))
}
fmt.Println("FUNCIONO DEPENDENCIA TIPO DEPENDENCIA")
fmt.Println(creaciones.DependenciaTipoDependenciaId)
var depedencia_padre models.DependenciaPadre
depedencia_padre.PadreId = &dependenciaAsociada
depedencia_padre.HijaId = &dependenciaNueva
depedencia_padre.Activo = true
depedencia_padre.FechaCreacion = time_bogota.TiempoBogotaFormato()
depedencia_padre.FechaModificacion = time_bogota.TiempoBogotaFormato()
url = beego.AppConfig.String("OikosCrudUrl") + "dependencia_padre"
var resDependenciaPadre map[string]interface{}
if err := request.SendJson(url,"POST",&resDependenciaPadre,depedencia_padre);err != nil{
    **rollbackDependenciaTipoDependenciaCreada**(&creaciones)
    logs.Error(err)
    panic(err.Error())
}
fmt.Println("FUNCIONO DEPENDENCIA PADRE")
fmt.Println(resDependenciaPadre["Id"])

return alerta, outputError

}