Pruebas de unidades de Cloud Storage

Demuestra cómo realizar la prueba de unidades de una función activada por Cloud Storage.

Páginas de documentación que incluyen esta muestra de código

Para ver la muestra de código usada en contexto, consulta la siguiente documentación:

Muestra de código

C#

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Testing;
using Google.Events;
using Google.Events.Protobuf.Cloud.Storage.V1;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace HelloWorld.Tests
{
    public class HelloGcsUnitTest
    {
        [Fact]
        public async Task FileNameIsLogged()
        {
            // Prepare the inputs
            var cloudEvent = new CloudEvent(StorageObjectData.FinalizedCloudEventType, new Uri("//storage.googleapis.com"));
            var data = new StorageObjectData { Name = "new-file.txt" };
            CloudEventConverters.PopulateCloudEvent(cloudEvent, data);
            var logger = new MemoryLogger<HelloGcs.Function>();

            // Execute the function
            var function = new HelloGcs.Function(logger);
            await function.HandleAsync(cloudEvent, data, CancellationToken.None);

            // Check the log results - just the entry starting with "File:".
            var logEntry = Assert.Single(logger.ListLogEntries(), entry => entry.Message.StartsWith("File:"));
            Assert.Equal("File: new-file.txt", logEntry.Message);
            Assert.Equal(LogLevel.Information, logEntry.Level);
        }
    }
}

C++

#include <google/cloud/functions/cloud_event.h>
#include <boost/log/core.hpp>
#include <boost/log/sinks/sync_frontend.hpp>
#include <boost/log/sinks/text_ostream_backend.hpp>
#include <boost/shared_ptr.hpp>
#include <gmock/gmock.h>
#include <nlohmann/json.hpp>
#include <memory>
#include <sstream>

namespace gcf = ::google::cloud::functions;
extern void hello_world_storage(gcf::CloudEvent event);

namespace {

using ::testing::HasSubstr;

TEST(StorageUnitTest, Basic) {
  auto core = boost::log::core::get();
  auto stream = boost::make_shared<std::ostringstream>();
  auto be = [core, stream]() {
    auto backend =
        boost::make_shared<boost::log::sinks::text_ostream_backend>();
    backend->add_stream(stream);

    // Enable auto-flushing after each log record written
    backend->auto_flush(true);
    using sink_t = boost::log::sinks::synchronous_sink<
        boost::log::sinks::text_ostream_backend>;
    auto be = boost::make_shared<sink_t>(backend);
    core->add_sink(be);
    return be;
  }();

  struct TestCases {
    std::string name;
    std::string expected;
  } cases[]{
      {"object1.txt", "Object: object1.txt"},
      {"object/with/longer/name.txt", "Object: object/with/longer/name.txt"},
  };

  auto const base = nlohmann::json::parse(R"js({
      "bucket": "some-bucket",
      "name": "--set-later--",
      "generation": "1587627537231057",
      "contentType": "text/plain",
      "timeCreated": "2020-04-23T07:38:57.230Z",
      "updated": "2020-04-23T07:38:57.230Z"
    })js");

  for (auto const& test : cases) {
    SCOPED_TRACE("Testing for " + test.expected);
    gcf::CloudEvent event(
        /*id=*/"test-id-0001", /*source=*/"https://test-source.example.com",
        /*type=*/"google.cloud.pubsub.topic.v1.messagePublished");
    event.set_data_content_type("application/json");
    auto data = base;
    data["name"] = test.name;
    event.set_data(data.dump());
    stream->str({});
    EXPECT_NO_THROW(hello_world_storage(event));
    auto log_lines = stream->str();
    EXPECT_THAT(log_lines, HasSubstr(test.expected));
  }

  core->remove_sink(be);
}

}  // namespace

Go

package helloworld

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

	"cloud.google.com/go/functions/metadata"
)

func TestHelloGCS(t *testing.T) {
	r, w, _ := os.Pipe()
	log.SetOutput(w)
	originalFlags := log.Flags()
	log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime))

	name := "hello_gcs.txt"
	e := GCSEvent{
		Name: name,
	}
	meta := &metadata.Metadata{
		EventID: "event ID",
	}
	ctx := metadata.NewContext(context.Background(), meta)

	HelloGCS(ctx, e)

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

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

	got := string(out)
	wants := []string{
		"File: " + name,
		"Event ID: " + meta.EventID,
	}
	for _, want := range wants {
		if !strings.Contains(got, want) {
			t.Errorf("HelloGCS(%v) = %q, want to contain %q", e, got, want)
		}
	}
}

Java

import static com.google.common.truth.Truth.assertThat;

import com.google.common.testing.TestLogHandler;
import functions.eventpojos.GcsEvent;
import functions.eventpojos.MockContext;
import java.util.logging.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Unit tests for main.java.com.example.functions.helloworld.HelloGcs.
 */
public class HelloGcsTest {
  private static final TestLogHandler LOG_HANDLER = new TestLogHandler();
  private static final Logger logger = Logger.getLogger(HelloGcs.class.getName());

  @Before
  public void beforeTest() throws Exception {
    logger.addHandler(LOG_HANDLER);
  }

  @After
  public void afterTest() {
    LOG_HANDLER.clear();
  }

