Prueba funciones de HTTP

Existen dos tipos distintos de Cloud Functions, cada uno con sus propios requisitos de prueba.

La estructura de prueba de una función depende de qué recursos de Google Cloud Platform use esa función. A su vez, el uso de los recursos de una función depende de cómo se active esa función.

En este documento, se describe cómo probar HTTP de Cloud Function. Consulta la documentación sobre cómo probar funciones en segundo plano para obtener información sobre cómo evaluar funciones en segundo plano.

Funciones activadas por HTTP

Al igual que la mayoría de las pruebas de unidades de Cloud Functions, las que pertenecen a la función activada por HTTP poseen una estructura específica. Sin embargo, las pruebas de integración y del sistema poseen una estructura similar, lo que no suele ocurrir con pruebas relativas a otros tipos de funciones.

A continuación, se muestra un ejemplo de una función de HTTP:

Node.js

const escapeHtml = require('escape-html');

    /**
     * HTTP Cloud Function.
     *
     * @param {Object} req Cloud Function request context.
     *                     More info: https://expressjs.com/en/api.html#req
     * @param {Object} res Cloud Function response context.
     *                     More info: https://expressjs.com/en/api.html#res
     */
    exports.helloHttp = (req, res) => {
      res.send(`Hello ${escapeHtml(req.query.name || req.body.name || 'World')}!`);
    };

Python

from flask import escape

    def hello_http(request):
        """HTTP Cloud Function.
        Args:
            request (flask.Request): The request object.
            <http://flask.pocoo.org/docs/1.0/api/#flask.Request>
        Returns:
            The response text, or any set of values that can be turned into a
            Response object using `make_response`
            <http://flask.pocoo.org/docs/1.0/api/#flask.Flask.make_response>.
        """
        request_json = request.get_json(silent=True)
        request_args = request.args

        if request_json and 'name' in request_json:
            name = request_json['name']
        elif request_args and 'name' in request_args:
            name = request_args['name']
        else:
            name = 'World'
        return 'Hello {}!'.format(escape(name))

Go


    // Package helloworld provides a set of Cloud Functions samples.
    package helloworld

    import (
    	"encoding/json"
    	"fmt"
    	"html"
    	"net/http"
    )

    // HelloHTTP is an HTTP Cloud Function with a request parameter.
    func HelloHTTP(w http.ResponseWriter, r *http.Request) {
    	var d struct {
    		Name string `json:"name"`
    	}
    	if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
    		fmt.Fprint(w, "Hello, World!")
    		return
    	}
    	if d.Name == "" {
    		fmt.Fprint(w, "Hello, World!")
    		return
    	}
    	fmt.Fprintf(w, "Hello, %s!", html.EscapeString(d.Name))
    }
    

Pruebas de unidad

Estas pruebas actúan como pruebas de unidad para la función activada por HTTP anterior.

Node.js

Express se simula con Sinon.
const assert = require('assert');
    const sinon = require('sinon');
    const uuid = require('uuid');

    const {helloHttp} = require('..');

    it('helloHttp: should print a name', () => {
      // Mock ExpressJS 'req' and 'res' parameters
      const name = uuid.v4();
      const req = {
        query: {},
        body: {
          name: name,
        },
      };
      const res = {send: sinon.stub()};

      // Call tested function
      helloHttp(req, res);

      // Verify behavior of tested function
      assert.ok(res.send.calledOnce);
      assert.deepStrictEqual(res.send.firstCall.args, [`Hello ${name}!`]);
    });

Python

Flask se simula con unittest.
from unittest.mock import Mock

    import main

    def test_print_name():
        name = 'test'
        data = {'name': name}
        req = Mock(get_json=Mock(return_value=data), args=data)

        # Call tested function
        assert main.hello_http(req) == 'Hello {}!'.format(name)

    def test_print_hello_world():
        data = {}
        req = Mock(get_json=Mock(return_value=data), args=data)

        # Call tested function
        assert main.hello_http(req) == 'Hello World!'

Go


    package helloworld

    import (
    	"net/http/httptest"
    	"strings"
    	"testing"
    )

    func TestHelloHTTP(t *testing.T) {
    	tests := []struct {
    		body string
    		want string
    	}{
    		{body: `{"name": ""}`, want: "Hello, World!"},
    		{body: `{"name": "Gopher"}`, want: "Hello, Gopher!"},
    	}

    	for _, test := range tests {
    		req := httptest.NewRequest("GET", "/", strings.NewReader(test.body))
    		req.Header.Add("Content-Type", "application/json")

    		rr := httptest.NewRecorder()
    		HelloHTTP(rr, req)

    		if got := rr.Body.String(); got != test.want {
    			t.Errorf("HelloHTTP(%q) = %q, want %q", test.body, got, test.want)
    		}
    	}
    }
    

Usa el siguiente comando para ejecutar las pruebas de unidad:

Node.js

    mocha test/sample.unit.http.test.js --exit
    

Python

    pytest sample_http_test.py
    

Go

    go test -v ./hello_http_test.go
    

Pruebas de integración

Estas pruebas actúan como pruebas de integración para la función anterior:

Node.js

