IC y pruebas

En esta guía se abarcan recomendaciones para probar y, luego, implementar funciones de Cloud Functions. En ella también se analizan los tipos de pruebas que debes usar y se proporcionan algunas instrucciones para ejemplos de situaciones de prueba. Esta guía también incluye información sobre cómo ejecutar automáticamente tus pruebas y sobre cómo volver a implementar las pruebas de manera opcional con una plataforma de integración e implementación continuas como Cloud Build.

Antes de comenzar

Antes de comenzar con esta guía, configura tu entorno.

Node.js

  1. Ve a la guía de configuración de Node.js.
  2. De manera opcional, instala el emulador de Node.js.

Python (Beta)

  1. Ve a la guía de configuración de Python.
  2. Instala pytest.
    pip install --upgrade pytest
    

Go (Beta)

Ve a la guía de configuración de Go

Marcos de trabajo

Node.js

Los ejemplos de esta guía usan AVA como un marco de trabajo para pruebas a fin de ejecutar pruebas, y Sinon como un simulador de marcos de trabajo para simular dependencias externas.

Una dependencia externa es una dependencia en la que se basa tu función, que no forma parte del código de esta. Algunos ejemplos comunes de dependencias externas son los servicios de Google Cloud Platform y bibliotecas instaladas con administradores de paquetes como npm.

Python (Beta)

Los ejemplos de esta guía usan Pytest como un marco de trabajo para pruebas a fin de ejecutar pruebas, y unittest como un simulador de marcos de trabajo para simular dependencias externas.

Una dependencia externa es una dependencia en la que se basa tu función, que no forma parte del código de esta. Algunos ejemplos comunes de dependencias externas son otros servicios de Google Cloud Platform y bibliotecas instaladas con administradores de paquetes como pip.

Go (Beta)

Los ejemplos de esta guía usan el paquete estándar testing de biblioteca para ejecutar las pruebas. Sin embargo, las pruebas de sistema tienen dependencias externas.

Una dependencia externa es una dependencia en la que se basa tu función, que no forma parte del código de esta. Algunos ejemplos comunes de dependencias externas son otros servicios de Google Cloud Platform y otros paquetes que hayas descargado.

Tipos de pruebas

Existen tres tipos de pruebas que puedes usar cuando trabajas con Cloud Functions, cada una de las cuales prueba un aspecto diferente de tu código. Se enumeran a continuación, en orden ascendente en términos de su rigurosidad:

  • Pruebas de unidad
  • Pruebas de integración
  • Pruebas de sistema

En general, las pruebas más rigurosas tardan más tiempo en completarse. En este documento, se analizan estos tipos de pruebas en detalle, así como la forma de encontrar un balance entre rapidez y rigurosidad.

Pruebas de unidad

Las pruebas de unidad son pruebas de poco alcance para partes pequeñas y específicas de tu código. Estas pruebas son buenas para verificar rápidamente las suposiciones que se hicieron durante el proceso de desarrollo, como el manejo de casos extremos y la validación de entradas.

Por su diseño, las pruebas de unidad no prueban la integración en dependencias externas, como las propias funciones de Cloud Functions o algunos otros componentes de Google Cloud Platform. Puedes usar tu marco de trabajo de simulación para crear versiones simuladas de dependencias externas.

Para las funciones de HTTP, las pruebas deben simular la unión de marcos de trabajo de HTTP. Confirma el comportamiento de la función mediante la combinación de marcos de trabajo de prueba y de simulación, y compara los resultados de tu función con los valores esperados.

Las pruebas de unidad no pueden detectar cambios en dependencias externas. Si estas dependencias cambian, tanto el código probado como tus simulaciones deben actualizarse.

Pruebas de integración

Las pruebas de integración validan la interacción entre partes de tu código y, por lo general, tardan un tiempo prudencial en completarse. Por ejemplo, en Cloud Functions, las pruebas de integración pueden usarse para probar el uso que hace una función de los servicios de GCP, como Cloud Datastore o Cloud Vision.

La diferencia principal entre las pruebas de unidad y las de integración en relación con Cloud Functions es que las últimas implican menos simulación que las primeras. Las pruebas de integración deben activar y responder a los eventos de Cloud, como las solicitudes HTTP, los mensajes de Cloud Pub/Sub o los cambios de objeto de Storage.

Puedes ejecutar estas pruebas de manera local con un corrector de compatibilidad. Valida el comportamiento de la función mediante la confirmación del resultado esperado, de acuerdo con las entradas específicas.

Pruebas de sistema

Las pruebas de sistema son más complejas, ya que validan el comportamiento de tu función de Cloud Functions a través de varios componentes de GCP en un entorno de pruebas aislado.

Implementa tu función de Cloud Functions en un entorno de pruebas y evalúa su funcionalidad mediante la activación de eventos relevantes. Para validar tu función, lee los registros o revisa el comportamiento deseado.

Debes aislar tus entornos de desarrollo, pruebas y producción. Una forma de lograrlo es usar un proyecto de GCP individual para cada paso.

También debes asignar nombres únicos a nivel global de recursos de prueba de sistema para evitar que pruebas simultáneas interfieran entre sí. Puedes hacerlo mediante la creación y eliminación de manera programática de los recursos requeridos antes y después de la ejecución de la prueba.

Ejemplos de situaciones de prueba

Las situaciones que se presentan a continuación abarcan las diferentes formas de activar Cloud Functions. La estructura de las pruebas de una función depende en gran medida de los recursos de GCP que esta usa. A su vez, el uso de los recursos de una función depende de cómo se activa esa función.

Funciones activadas por HTTP