  @Test
  public void helloGcs_shouldPrintFileName() {
    GcsEvent event = new GcsEvent();
    event.setName("foo.txt");

    MockContext context = new MockContext();
    context.eventType = "google.storage.object.finalize";

    new HelloGcs().accept(event, context);

    String message = LOG_HANDLER.getStoredLogRecords().get(3).getMessage();
    assertThat(message).contains("File: foo.txt");
  }
}

Node.js

const assert = require('assert');
const uuid = require('uuid');
const sinon = require('sinon');

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

const stubConsole = function () {
  sinon.stub(console, 'error');
  sinon.stub(console, 'log');
};

const restoreConsole = function () {
  console.log.restore();
  console.error.restore();
};

beforeEach(stubConsole);
afterEach(restoreConsole);

it('helloGCS: should print out event', () => {
  // Initialize mocks
  const filename = uuid.v4();
  const eventType = 'google.storage.object.finalize';
  const event = {
    name: filename,
    resourceState: 'exists',
    metageneration: '1',
  };
  const context = {
    eventId: 'g1bb3r1sh',
    eventType: eventType,
  };

  // Call tested function and verify its behavior
  helloGCS(event, context);
  assert.ok(console.log.calledWith(`  File: ${filename}`));
  assert.ok(console.log.calledWith(`  Event Type: ${eventType}`));
});

PHP


namespace Google\Cloud\Samples\Functions\HelloworldStorage\Test;

use Google\CloudFunctions\CloudEvent;
use PHPUnit\Framework\TestCase;

/**
 * Class SampleUnitTest.
 *
 * Unit test for 'Helloworld Storage' Cloud Function.
 */
class SampleUnitTest extends TestCase
{
    /**
     * Include the Cloud Function code before running any tests.
     *
     * @see https://phpunit.readthedocs.io/en/latest/fixtures.html
     */
    public static function setUpBeforeClass(): void
    {
        require_once __DIR__ . '/index.php';
    }

    public function dataProvider()
    {
        return [
            [
                'cloudevent' => CloudEvent::fromArray([
                    'id' => uniqid(),
                    'source' => 'storage.googleapis.com',
                    'specversion' => '1.0',
                    'type' => 'google.cloud.storage.object.v1.finalized',
                    'data' => [
                        'bucket' => 'some-bucket',
                        'metageneration' => '1',
                        'name' => 'folder/friendly.txt',
                        'timeCreated' => '2020-04-23T07:38:57.230Z',
                        'updated' => '2020-04-23T07:38:57.230Z',
                    ],
                ]),
                'statusCode' => '200',
            ],
        ];
    }

    /**
     * @dataProvider dataProvider
     */
    public function testFunction(CloudEvent $cloudevent, string $statusCode): void
    {
        // Capture function output by overriding the function's logging behavior.
        // The 'LOGGER_OUTPUT' environment variable must be used in your function:
        //
        // $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb');
        // fwrite($log, 'Log Entry');
        putenv('LOGGER_OUTPUT=php://output');
        helloGCS($cloudevent);
        // Provided by PHPUnit\Framework\TestCase.
        $actual = $this->getActualOutput();

        // Test output includes the properties provided in the CloudEvent.
        foreach ($cloudevent->getData() as $property => $value) {
            $this->assertStringContainsString($value, $actual);
        }
        $this->assertStringContainsString($cloudevent->getId(), $actual);
        $this->assertStringContainsString($cloudevent->getType(), $actual);
    }
}

Python

import mock

import main

def test_print(capsys):
    name = 'test'
    event = {
        'bucket': 'some-bucket',
        'name': name,
        'metageneration': 'some-metageneration',
        'timeCreated': '0',
        'updated': '0'
    }

    context = mock.MagicMock()
    context.event_id = 'some-id'
    context.event_type = 'gcs-event'

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

Ruby

require "minitest/autorun"
require "functions_framework/testing"
require "date"

describe "functions_helloworld_storage" do
  include FunctionsFramework::Testing

  let(:source) { "//storage.googleapis.com/projects/sample-project/buckets/sample-bucket/objects/ruby-rocks.rb" }
  let(:type) { "google.cloud.storage.object.v1.finalized" }

  it "responds to generic event" do
    load_temporary "helloworld/storage/app.rb" do
      timestamp = DateTime.new(2020, 2, 3, 4, 5, 6).rfc3339
      payload = {
        "bucket"         => "sample-bucket",
        "name"           => "ruby-rocks.rb",
        "metageneration" => "1",
        "timeCreated"    => timestamp,
        "updated"        => timestamp
      }
      event = make_cloud_event payload, source: source, type: type
      _out, err = capture_subprocess_io do
        call_event "hello_gcs", event
      end

      assert_match(/Event: /, err)
      assert_match(/Event Type: google.cloud.storage.object.v1.finalized/, err)
      assert_match(/Bucket: sample-bucket/, err)
      assert_match(/File: ruby-rocks.rb/, err)
      assert_match(/Metageneration: 1/, err)
      assert_match(/Created: 2020-02-03T04:05:06\+00:00/, err)
      assert_match(/Updated: 2020-02-03T04:05:06\+00:00/, err)
    end
  end
end

¿Qué sigue?

Para buscar y filtrar muestras de código para otros productos de Google Cloud, consulta el navegador de muestra de Google Cloud.