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("%+v\n", 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.


Miguel Martinez

Miguel Martinez

Desarrollador Full Stack. Interés por nuevas tecnologías , desarrollo de software en diversos lenguajes. Ambición por aprender cosas nuevas , trabajo en equipo y junto a ellos sacar nuevos proyectos adelante.

Leave a Reply

Your email address will not be published. Required fields are marked *