A diferencia de los demás tipos de funciones, las pruebas de sistema y de integración para las funciones activadas por HTTP son similares en estructura, pero la mayoría de las pruebas de unidad de funciones de HTTP tienen una estructura diferente.

Esta coincidencia entre las pruebas de sistema y de integración se muestra en el siguiente ejemplo de una función activada por 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 (Beta)

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 (Beta)

// Package helloworld provides a set of Cloud Function 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 test = require(`ava`);
const sinon = require(`sinon`);
const uuid = require(`uuid`);

const helloHttp = require(`..`).helloHttp;

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

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

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

test(`helloHttp: should print hello world`, t => {
  // Mock ExpressJS 'req' and 'res' parameters
  const req = {
    body: {},
  };
  const res = {send: sinon.stub()};

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

  // Verify behavior of tested function
  t.true(res.send.calledOnce);
  t.deepEqual(res.send.firstCall.args, [`Hello World!`]);
});

Python (Beta)

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 (Beta)

package helloworld

import (
	"io/ioutil"
	"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)

		out, err := ioutil.ReadAll(rr.Result().Body)
		if err != nil {
			t.Fatalf("ReadAll: %v", err)
		}
		if got := string(out); 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

ava test/sample.unit.http.test.js

Python (Beta)

pytest sample_http_test.py

Go (Beta)

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 test = require(`ava`);
const Supertest = require(`supertest`);
const supertest = Supertest(process.env.BASE_URL);

test.cb(`helloHttp: should print a name`, t => {
  supertest
    .post(`/helloHttp`)
    .send({name: 'John'})
    .expect(200)
    .expect(response => {
      t.is(response.text, 'Hello John!');
    })
    .end(t.end);
});

test.cb(`helloHttp: should print hello world`, t => {
  supertest
    .get(`/helloHttp`)
    .expect(200)
    .expect(response => {
      t.is(response.text, `Hello World!`);
    })
    .end(t.end);
});

Usa el siguiente archivo como un corrector de compatibilidad:

Node.js

// Import dependencies
const gcfCode = require('./index.js');
const express = require('express');

// TODO(developer): specify the port to use
// const PORT = 3000;

// Start local HTTP server
const app = express();
const server = require(`http`).createServer(app);
server.on('connection', socket => socket.unref());
server.listen(PORT);

// Register HTTP handlers
Object.keys(gcfCode).forEach(gcfFn => {
  // Handle a single HTTP request
  const handler = (req, res) => {
    gcfCode[gcfFn](req, res);
    server.close();
  };

  app.get(`/${gcfFn}`, handler);
  app.post(`/${gcfFn}`, handler);
});

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

Node.js

export PORT=8010
export BASE_URL=http://localhost:8010/YOUR_GCP_PROJECT_ID/YOUR_GCF_REGION
ava test/sample.integration.http.test.js

en el que:

  • YOUR_GCP_PROJECT_ID es el ID de tu proyecto de GCP.
  • YOUR_GCF_REGION es la región de tus funciones de Cloud Functions.
  • BASE_URL es una variable del entorno que especifica la URL en la que se puede conseguir la función. Las variables del 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 test = require(`ava`);
const Supertest = require(`supertest`);
const supertest = Supertest(process.env.BASE_URL);

test.cb(`helloHttp: should print a name`, t => {
  supertest
    .post(`/helloHttp`)
    .send({name: 'John'})
    .expect(200)
    .expect(response => {
      t.is(response.text, 'Hello John!');
    })
    .end(t.end);
});

test.cb(`helloHttp: should print hello world`, t => {
  supertest
    .get(`/helloHttp`)
    .expect(200)
    .expect(response => {
      t.is(response.text, `Hello World!`);
    })
    .end(t.end);
});

Python (Beta)

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 (Beta)

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)
		}
	}
}

Para ejecutar pruebas de sistema para funciones de HTTP, implementa tus funciones con el siguiente comando:

Node.js 6

gcloud functions deploy helloHttp --runtime nodejs6 

Node.js 8 (Beta)

gcloud functions deploy helloHttp --runtime nodejs8 

Python (Beta)

gcloud functions deploy hello_http --runtime python37 

Go (Beta)

gcloud functions deploy HelloHTTP --runtime go111 

Usa los siguientes comandos para probar la función de HTTP implementada:

Node.js

Ten en cuenta que la diferencia principal entre las pruebas de sistema y las de integración para funciones de HTTP de Cloud Functions es la URL en la que se puede conseguir la función.

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

Python (Beta)

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

Go (Beta)

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

en el que:

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

Funciones activadas por Pub/Sub

Las pruebas de las funciones activadas por Pub/Sub se estructuran de forma diferente según el lugar donde reside la función que se prueba.

Este es un ejemplo de una función activada por Pub/Sub:

Node.js 6

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} event The Cloud Functions event.
 * @param {function} callback The callback function.
 */
exports.helloPubSub = (event, callback) => {
  const pubsubMessage = event.data;
  const name = pubsubMessage.data
    ? Buffer.from(pubsubMessage.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);

  callback();
};

Node.js 8 (Beta)

/**
 * Background Cloud Function to be triggered by Pub/Sub.
 * This function is exported by index.js, and executed when
 * the trigger topic receives a message.
 *
 * @param {object} data The event payload.
 * @param {object} context The event metadata.
 */
exports.helloPubSub = (data, context) => {
  const pubSubMessage = data;
  const name = pubSubMessage.data
    ? Buffer.from(pubSubMessage.data, 'base64').toString()
    : 'World';

  console.log(`Hello, ${name}!`);
};

Python (Beta)

