07 Curso Go -Gestión de proyectos-

blog-image

En este nuevo nuevo curso de go aprenderemos el manejo de dependencias, creación de tests que analicen nuestro código y benchmark para obtener análisis de resultados.

Si no has visto la parte anterior del curso

https://blog.ironchip.net/2019/04/30/06-curso-go-computacion-concurrente/ te recomendamos que lo mires , aprendimos la creación de hilos y comunicaciones entre estos.

En esta entrega necesitaremos usar un entorno de desarrollo de Go, si aun no tienes el tuyo , te indicamos como hacerlo de forma sencilla aquí:

https://blog.ironchip.net/2019/05/06/preparando-intellij-para-go/

Imports En este primer apartado podemnos aprender como utilizar paquetes de terceros con el fin de utilizar librerias ya creadas por la comunidad y asi perfeccioanr nuestro codigo. Para esto introduciremos la libreria que queremos usar y le asignamos un nombre para poder realizar llamadas como podemos ver en la linea 8 Una vez creado el codigo instalamos la dependencia, en este caso

go get github.com/pelletier/go-toml y despues lo ejecutamos normalmente 

go run main.go
package main

import (
   "fmt"
   "log"
   "os"

   toml "github.com/pelletier/go-toml"
)

// Config is configuration
type Config struct {
   Login struct {
      User     string
      Password string
   }
}

func main() {
   file, err := os.Open("config.toml")
   if err != nil {
      log.Fatalf("error: can't open config file - %s", err)
   }
   defer file.Close()

   cfg := &Config{}
   dec := toml.NewDecoder(file)
   if err := dec.Decode(cfg); err != nil {
      log.Fatalf("error: can't decode configuration file - %s", err)
   }

   fmt.Printf("%+vn", cfg)
}

Test En este apartado aprenderemos a crear tests que evalúen nuestro código y asi comprobar su correcto funcionamiento A continuación tenemos la función raíz cuadrada

package sqrt
import (
   "errors"
)
// Common errors
var (
   ErrNegSqrt    = errors.New("sqrt of negative number")
   ErrNoSolution = errors.New("no solution found")
)
// Abs returns the absolute value of val
func Abs(val float64) float64 {
   if val < 0 {
      return -val
   }
   return val
}
// Sqrt return the square root of a number
func Sqrt(val float64) (float64, error) {
   if val < 0.0 {
      return 0.0, ErrNegSqrt
   }
   if val == 0.0 {
      return 0.0, nil // shortcut
   }
   guess, epsilon := 1.0, 0.00001
   for i := 0; i < 10000; i++ {
      if Abs(guess*guess-val) <= epsilon {
         return guess, nil
      }
      guess = (val/guess + guess) / 2.0
   }
   return 0.0, ErrNoSolution
} Y aquí tenemos el código que se encontrara en la función Test.go y que evaluará el código 

package sqrt
import (
   "fmt"
   "testing"
)
func almostEqual(v1, v2 float64) bool {
   return Abs(v1-v2) <= 0.001
}
func TestSimple(t *testing.T) {
   val, err := Sqrt(2)
   if err != nil {
      t.Fatalf("error in calculation - %s", err)
   }
   if !almostEqual(val, 1.414214) {
      t.Fatalf("bad value - %f", val)
   }
}
type testCase struct {
   value    float64
   expected float64
}
func TestMany(t *testing.T) {
   testCases := []testCase{
      {0.0, 0.0},
      {2.0, 1.414214},
      {9.0, 3.0},
   }
   for _, tc := range testCases {
      t.Run(fmt.Sprintf("%f", tc.value), func(t *testing.T) {
         out, err := Sqrt(tc.value)
         if err != nil {
            t.Fatal("error")
         }
         if !almostEqual(out, tc.expected) {
            t.Fatalf("%f != %f", out, tc.expected)
         }
      })
   }
} Ejecutaremos el test mediante 
 go test -v

Challenge: Test El reto en esta ocasión será obtener de un fichero un listado de numeros que corresponderán a cada numero con su raiz cuadrada. El programa a crear deberá abrir el fichero csv y evaluar los datos pasándolos por la función sqrt anterior y obtener los resultados comprándolos con los del fichero El contenido del fichero sqrt_cases.csv es:

0,0
2,1.4142
3,1.7321
4,2.0 La solución a este problema seria el siguiente 
package sqrt
import (
   "encoding/csv"
   "fmt"
   "io"
   "os"
   "strconv"
   "testing"
)
func almostEqual(v1, v2 float64) bool {
   return Abs(v1-v2) <= 0.001
}
func TestMany(t *testing.T) {
   file, err := os.Open("sqrt_cases.csv")
   if err != nil {
      t.Fatalf("can't open cases file - %s", err)
   }
   defer file.Close()
   rdr := csv.NewReader(file)
   for {
      record, err := rdr.Read()
      if err == io.EOF {
         break
      }
      if err != nil {
         t.Fatalf("error reading cases file - %s", err)
      }
      val, err := strconv.ParseFloat(record[0], 64)
      if err != nil {
         t.Fatalf("bad value - %s", record[0])
      }
      expected, err := strconv.ParseFloat(record[1], 64)
      if err != nil {
         t.Fatalf("bad value - %s", record[1])
      }
      t.Run(fmt.Sprintf("%f", val), func(t *testing.T) {
         out, err := Sqrt(val)
         if err != nil {
            t.Fatal(err)
         }
         if !almostEqual(out, expected) {
            t.Fatalf("%f != %f", out, expected)
         }
      })
   }
}

Benchmarks y perfiles Los benchmarks son herramientas que nos ayudan a evaluar el rendimiento de un código Este es el codigo que ejecutará ciclicamente llamadas a nuestra aplicacion y obtendra los tiempos de procesado. Lo ejecutaramos mediante el comando:

go test -v -bench .

package sqrt
import (
   "testing"
)
func almostEqual(v1, v2 float64) bool {
   return Abs(v1-v2) <= 0.001
}
func TestSimple(t *testing.T) {
   val, err := Sqrt(2)
   if err != nil {
      t.Fatalf("error in calculation - %s", err)
   }
   if !almostEqual(val, 1.414214) {
      t.Fatalf("bad value - %f", val)
   }
}
func BenchmarkSqrt(b *testing.B) {
   for i := 0; i < b.N; i++ {
      _, err := Sqrt(float64(i))
      if err != nil {
         b.Fatal(err)
      }
   }
} Podemos centrarnos en los resultados del benchamrk mediante el comando: 

go test -v -bench . -run TTT Si queremos generar un informe detallado podemos hacer lo sioguiente 

go test -v -bench . -run TTT -cpuprofile=prof.out Con este comando he generado el informe completo y mediante: 

go tool pprof prof.out
list Sqrt

Mediante ese comando obtendremos el tiempo de cpu requerido en cada una de las partes del código.

Conclusiones Esperamos que gracias a esta entrada hayáis podido ampliar vuestro conocimiento sobre el lenguaje de programcion Go o hayáis obtenido respuestas.

En la siguiente y ultima parte de esta serie de tutoriales trabajaremos con las llamadas de red y así poder crear una aplicación conectada con otras, cosa indispensable en gran parte de los proyectos hoy en día.