
A nadie le gusta escribir exámenes. Por supuesto que estoy bromeando, ¡a todos les encanta escribirlos! Como dirán los líderes de equipo y RR.HH., la respuesta correcta en las entrevistas es que realmente me encantan y escribo exámenes. Pero de repente te gusta escribir exámenes en otro idioma. ¿Cómo comienzas a escribir el código de go cubierto de prueba?
Parte 1. Probar el controlador
Al salir de la caja, hay soporte para el servidor http en "net / http", por lo que puede levantarlo sin ningún esfuerzo. Las oportunidades que se han abierto nos permiten sentirnos extremadamente poderosos y, por lo tanto, nuestro código devolverá al usuario número 42.
func userHandler(w http.ResponseWriter, r *http.Request) { var user User userId, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil { w.Write([]byte( "Error")) return } if userId == 42 { user = User{userId, "Jack", 2} } jsonData, _ := json.Marshal(user) w.Write(jsonData) } type User struct { Id int Name string Rating uint }
Este código recibe el parámetro de identificación de usuario como entrada, luego emula la presencia del usuario en la base de datos y regresa. Ahora tenemos que probarlo ...
Hay una cosa maravillosa "net / http / httptest", le permite simular una llamada a nuestro controlador y luego comparar la respuesta.
r := httptest.NewRequest("GET", "http://127.0.0.1:80/user?id=42", nil) w := httptest.NewRecorder() userHandler(w, r) user := User{} json.Unmarshal(w.Body.Bytes(), &user) if user.Id != 42 { t.Errorf("Invalid user id %d expected %d", user.Id, 42) }
Parte 2. Cariño, tenemos una API externa aquí
¿Y por qué necesitamos respirar, si acabamos de calentarnos? Dentro de nuestros servicios, tarde o temprano, aparecerá una API externa. Esta es una extraña bestia a menudo escondida que puede comportarse como le plazca. Para las pruebas, nos gustaría un colega más complaciente. Y nuestro httptest recientemente descubierto también nos ayudará aquí. Como ejemplo, el código de llamada es una API externa con transferencia de datos adicional.
func ApiCaller(user *User, url string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() return updateUser(user, resp.Body) }
Para vencer esto, podemos hacer una simulación de API externa, la opción más simple es:
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Access-Control-Allow-Origin", "*") fmt.Fprintln(w, `{ "result": "ok", "data": { "user_id": 1, "rating": 42 } }`) })) defer ts.Close() user := User{id: 1} err := ApiCaller(&user, ts.URL)
ts.URL contendrá una cadena con el formato `http: //127.0.0.1: 49799`, que será el simulacro de API que llama a nuestra implementación
Parte 3. Trabajemos con la base
Hay una manera simple: elevar la ventana acoplable con la base, realizar migraciones, accesorios y ejecutar nuestro excelente servicio. Pero intentemos escribir pruebas con un mínimo de dependencias con servicios externos.
La implementación de trabajar con la base in go le permite reemplazar el controlador en sí y, sin pasar por 100 páginas de código y reflexión, le sugiero que tome la biblioteca
github.com/DATA-DOG/go-sqlmockPuede lidiar con sql.Db en el dock. Tomemos un ejemplo un poco más interesante, en el que habrá una orma para -
gorm .
func DbListener(db *gorm.DB) { user := User{} transaction := db.Begin() transaction.First(&user, 1) transaction.Model(&user).Update("counter", user.Counter+1) transaction.Commit() }
Espero que este ejemplo al menos te haya hecho pensar cómo probarlo. En "mock.ExpectExec" puede sustituir una expresión regular que cubre el caso que necesita. Lo único que debe recordar es que el orden en que se establece la expectativa debe coincidir con el orden y el número de llamadas.
func TestDbListener(t *testing.T) { db, mock, _ := sqlmock.New() defer db.Close() mock.ExpectBegin() result := []string{"id", "name", "counter"} mock.ExpectQuery("SELECT \\* FROM `Users`").WillReturnRows(sqlmock.NewRows(result).AddRow(1, "Jack", 2)) mock.ExpectExec("UPDATE `Users`").WithArgs(3, 1).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() gormDB, _ := gorm.Open("mysql", db) DbListener(gormDB.LogMode(true)) if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } }
Encontré muchos ejemplos para probar la base
aquí .
Parte 4. Trabajando con el sistema de archivos
Probamos nuestra mano en diferentes áreas y nos reconciliamos con que todo es bueno para mojarse. Aquí no todo está tan claro. Sugiero dos enfoques, mojarse o usar el sistema de archivos.
Opción 1: todos nos
mojamos en
github.com/spf13/aferoPros :
- No tiene que rehacer nada si ya está utilizando esta biblioteca. (pero entonces estás aburrido de leerlo)
- Trabajar con un sistema de archivos virtual, lo que acelerará enormemente sus pruebas.
Contras :
- Se requiere la modificación del código existente.
- El chmod no funciona en el sistema de archivos virtual. Pero pueden ser características desde la documentación dice "Evitar problemas de seguridad y permisos".
De estos pocos puntos, inmediatamente hice 2 pruebas. En la versión con el sistema de archivos, creé un archivo ilegible y verifiqué cómo funcionaba el sistema.
func FileRead(path string) error { path = strings.TrimRight(path, "/") + "/" // files, err := ioutil.ReadDir(path) if err != nil { return fmt.Errorf("cannot read from file, %v", err) } for _, f := range files { deleteFileName := path + f.Name() _, err := ioutil.ReadFile(deleteFileName) if err != nil { return err } err = os.Remove(deleteFileName) // } return nil }
El uso de afero.Fs requiere modificaciones mínimas, pero fundamentalmente nada cambia en el código
func FileReadAlt(path string, fs afero.Fs) error { path = strings.TrimRight(path, "/") + "/" // files, err := afero.ReadDir(fs, path) if err != nil { return fmt.Errorf("cannot read from file, %v", err) } for _, f := range files { deleteFileName := path + f.Name() _, err := afero.ReadFile(fs, deleteFileName) if err != nil { return err } err = fs.Remove(deleteFileName) // } return nil }
Pero nuestra diversión no será completa a menos que descubramos cuánto más rápido es afero que nativo.
Minuto de referencia:
BenchmarkIoutil 5000 242504 ns/op 7548 B/op 27 allocs/op BenchmarkAferoOs 300000 4259 ns/op 2144 B/op 30 allocs/op BenchmarkAferoMem 300000 4169 ns/op 2144 B/op 30 allocs/op
Por lo tanto, la biblioteca es un orden de magnitud por delante del estándar, pero el uso del sistema de archivos virtual o real es a su discreción.
Yo recomiendo:
haisum.imtqy.com/2017/09/11/golang-ioutil-readallmatthias-endler.de/2018/go-io-testingEpílogo
Sinceramente, realmente me gusta el 100% de cobertura, pero el código que no es de la biblioteca no lo necesita. E incluso no garantiza la protección contra errores. Concéntrese en los requisitos comerciales, no en la capacidad de una función para devolver 10 errores diferentes.
Para aquellos que les gusta meter código y ejecutar pruebas, un
repositorio .