def hello_pubsub(data, context):
    """Background Cloud Function to be triggered by Pub/Sub.
    Args:
         data (dict): The dictionary with data specific to this type of event.
         context (google.cloud.functions.Context): The Cloud Functions event
         metadata.
    """
    import base64

    if 'data' in data:
        name = base64.b64decode(data['data']).decode('utf-8')
    else:
        name = 'World'
    print('Hello {}!'.format(name))

Go (Beta)

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

import (
	"context"
	"log"
)

// PubSubMessage is the payload of a Pub/Sub event. Please refer to the docs for
// additional information regarding Pub/Sub events.
type PubSubMessage struct {
	Data []byte `json:"data"`
}

// HelloPubSub consumes a Pub/Sub message.
func HelloPubSub(ctx context.Context, m PubSubMessage) error {
	name := string(m.Data)
	if name == "" {
		name = "World"
	}
	log.Printf("Hello, %s!", name)
	return nil
}

Pruebas de unidad

Estas pruebas actúan como pruebas de unidad para la función activada por Pub/Sub anterior:

Node.js 6

const test = require(`ava`);
const uuid = require(`uuid`);
const sinon = require(`sinon`);

const helloPubSub = require(`..`).helloPubSub;
const consoleLog = sinon.stub(console, 'log');

test.cb(`helloPubSub: should print a name`, t => {
  t.plan(1);

  // Initialize mocks
  const name = uuid.v4();
  const event = {
    data: {
      data: Buffer.from(name).toString(`base64`),
    },
  };

  // Call tested function and verify its behavior
  helloPubSub(event, () => {
    t.true(consoleLog.calledWith(`Hello, ${name}!`));
    t.end();
  });
});

test.cb(`helloPubSub: should print hello world`, t => {
  t.plan(1);

  // Initialize mocks
  const event = {
    data: {},
  };

  // Call tested function and verify its behavior
  helloPubSub(event, () => {
    t.true(consoleLog.calledWith(`Hello, World!`));
    t.end();
  });
});

Node.js 8 (Beta)

const test = require(`ava`);
const uuid = require(`uuid`);
const sinon = require(`sinon`);

const helloPubSub = require(`..`).helloPubSub;
const consoleLog = sinon.stub(console, 'log');

test(`helloPubSub: should print a name`, async t => {
  // Initialize mocks
  const name = uuid.v4();
  const event = {
    data: Buffer.from(name).toString(`base64`),
  };

  // Call tested function and verify its behavior
  await helloPubSub(event);
  t.true(consoleLog.calledWith(`Hello, ${name}!`));
});

test(`helloPubSub: should print hello world`, async t => {
  // Initialize mocks
  const event = {};

  // Call tested function and verify its behavior
  await helloPubSub(event);
  t.true(consoleLog.calledWith(`Hello, World!`));
});

Python (Beta)

import base64

import main

def test_print_hello_world(capsys):
    data = {}

    # Call tested function
    main.hello_pubsub(data, None)
    out, err = capsys.readouterr()
    assert out == 'Hello World!\n'

def test_print_name(capsys):
    name = 'test'
    data = {'data': base64.b64encode(name.encode())}

    # Call tested function
    main.hello_pubsub(data, None)
    out, err = capsys.readouterr()
    assert out == 'Hello {}!\n'.format(name)

Go (Beta)

package helloworld

import (
	"context"
	"io/ioutil"
	"log"
	"os"
	"testing"
)

func TestHelloPubSub(t *testing.T) {
	tests := []struct {
		data string
		want string
	}{
		{want: "Hello, World!\n"},
		{data: "Go", want: "Hello, Go!\n"},
	}
	for _, test := range tests {
		r, w, _ := os.Pipe()
		log.SetOutput(w)
		originalFlags := log.Flags()
		log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime))

		m := PubSubMessage{
			Data: []byte(test.data),
		}
		HelloPubSub(context.Background(), m)

		w.Close()
		log.SetOutput(os.Stderr)
		log.SetFlags(originalFlags)

		out, err := ioutil.ReadAll(r)
		if err != nil {
			t.Fatalf("ReadAll: %v", err)
		}
		if got := string(out); got != test.want {
			t.Errorf("HelloPubSub(%q) = %q, want %q", test.data, got, test.want)
		}
	}
}

Usa el siguiente comando para ejecutar las pruebas de unidad:

Node.js

ava test/sample.unit.pubsub.test.js

Python (Beta)

pytest sample_pubsub_test.py

Go (Beta)

go test -v ./hello_pubsub_test.go

Pruebas de integración

Estas pruebas actúan como pruebas de integración para la función activada por Pub/Sub anterior:

Node.js

const childProcess = require(`child_process`);
const test = require(`ava`);
const uuid = require(`uuid`);

test.serial(`helloPubSub: should print a name`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();
  const name = uuid.v4();

  // Mock Pub/Sub call, as the emulator doesn't listen to Pub/Sub topics
  const encodedName = Buffer.from(name).toString(`base64`);
  const data = JSON.stringify({data: encodedName});
  childProcess.execSync(`functions call helloPubSub --data '${data}'`);

  // Check the emulator's logs
  const logs = childProcess
    .execSync(`functions logs read helloPubSub --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`Hello, ${name}!`));
});

test.serial(`helloPubSub: should print hello world`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();

  // Mock Pub/Sub call, as the emulator doesn't listen to Pub/Sub topics
  childProcess.execSync(`functions call helloPubSub --data {}`);

  // Check the emulator's logs
  const logs = childProcess
    .execSync(`functions logs read helloPubSub --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`Hello, World!`));
});

Usa el siguiente archivo como un corrector de compatibilidad:

