Closed diagutierrezro closed 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)
})
}
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
}
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