const assert = require('assert');
    const execPromise = require('child-process-promise').exec;
    const path = require('path');
    const requestRetry = require('requestretry');
    const uuid = require('uuid');

    const PORT = 9010;
    const BASE_URL = `http://localhost:${PORT}`;
    const cwd = path.join(__dirname, '..');

      let ffProc;

      // Run the functions-framework instance to host functions locally
      before(() => {
        // exec's 'timeout' param won't kill children of "shim" /bin/sh process
        // Workaround: include "& sleep <TIMEOUT>; kill $!" in executed command
        ffProc = execPromise(
          `functions-framework --target=helloHttp --signature-type=http --port ${PORT} & sleep 2; kill $!`,
          {shell: true, cwd}
        );
      });

      after(async () => {
        // Wait for the functions framework to stop
        await ffProc;
      });

      it('helloHttp: should print a name', async () => {
        const name = uuid.v4();

        const response = await requestRetry({
          url: `${BASE_URL}/helloHttp`,
          method: 'POST',
          body: {name},
          retryDelay: 200,
          json: true,
        });

        assert.strictEqual(response.statusCode, 200);
        assert.strictEqual(response.body, `Hello ${name}!`);
      });
    });

Para ejecutar las pruebas de integración para las funciones de HTTP, usa el siguiente comando:

Node.js

    export BASE_URL=http://localhost:8010/YOUR_GCP_PROJECT_ID/YOUR_GCF_REGION
    mocha test/sample.integration.http.test.js --exit
    

Donde:

  • YOUR_GCP_PROJECT_ID es el ID del proyecto de Cloud.
  • YOUR_GCF_REGION es la región de tus funciones de Cloud Functions.
  • BASE_URL es una variable de entorno que especifica la URL en la que se puede alcanzar la función. Las variables de entorno te permiten especificar valores disponibles solo en tu entorno de pruebas local. Esto te permite evitar codificar estos valores en tu código.

Pruebas de sistema

Estas pruebas actúan como pruebas de sistema para la función anterior:

Node.js

Ten en cuenta que las pruebas de sistema son idénticas a las pruebas de integración de la función.
const assert = require('assert');
    const Supertest = require('supertest');
    const supertest = Supertest(process.env.BASE_URL);

    describe('system tests', () => {
      it('helloHttp: should print a name', async () => {
        await supertest
          .post('/helloHttp')
          .send({name: 'John'})
          .expect(200)
          .expect(response => {
            assert.strictEqual(response.text, 'Hello John!');
          });
      });

Python

import os
    import uuid

    import requests

    def test_no_args():
        BASE_URL = os.getenv('BASE_URL')
        assert BASE_URL is not None

        res = requests.get('{}/hello_http'.format(BASE_URL))
        assert res.text == 'Hello, World!'

    def test_args():
        BASE_URL = os.getenv('BASE_URL')
        assert BASE_URL is not None

        name = str(uuid.uuid4())
        res = requests.post(
          '{}/hello_http'.format(BASE_URL),
          json={'name': name}
        )
        assert res.text == 'Hello, {}!'.format(name)

Go


    package helloworld

    import (
    	"io/ioutil"
    	"net/http"
    	"net/url"
    	"os"
    	"strings"
    	"testing"
    	"time"
    )

    func TestHelloHTTPSystem(t *testing.T) {
    	client := http.Client{
    		Timeout: 10 * time.Second,
    	}
    	urlString := os.Getenv("BASE_URL") + "/HelloHTTP"
    	testURL, err := url.Parse(urlString)
    	if err != nil {
    		t.Fatalf("url.Parse(%q): %v", urlString, err)
    	}

    	tests := []struct {
    		body string
    		want string
    	}{
    		{body: `{"name": ""}`, want: "Hello, World!"},
    		{body: `{"name": "Gopher"}`, want: "Hello, Gopher!"},
    	}

    	for _, test := range tests {
    		req := &http.Request{
    			Method: http.MethodPost,
    			Body:   ioutil.NopCloser(strings.NewReader(test.body)),
    			URL:    testURL,
    		}
    		resp, err := client.Do(req)
    		if err != nil {
    			t.Fatalf("HelloHTTP http.Get: %v", err)
    		}
    		body, err := ioutil.ReadAll(resp.Body)
    		if err != nil {
    			t.Fatalf("HelloHTTP ioutil.ReadAll: %v", err)
    		}
    		if got := string(body); got != test.want {
    			t.Errorf("HelloHTTP(%q) = %q, want %q", test.body, got, test.want)
    		}
    	}
    }
    

A fin de ejecutar pruebas del sistema para funciones de HTTP, implementa tus funciones con el siguiente comando:

Node.js

gcloud functions deploy helloHttp --runtime nodejs8 
Puedes usar los siguientes valores para que la marca --runtime especifique tu versión preferida de Node.js:
  • nodejs6 (obsoleto)
  • nodejs8
  • nodejs10 (Beta)

Python

gcloud functions deploy hello_http --runtime python37 

Go

gcloud functions deploy HelloHTTP --runtime go111 
Puedes usar los siguientes valores para que la marca --runtime especifique tu versión preferida de Go:
  • go111
  • go113 (Beta)

Usa los siguientes comandos para probar la función de HTTP implementada. Ten en cuenta que la diferencia principal entre las pruebas del sistema y las de integración para funciones de HTTP de Cloud Functions es la URL en la que se puede alcanzar la función:

Node.js

    export BASE_URL=https://YOUR_GCF_REGION-YOUR_GCP_PROJECT_ID.cloudfunctions.net/
    mocha test/sample.system.http.test.js --exit
    

Python

    export BASE_URL=https://YOUR_GCF_REGION-YOUR_GCP_PROJECT_ID.cloudfunctions.net/
    pytest sample_http_test_system.py
    

Go

    export BASE_URL=https://YOUR_GCF_REGION-YOUR_GCP_PROJECT_ID.cloudfunctions.net/
    go test -v ./hello_http_system_test.go
    

donde:

  • YOUR_GCF_REGION es la región de tus funciones de Cloud Functions.
  • YOUR_GCP_PROJECT_ID es el ID del proyecto de Cloud.