Node.js

// Import dependencies
const {PubSub} = require('@google-cloud/pubsub');
const pubsub = new PubSub();

// TODO(developer): specify a function to test
// const gcfCode = require('./index.js');
// const gcfFn = gcfCode.YOUR_FUNCTION;

// TODO(developer): specify an existing topic and subscription to use
// const topicName = process.env.TOPIC || 'YOUR_TOPIC';
// const subscriptionName = process.env.SUBSCRIPTION || 'YOUR_SUBSCRIPTION';

// Subscribe to Pub/Sub topic
const subscription = pubsub.topic(topicName).subscription(subscriptionName);

// Handle a single Pub/Sub message
const messageHandler = msg => {
  gcfFn({data: msg}, () => {
    msg.ack();
    subscription.removeListener(`message`, messageHandler);
  });
};
subscription.on(`message`, messageHandler);

A fin de ejecutar las pruebas de integración para esta función, completa los siguientes pasos:

Node.js

  1. De manera opcional, si no has configurado el tema ni la suscripción en tu archivo de corrección de compatibilidad, configura las siguientes variables del entorno:

    export TOPIC=YOUR_TOPIC
    export SUBSCRIPTION=YOUR_SUBSCRIPTION
    

    en las que:

    • YOUR_TOPIC es el nombre del tema de Cloud Pub/Sub al que quieres que se suscriban tus funciones.
    • YOUR_SUBSCRIPTION es tu suscripción a Cloud Pub/Sub.

  2. Para ejecutar la prueba, usa el siguiente comando:

    ava test/sample.integration.pubsub.test.js
    

Pruebas de sistema

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

Node.js

const childProcess = require(`child_process`);
const test = require(`ava`);
const uuid = require(`uuid`);
const {PubSub} = require(`@google-cloud/pubsub`);
const pubsub = new PubSub();

const topicName = process.env.FUNCTIONS_TOPIC;
const baseCmd = `gcloud functions`;

