Как тестируют в Google - Страница 16

Изменить размер шрифта:

Пример работы разработчика в тестировании

Следующий пример объединяет все, о чем мы говорили выше. Предупреждаем, в этом разделе много технической информации с уймой низкоуровневых деталей. Если вам интересна только общая картина, смело переходите к следующему разделу.

Представьте простое веб-приложение, с помощью которого пользователи отправляют URL-адреса в Google для добавления в Google-индекс. Форма HTML содержит два поля – ULR-адрес и комментарий – и генерирует запрос HTTP GET к серверу Google в следующем формате:

GET /addurl?url=http://www.foo.com&comment=Foo+comment HTTP/1.1

На стороне сервера это веб-приложение делится на две части: AddUrlFrontend, который получает запрос HTTP, распознает и проверяет его, и бэкенд AddUrlService. Сервис бэкенда получает запросы от AddUrlFrontend, проверяет, нет ли в них ошибок, и дальше взаимодействует с такими хранилищами данных, как, например, Google Bigtable[22] или Google File System[23].

Разработчик начинает работу с создания каталога для проекта:

$ mkdir depot/addurl/

Затем он определяет протокол AddUrlService с использованием языка Protocol Buffers[24]:

File: depot/addurl/addurl.proto

message AddUrlRequest {

 required string url = 1; // The URL address entered by user.

 optional string comment = 2; // Comments made by user.

}

message AddUrlReply {

 // Error code if an error occured.

 optional int32 error_code = 1;

 // Error mtssage if an error occured.

optional string error_details = 2;

}

service AddUrlService {

 // Accepts a URL for submission to the index.

 rpc AddUrl(AddUrlRequest) returns (AddUrlReply) {

option deadline = 10.0;

 }

}

В файле addurl.proto определены три важных элемента: сообщения AddUrlRequest и AddUrlReply и сервис удаленного вызова процедур (RPC, Remote Procedure) AddUrlService.

Посмотрев на определения сообщения AddUrlRequest, мы видим, что поле url должно быть задано вызывающей стороной, а поле comment не является обязательным.

Точно так же из определения сообщения AddUrlReply следует, что оба поля – error_code и error_details опционально могут быть переданы в ответах сервиса. Мы предполагаем, что в типичном случае, когда URL-адрес успешно принят, эти поля останутся пустыми, чтобы минимизировать объем передаваемых данных. Это одно из правил Google: типичный случай должен работать быстро.

Из определения AddUrlService видно, что сервис содержит единственный метод AddUrl, который принимает AddUrlRequest и возвращает AddUrlReply. По умолчанию вызов метода AddUrl прерывается по тайм-ауту через 10 секунд, если клиент не получил ответа за это время. Реализации интерфейса AddUrlService могут включать в себя сколько угодно систем хранения данных, но для клиентов интерфейса это несущественно, поэтому эти подробности не отражены в файле addurl.proto.

Обозначение '= 1' в полях сообщений не имеет никакого отношения к значениям этих полей. Оно существует для того, чтобы протокол можно было дорабатывать. Например, кто-то захочет добавить поле uri в сообщение AddUrlRequest к уже имеющимся полям. Для этого вносится следующее изменение:

message AddUrlRequest {

 required string url = 1; // The URL entered by the user.

 optional string comment = 2; // Comments made by the user.

 optional string uri = 3; // The URI entered by the user.

}

Но это выглядит довольно глупо – скорее всего, потребуется просто переименовать поле url в uri. Если это число и тип останутся неизменными, сохранится совместимость между старой и новой версией:

message AddUrlRequest {

 required string uri = 1; // The URI entered by user.

 optional string comment = 2; // Comments made by the user.

}

Написав файл addurl.proto, разработчик переходит к созданию правила сборки proto_library, которое генерирует исходные файлы C++, определяющие сущности из addurl.proto, и компилирует их в статическую библиотеку addurl C++. С дополнительными параметрами можно сгенерировать исходный код для языков Java и Python.

File: depot/addurl/BUILD

proto_library(name="addurl",

srcs=["addurl.proto"])

Разработчик запускает систему сборки и исправляет все проблемы, обнаруженные ею в addurl.proto и в файле BUILD. Система сборки вызывает компилятор Protocol Buffers, генерирует исходные файлы addurl.pb.h и addurl.pb.cc и статическую библиотеку addurl, которую теперь можно подключить.