test(`helloPubSub: should print a name`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();
  const name = uuid.v4();

  // Publish to pub/sub topic
  const topic = pubsub.topic(topicName);
  const publisher = topic.publisher();
  await publisher.publish(Buffer.from(name));

  // Wait for logs to become consistent
  await new Promise(resolve => setTimeout(resolve, 15000));

  // Check logs after a delay
  const logs = childProcess
    .execSync(`${baseCmd} logs read helloPubSub --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`Hello, ${name}!`));
});

test(`helloPubSub: should print hello world`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();

  // Publish to pub/sub topic
  const topic = pubsub.topic(topicName);
  const publisher = topic.publisher();
  await publisher.publish(Buffer.from(''), {a: 'b'});

  // Wait for logs to become consistent
  await new Promise(resolve => setTimeout(resolve, 15000));

  // Check logs after a delay
  const logs = childProcess
    .execSync(`${baseCmd} logs read helloPubSub --start-time ${startTime}`)
    .toString();
  t.true(logs.includes('Hello, World!'));
});

Python (Beta)

from datetime import datetime
from os import getenv
import subprocess
import time
import uuid

from google.cloud import pubsub_v1
import pytest

PROJECT = getenv('GCP_PROJECT')
TOPIC = getenv('TOPIC')

assert PROJECT is not None
assert TOPIC is not None

@pytest.fixture(scope='module')
def publisher_client():
    yield pubsub_v1.PublisherClient()

def test_print_name(publisher_client):
    start_time = datetime.utcnow().isoformat()
    topic_path = publisher_client.topic_path(PROJECT, TOPIC)

    # Publish the message
    name = uuid.uuid4()
    data = str(name).encode('utf-8')
    publisher_client.publish(topic_path, data=data).result()

    # Wait for logs to become consistent
    time.sleep(15)

    # Check logs after a delay
    log_process = subprocess.Popen([
        'gcloud',
        'alpha',
        'functions',
        'logs',
        'read',
        'hello_pubsub',
        '--start-time',
        start_time
    ], stdout=subprocess.PIPE)
    logs = str(log_process.communicate()[0])
    assert 'Hello, {}!'.format(name) in logs

Go (Beta)

package helloworld

import (
	"context"
	"log"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"

	"cloud.google.com/go/pubsub"
	"github.com/gobuffalo/uuid"
)

func TestHelloPubSubSystem(t *testing.T) {
	ctx := context.Background()

	topicName := os.Getenv("FUNCTIONS_TOPIC")
	projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")

	startTime := time.Now().UTC().Format(time.RFC3339)

	// Create the Pub/Sub client and topic.
	client, err := pubsub.NewClient(ctx, projectID)
	if err != nil {
		log.Fatal(err)
	}
	topic := client.Topic(topicName)

	// Publish a message with a random string to verify.
	// We use a random string to make sure the function is logging the correct
	// message for this test invocation.
	u := uuid.Must(uuid.NewV4())
	msg := &pubsub.Message{
		Data: []byte(u.String()),
	}
	topic.Publish(ctx, msg).Get(ctx)

	// Wait for logs to be consistent.
	time.Sleep(20 * time.Second)

	// Check logs after a delay.
	cmd := exec.Command("gcloud", "alpha", "functions", "logs", "read", "HelloPubSub", "--start-time", startTime)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("exec.Command: %v", err)
	}
	if got := string(out); !strings.Contains(got, u.String()) {
		t.Errorf("HelloPubSub got %q, want to contain %q", got, u.String())
	}
}

Sigue estos pasos para ejecutar las pruebas de sistema:

  1. En tu proyecto de GCP, selecciona un tema de Cloud Pub/Sub al cual suscribir la función. Si proporcionas el nombre de un tema de Cloud Pub/Sub que no existe, este se crea automáticamente.

  2. A continuación, implementa tus funciones con el siguiente comando:

    Node.js 6

    gcloud functions deploy helloPubSub --runtime nodejs6 --trigger-topic YOUR_PUBSUB_TOPIC

    Node.js 8 (Beta)

    gcloud functions deploy helloPubSub --runtime nodejs8 --trigger-topic YOUR_PUBSUB_TOPIC

    Python (Beta)

    gcloud functions deploy hello_pubsub --runtime python37 --trigger-topic YOUR_PUBSUB_TOPIC

    Go (Beta)

    gcloud functions deploy HelloPubSub --runtime go111 --trigger-topic YOUR_PUBSUB_TOPIC

    en el que YOUR_PUBSUB_TOPIC es el nombre del tema de Cloud Pub/Sub al que quieres que se suscriban tus funciones.

  3. Ejecuta las pruebas de sistema con el siguiente comando:

    Node.js

    export FUNCTIONS_TOPIC=YOUR_PUBSUB_TOPIC
    ava test/sample.system.pubsub.test.js
    

    Python (Beta)

    export FUNCTIONS_TOPIC=YOUR_PUBSUB_TOPIC
    pytest sample_pubsub_test_system.py
    

    Go (Beta)

    export FUNCTIONS_TOPIC=YOUR_PUBSUB_TOPIC
    go test -v ./hello_pubsub_system_test.go
    

    en el que YOUR_PUBSUB_TOPIC es el nombre del tema de Cloud Pub/Sub al que quieres que se suscriban tus funciones.

Funciones activadas por Storage

Las pruebas para las funciones activadas por Storage son similares en estructura a sus contrapartes activadas por Cloud Pub/Sub. Al igual que las pruebas de las funciones activadas por Cloud Pub/Sub, las pruebas de las funciones activadas por Storage se estructuran de forma diferente según el lugar donde se aloja la función que se prueba.

Aquí te mostramos un ejemplo de una función activada por Storage:

Node.js 6

/**
 * Background Cloud Function to be triggered by Cloud Storage.
 *
 * @param {object} event The Cloud Functions event.
 * @param {function} callback The callback function.
 */
exports.helloGCS = (event, callback) => {
  const file = event.data;

  if (file.resourceState === 'not_exists') {
    console.log(`File ${file.name} deleted.`);
  } else if (file.metageneration === '1') {
    // metageneration attribute is updated on metadata changes.
    // value is 1 if file was newly created or overwritten
    console.log(`File ${file.name} uploaded.`);
  } else {
    console.log(`File ${file.name} metadata updated.`);
  }

  callback();
};

Node.js 8 (Beta)

/**
 * Background Cloud Function to be triggered by Cloud Storage.
 *
 * @param {object} data The event payload.
 * @param {object} context The event metadata.
 */
exports.helloGCS = (data, context) => {
  const file = data;
  if (file.resourceState === 'not_exists') {
    console.log(`File ${file.name} deleted.`);
  } else if (file.metageneration === '1') {
    // metageneration attribute is updated on metadata changes.
    // on create value is 1
    console.log(`File ${file.name} uploaded.`);
  } else {
    console.log(`File ${file.name} metadata updated.`);
  }
};

Python (Beta)

def hello_gcs(data, context):
    """Background Cloud Function to be triggered by Cloud Storage.
    Args:
         data (dict): The dictionary with data specific to this type of event.
         context (google.cloud.functions.Context): The Cloud Functions
         event metadata.
    """
    print("File: {}.".format(data['objectId']))

Go (Beta)

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

import (
	"context"
	"log"
)

// GCSEvent is the payload of a GCS event. Please refer to the docs for
// additional information regarding GCS events.
type GCSEvent struct {
	Bucket         string `json:"bucket"`
	Name           string `json:"name"`
	Metageneration string `json:"metageneration"`
	ResourceState  string `json:"resourceState"`
}

// HelloGCS consumes a GCS event.
func HelloGCS(ctx context.Context, e GCSEvent) error {
	if e.ResourceState == "not_exists" {
		log.Printf("File %v deleted.", e.Name)
		return nil
	}
	if e.Metageneration == "1" {
		// The metageneration attribute is updated on metadata changes.
		// The on create value is 1.
		log.Printf("File %v created.", e.Name)
		return nil
	}
	log.Printf("File %v metadata updated.", e.Name)
	return nil
}

Pruebas de unidad

Estas son las pruebas de unidad para la función activada por Storage anterior:

Node.js 6

const test = require(`ava`);
const uuid = require(`uuid`);
const sinon = require(`sinon`);

const helloGCS = require(`..`).helloGCS;
const consoleLog = sinon.stub(console, 'log');

test.cb(`helloGCS: should print uploaded message`, t => {
  t.plan(1);

  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    data: {
      name: filename,
      resourceState: 'exists',
      metageneration: '1',
    },
  };

  // Call tested function and verify its behavior
  helloGCS(event, () => {
    t.true(consoleLog.calledWith(`File ${filename} uploaded.`));
    t.end();
  });
});

test.cb(`helloGCS: should print metadata updated message`, t => {
  t.plan(1);

  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    data: {
      name: filename,
      resourceState: 'exists',
      metageneration: '2',
    },
  };

  // Call tested function and verify its behavior
  helloGCS(event, () => {
    t.true(consoleLog.calledWith(`File ${filename} metadata updated.`));
    t.end();
  });
});

test.cb(`helloGCS: should print deleted message`, t => {
  t.plan(1);

  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    data: {
      name: filename,
      resourceState: 'not_exists',
      metageneration: '3',
    },
  };

  // Call tested function and verify its behavior
  helloGCS(event, () => {
    t.true(consoleLog.calledWith(`File ${filename} deleted.`));
    t.end();
  });
});

Node.js 8 (Beta)

const test = require(`ava`);
const uuid = require(`uuid`);
const sinon = require(`sinon`);

const helloGCS = require(`..`).helloGCS;
const consoleLog = sinon.stub(console, 'log');

test(`helloGCS: should print uploaded message`, async t => {
  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    name: filename,
    resourceState: 'exists',
    metageneration: '1',
  };

  // Call tested function and verify its behavior
  await helloGCS(event);
  t.true(consoleLog.calledWith(`File ${filename} uploaded.`));
});

test(`helloGCS: should print metadata updated message`, async t => {
  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    name: filename,
    resourceState: 'exists',
    metageneration: '2',
  };

  // Call tested function and verify its behavior
  await helloGCS(event);
  t.true(consoleLog.calledWith(`File ${filename} metadata updated.`));
});

test(`helloGCS: should print deleted message`, async t => {
  // Initialize mocks
  const filename = uuid.v4();
  const event = {
    name: filename,
    resourceState: 'not_exists',
    metageneration: '3',
  };

  // Call tested function and verify its behavior
  await helloGCS(event);
  t.true(consoleLog.calledWith(`File ${filename} deleted.`));
});

Python (Beta)

import main

def test_print(capsys):
    name = 'test'
    data = {'objectId': name}

    # Call tested function
    main.hello_gcs(data, None)
    out, err = capsys.readouterr()
    assert out == 'File: {}.\n'.format(name)

Go (Beta)

package helloworld

import (
	"context"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"testing"
)

func TestHelloGCS(t *testing.T) {
	name := "hello_gcs.txt"
	tests := []struct {
		resourceState  string
		metageneration string
		want           string
	}{
		{
			resourceState: "not_exists",
			want:          fmt.Sprintf("File %s deleted.\n", name),
		},
		{
			metageneration: "1",
			want:           fmt.Sprintf("File %s created.\n", name),
		},
		{
			want: fmt.Sprintf("File %s metadata updated.\n", name),
		},
	}

	for _, test := range tests {
		r, w, _ := os.Pipe()
		log.SetOutput(w)
		originalFlags := log.Flags()
		log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime))

		e := GCSEvent{
			Name:           name,
			ResourceState:  test.resourceState,
			Metageneration: test.metageneration,
		}
		HelloGCS(context.Background(), e)

		w.Close()
		log.SetOutput(os.Stderr)
		log.SetFlags(originalFlags)

		out, err := ioutil.ReadAll(r)
		if err != nil {
			t.Fatalf("ReadAll: %v", err)
		}

		if got := string(out); got != test.want {
			t.Errorf("HelloGCS(%+v) = %q, want %q", e, got, test.want)
		}
	}
}

Usa el siguiente comando para ejecutar las pruebas de unidad:

Node.js

ava test/sample.unit.storage.test.js

Python (Beta)

pytest sample_storage_test.py

Go (Beta)

go test -v ./hello_cloud_storage_test.go

Pruebas de integración

Estas son las pruebas de integración para la función activada por Storage anterior:

Node.js

const childProcess = require(`child_process`);
const test = require(`ava`);
const uuid = require(`uuid`);

test.serial(`helloGCS: should print uploaded message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();
  const filename = uuid.v4(); // Use a unique filename to avoid conflicts

  // Mock GCS call, as the emulator doesn't listen to GCS buckets
  const data = JSON.stringify({
    name: filename,
    resourceState: 'exists',
    metageneration: '1',
  });

  childProcess.execSync(`functions-emulator call helloGCS --data '${data}'`);

  // Check the emulator's logs
  const logs = childProcess
    .execSync(`functions-emulator logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${filename} uploaded.`));
});

test.serial(`helloGCS: should print metadata updated message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();
  const filename = uuid.v4(); // Use a unique filename to avoid conflicts

  // Mock GCS call, as the emulator doesn't listen to GCS buckets
  const data = JSON.stringify({
    name: filename,
    resourceState: 'exists',
    metageneration: '2',
  });

  childProcess.execSync(`functions-emulator call helloGCS --data '${data}'`);

  // Check the emulator's logs
  const logs = childProcess
    .execSync(`functions-emulator logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${filename} metadata updated.`));
});

test.serial(`helloGCS: should print deleted message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();
  const filename = uuid.v4(); // Use a unique filename to avoid conflicts

  // Mock GCS call, as the emulator doesn't listen to GCS buckets
  const data = JSON.stringify({
    name: filename,
    resourceState: 'not_exists',
    metageneration: '3',
  });

  childProcess.execSync(`functions-emulator call helloGCS --data '${data}'`);

  // Check the emulator's logs
  const logs = childProcess
    .execSync(`functions-emulator logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${filename} deleted.`));
});

Usa el siguiente archivo como un corrector de compatibilidad:

Node.js

// Import dependencies
const Pubsub = require('@google-cloud/pubsub');
const {Storage} = require(`@google-cloud/storage`);
const pubsub = Pubsub();
const storage = new Storage();

// TODO(developer): specify a function to test
// const gcfCode = require('./index.js');
// const gcfFn = gcfCode.YOUR_FUNCTION;

// TODO(developer): specify a Cloud Storage bucket to monitor
// const bucketName = 'YOUR_GCS_BUCKET'