Пора писать AddUrlFrontend. Для этого мы объявляем класс AddUrlFrontend в новом файле addurl_frontend.h. Этот код в основном шаблонный.

File: depot/addurl/addurl_frontend.h

#ifndef ADDURL_ADDURL_FRONTEND_H_

#define ADDURL_ADDURL_FRONTEND_H_

// Forward-declaration of dependencies.

class AddUrlService;

class HTTPRequest;

class HTTPReply;

// Frontend for  the AddUrl system.

// Accepts HTTP requests from web clients,

// and forwards well-formed requests to the backend.

class AddUrlFrontend {

 public:

// Constructor which enables injection of an

// AddUrlService dependency.

explicit AddUrlFrontend(AddUrlService* add_url_service);

~AddUrlFrontend();

// Method invoked by our HTTP server when a request arrives

// for  the /addurl resource.

void HandleAddUrlFrontendRequest(const HTTPRequest* http_request,

HTTPReply* http_reply);

 private:

AddUrlService* add_url_service_;

// Declare copy constructor and operator= private  to prohibit

// unintentional copying of instances of this class.

AddUrlFrontend(const AddUrlFrontend&);

AddUrlFrontend& operator=(const AddUrlFrontend& rhs);

 };

#endif // ADDURL_ADDURL_FRONTEND_H_

Продолжая определять классы AddUrlFrontend, разработчик создает файл addurl_frontend.cc, в котором описывает класс AddUrlFrontend. Для экономии места мы опустили часть файла.

File: depot/addurl/addurl_frontend.cc

#include "addurl/addurl_frontend.h"

#include "addurl/addurl.pb.h"

#include "path/to/httpqueryparams.h"

// Functions used by HandleAddUrlFrontendRequest() below, but

// whose definitions are omitted for brevity.

void ExtractHttpQueryParams(const HTTPRequest* http_request,

HTTPQueryParams* query_params);

void WriteHttp200Reply(HTTPReply* reply);

void WriteHttpReplyWithErrorDetails(

HTTPReply* http_reply, const AddUrlReply& add_url_reply);

// AddUrlFrontend constructor that injects the AddUrlService

// dependency.

AddUrlFrontend::AddUrlFrontend(AddUrlService* add_url_service)

: add_url_service_(add_url_service) {

}

// AddUrlFrontend destructor – there's nothing to do here.

AddUrlFrontend::~AddUrlFrontend() {

}

// HandleAddUrlFrontendRequest:

// Handles requests to /addurl by parsing the request,

// dispatching a backend request to an AddUrlService backend,

// and transforming the backend reply into an appropriate

// HTTP reply.

//

// Args:

// http_request – The raw HTTP request received by the server.

// http_reply – The raw HTTP reply to send in response.

void AddUrlFrontend::HandleAddUrlFrontendRequest(

const HTTPRequest* http_request, HTTPReply* http_reply) {

 // Extract the query parameters from the raw HTTP request.

 HTTPQueryParams query_params;

 ExtractHttpQueryParams(http_request, &query_params);

 // Get the 'url' and 'comment' query components.

 // Default each to an empty string if they were not present

 // in http_request.

 string url =

query_params.GetQueryComponentDefault("url", "");

string comment =

query_params.GetQueryComponentDefault("comment", "");

// Prepare the request to the AddUrlService backend.

AddUrlRequest add_url_request;

AddUrlReply add_url_reply;

add_url_request.set_url(url);

if (!comment.empty()) {

 add_url_request.set_comment(comment);

}

// Issue the request to the AddUrlService backend.

RPC rpc;

add_url_service_->AddUrl(

&rpc, &add_url_request, &add_url_reply);

// Block until the reply is received from the

// AddUrlService backend.

rpc.Wait();

// Handle errors, if any:

if (add_url_reply.has_error_code()) {

 WriteHttpReplyWithErrorDetails(http_reply, add_url_reply);

} else {

 // No errors. Send HTTP 200 OK response to client.

 WriteHttp200Reply(http_reply);

}

}

Оригинальный текст книги читать онлайн бесплатно в онлайн-библиотеке Knigger.com