// TODO(developer): specify an existing topic and subscription to use
// const topicName = process.env.TOPIC || 'YOUR_TOPIC';
// const subscriptionName = process.env.SUBSCRIPTION || 'YOUR_SUBSCRIPTION';

// Create notification on target bucket
// Further info: https://cloud.google.com/storage/docs/reporting-changes
const bucket = storage.bucket(bucketName);
return bucket
  .createNotification(topicName)
  .then(data => data[0])
  .then(
    notification =>
      new Promise(resolve => {
        // Subscribe to Pub/Sub topic
        const subscription = pubsub
          .topic(topicName)
          .subscription(subscriptionName);

        // Handle a single Pub/Sub message
        const messageHandler = msg => {
          const data = JSON.parse(Buffer.from(msg.data, 'base64').toString());
          gcfFn({data: data}, () => {
            msg.ack();
            subscription.removeListener(`message`, messageHandler);
            resolve(notification);
          });
        };
        subscription.on(`message`, messageHandler);
      })
  )
  .then(notification => notification.delete()); // Delete notification

Usa el siguiente comando para ejecutar las pruebas de integración:

Node.js

  1. De manera opcional, si no has configurado el tema ni la suscripción en tu archivo de corrección de compatibilidad, configura las siguientes variables del entorno:

    export TOPIC=YOUR_TOPIC
    export SUBSCRIPTION=YOUR_SUBSCRIPTION
    

    en las que:

    • YOUR_TOPIC es el nombre del tema de Cloud Pub/Sub al que quieres que se suscriban tus funciones.
    • YOUR_SUBSCRIPTION es tu suscripción a Cloud Pub/Sub.

  2. Para ejecutar la prueba, usa el siguiente comando:

    ava test/sample.integration.storage.test.js
    

Pruebas de sistema

Estas son las pruebas de sistema para la función activada por Storage anterior:

Node.js

const {Storage} = require(`@google-cloud/storage`);
const storage = new Storage();
const uuid = require(`uuid`);
const test = require(`ava`);
const path = require(`path`);
const childProcess = require(`child_process`);
const localFileName = `test.txt`;

// Use unique GCS filename to avoid conflicts between concurrent test runs
const gcsFileName = `test-${uuid.v4()}.txt`;

const bucketName = process.env.FUNCTIONS_BUCKET;
const bucket = storage.bucket(bucketName);
const baseCmd = `gcloud functions`;

test.serial(`helloGCS: should print uploaded message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();

  // Upload file
  const filepath = path.join(__dirname, localFileName);
  await bucket.upload(filepath, {
    destination: gcsFileName,
  });

  // Wait for consistency
  await new Promise(resolve => setTimeout(resolve, 15000));

  // Check logs
  const logs = childProcess
    .execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${gcsFileName} uploaded`));
});

test.serial(`helloGCS: should print metadata updated message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();

  // Update file metadata
  const file = bucket.file(gcsFileName);
  await file.setMetadata(gcsFileName, {foo: `bar`});

  // Wait for consistency
  await new Promise(resolve => setTimeout(resolve, 15000));

  // Check logs
  const logs = childProcess
    .execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${gcsFileName} metadata updated`));
});

test.serial(`helloGCS: should print deleted message`, async t => {
  t.plan(1);
  const startTime = new Date(Date.now()).toISOString();

  // Delete file
  bucket.deleteFiles();

  // Wait for consistency
  await new Promise(resolve => setTimeout(resolve, 15000));

  // Check logs
  const logs = childProcess
    .execSync(`${baseCmd} logs read helloGCS --start-time ${startTime}`)
    .toString();
  t.true(logs.includes(`File ${gcsFileName} deleted`));
});

Python (Beta)

from datetime import datetime
from os import getenv, path
import subprocess
import time
import uuid

from google.cloud import storage
import pytest

PROJECT = getenv('GCP_PROJECT')
BUCKET = getenv('BUCKET')

assert PROJECT is not None
assert BUCKET is not None

@pytest.fixture(scope='module')
def storage_client():
    yield storage.Client()

@pytest.fixture(scope='module')
def bucket_object(storage_client):
    bucket_object = storage_client.get_bucket(BUCKET)
    yield bucket_object

@pytest.fixture(scope='module')
def uploaded_file(bucket_object):
    name = 'test-{}.txt'.format(str(uuid.uuid4()))
    blob = bucket_object.blob(name)

    test_dir = path.dirname(path.abspath(__file__))
    blob.upload_from_filename(path.join(test_dir, 'test.txt'))
    yield name
    blob.delete()

def test_hello_gcs(uploaded_file):
    start_time = datetime.utcnow().isoformat()
    time.sleep(10)  # Wait for logs to become consistent

    log_process = subprocess.Popen([
        'gcloud',
        'alpha',
        'functions',
        'logs',
        'read',
        'hello_gcs',
        '--start-time',
        start_time
    ], stdout=subprocess.PIPE)
    logs = str(log_process.communicate()[0])
    assert uploaded_file in logs

Go (Beta)

package helloworld

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"strings"
	"testing"
	"time"

	"cloud.google.com/go/storage"
)

func TestHelloGCSSystem(t *testing.T) {
	ctx := context.Background()
	bucketName := os.Getenv("BUCKET_NAME")

	client, err := storage.NewClient(ctx)
	if err != nil {
		t.Fatalf("storage.NewClient: %v", err)
	}

	// Create a file.
	startTime := time.Now().UTC().Format(time.RFC3339)
	oh := client.Bucket(bucketName).Object("TestHelloGCSSystem")
	w := oh.NewWriter(ctx)
	fmt.Fprintf(w, "Content of the file")
	w.Close()

	// Wait for logs to be consistent.
	time.Sleep(20 * time.Second)

	// Check logs.
	want := "created"
	if got := readLogs(t, startTime); !strings.Contains(got, want) {
		t.Errorf("HelloGCS logged %q, want to contain %q", got, want)
	}

	// Modify the file.
	startTime = time.Now().UTC().Format(time.RFC3339)
	_, err = oh.Update(ctx, storage.ObjectAttrsToUpdate{
		Metadata: map[string]string{"Content-Type": "text/html"},
	})
	if err != nil {
		t.Errorf("Update: %v", err)
	}

	// Wait for logs to be consistent.
	time.Sleep(20 * time.Second)

	// Check logs.
	want = "updated"
	if got := readLogs(t, startTime); !strings.Contains(got, want) {
		t.Errorf("HelloGCS logged %q, want to contain %q", got, want)
	}

	// Delete the file.
	startTime = time.Now().UTC().Format(time.RFC3339)
	if err := oh.Delete(ctx); err != nil {
		t.Errorf("Delete: %v", err)
	}

	// Wait for logs to be consistent.
	time.Sleep(20 * time.Second)

	// Check logs.
	want = "deleted"
	if got := readLogs(t, startTime); !strings.Contains(got, want) {
		t.Errorf("HelloGCS logged %q, want to contain %q", got, want)
	}
}

func readLogs(t *testing.T, startTime string) string {
	t.Helper()
	cmd := exec.Command("gcloud", "alpha", "functions", "logs", "read", "HelloGCS", "--start-time", startTime)
	got, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("exec.Command: %v", err)
	}
	return string(got)
}

Usa el siguiente comando para implementar tu función:

Node.js 6

gcloud functions deploy helloGCS --runtime nodejs6 --trigger-bucket YOUR_GCS_BUCKET_NAME

Node.js 8 (Beta)

gcloud functions deploy helloGCS --runtime nodejs8 --trigger-bucket YOUR_GCS_BUCKET_NAME

Python (Beta)

gcloud functions deploy hello_gcs --runtime python37 --trigger-bucket YOUR_GCS_BUCKET_NAME

Go (Beta)

gcloud functions deploy HelloGCS --runtime go111 --trigger-bucket YOUR_GCS_BUCKET_NAME

en el que YOUR_GCS_BUCKET_NAME es el depósito de Cloud Storage que quieres supervisar. Ten en cuenta que esto debe hacer referencia a un depósito que existe en el mismo proyecto de GCP en el que se implementa la función.

Usa los siguientes comandos para ejecutar las pruebas de sistema:

Node.js

export BUCKET_NAME=YOUR_GCS_BUCKET_NAME
ava test/sample.system.storage.test.js

Python (Beta)

export BUCKET_NAME=YOUR_GCS_BUCKET_NAME
pytest sample_storage_test_system.py

Go (Beta)

export BUCKET_NAME=YOUR_GCS_BUCKET_NAME
go test -v ./hello_cloud_storage_system_test.go

Implementación y pruebas continuas

A medida que desarrollas tu función, puedes ejecutar pruebas de unidad, de integración y de sistema para asegurarte de que las funciones sirven a nivel local y en un entorno de pruebas en GCP.

Una vez que termines de desarrollar funciones localmente, puedes configurar una plataforma de integración e implementación continuas como Cloud Build para ejecutar las pruebas de Cloud Functions existentes de manera constante. Las pruebas continuas ayudan a asegurar que tu código sigue funcionando como se prevé y que tus dependencias permanecen actualizadas. Como las funciones de Cloud Functions no se actualizan automáticamente, también puedes configurar plataformas de integración continua (incluida Cloud Build) para probar y volver a implementar de forma automática tus funciones desde un repositorio de código fuente como GitHub, Bitbucket o Cloud Source Repositories.

Sigue las instrucciones en la guía Automatiza compilaciones con activadores de compilación mediante el archivo de compilación cloudbuild.yaml que aparece a continuación para configurar Cloud Build a fin de probar y, luego, implementar tu función automáticamente. Reemplaza [YOUR_FUNCTION_NAME] por el nombre de tus funciones de Cloud Functions y [YOUR_FUNCTION_TRIGGER] por el valor de activador apropiado.

Node.js

steps:
- name: 'gcr.io/cloud-builders/yarn'
  args: ['install']
  dir: 'functions/autodeploy'
- name: 'gcr.io/cloud-builders/npm'
  args: ['test']
  dir: 'functions/autodeploy'
- name: 'gcr.io/cloud-builders/gcloud'
  args: ['functions', 'deploy', '[YOUR_DEPLOYED_FUNCTION_NAME]', '[YOUR_FUNCTION_TRIGGER]', '--runtime', '[YOUR_RUNTIME]', '--entry-point', '[YOUR_FUNCTION_NAME_IN_CODE]']
  dir: 'functions/autodeploy'

Si Cloud Build no se ajusta a tus necesidades, consulta esta lista de plataformas de integración continua.

Otorga permisos para ejecutar implementaciones y compilaciones

Si usas Cloud Build, es necesario que otorgues permisos a la cuenta de servicio de Cloud Build.

Por ejemplo:

  • Para implementar Cloud Functions, te recomendamos que asignes la función de programador de Cloud Functions a la cuenta de servicio de Cloud Build (PROJECT_NUMBER@cloudbuild.gserviceaccount.com).
  • Si usas la función de programador de Cloud Functions, es necesario que también otorgues a la cuenta de servicio del entorno de ejecución de Cloud Functions (PROJECT_ID@appspot.gserviceaccount.com) el rol de usuario de cuenta de servicio de IAM.
¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Functions