#unit-testing #go #testing #reverse-proxy
#модульное тестирование #Вперед #тестирование #обратный прокси
Вопрос:
Я пытаюсь модульно протестировать следующий код:
func(h *Handler)Forward(w http.ResponseWriter, r *http.Request) {
url, err := url.Parse("http://test.com")
if err != nil {
return
}
reverseProxy := amp;httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Host = url.Host
r.URL.Path = "/"
r.URL.Scheme = url.Scheme
r.Host = url.Host
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
},
}
reverseProxy.ServeHTTP(w, r)
}
Я не могу понять, как проверить, изменяются ли заголовки функцией Director. Как мы тестируем заголовки в reverseproxy в Go?
Комментарии:
1. Очевидным способом было бы отправить запрос на ваш прокси и посмотреть, получите ли вы нужные заголовки. Что вы пробовали? С какими проблемами вы столкнулись?
2. Если целью является модульное тестирование обратного прокси, вы могли бы запустить тестовый сервер с помощью
httptest
package, установить его в качестве места назначения для прокси, а затем, используя обычный http-клиент, отправлять запросы на обратный прокси и сравнивать заголовки от клиента с теми, что у вас есть на сервере.
Ответ №1:
1. Внедрите внешние зависимости в тестируемый модуль
Самая большая проблема, которую я вижу прямо сейчас, заключается в том, что URL, на который вы пересылаете, жестко запрограммирован в вашей функции. Это очень затрудняет модульное тестирование. Итак, первым шагом было бы извлечь URL из функции. Не зная остальной части вашего кода, Handler
кажется, что это подходящее место для этого. Упрощенный:
type Handler struct {
backend *url.URL
}
func NewHandler() (*Handler, error) {
backend, err := url.Parse("http://test.com")
if err != nil {
return nil, err
}
return amp;Handler{backend}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reverseProxy := amp;httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Host = h.backend.Host
r.URL.Path = "/"
r.URL.Scheme = h.backend.Scheme
r.Host = url.Host
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
},
}
reverseProxy.ServeHTTP(w, r)
}
Обратите внимание, что я переименовал Forward
в ServeHTTP
, чтобы упростить этот пример.
2. Используйте httptest
для тестирования обработчика в реальном времени
Следующий шаг — провести базовый тест:
func TestHandler(t *testing.T) {
// 1. set-up a backend server
// 2. set-up a reverse proxy with the handler we are testing
// 3. call the reverse-proxy
// 4. check that the backend server received the correct header
}
Давайте начнем с заполнения простых частей:
// set-up a backend server
backendServer := httptest.NewServer(http.DefaultServeMux)
defer backendServer.Close()
backendURL, err := url.Parse(backendServer.URL)
if err != nil {
t.Fatal(err)
}
// set-up the reverse proxy
handler := amp;Handler{backend: backendURL} // <-- here we inject our own endpoint!
reverseProxy := httptest.NewServer(handler)
defer reverseProxy.Close()
reverseProxyURL, err := url.Parse(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// call the reverse proxy
res, err := http.Get(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// todo optional: assert properties of the response
_ = res
// check that the backend server received the correct header
// this comes next...
3. Передайте результаты с тестового сервера на тестовый
Теперь нам нужен способ передачи полученного заголовка в основной тест. Поскольку наши тестовые серверы могут использовать произвольные обработчики, давайте расширим настройки нашего внутреннего сервера.
var (
mu sync.Mutex
header string
)
// create a backend server that checks the incoming headers
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
header = r.Header.Get("X-Forwarded-Host")
w.WriteHeader(http.StatusOK)
}))
defer backendServer.Close()
Обратите внимание, как я использую мьютекс, потому что обработчик будет выполняться в другой процедуре перехода. Вы также могли бы использовать канал.
На этом этапе мы можем реализовать наше утверждение:
mu.Lock()
got := header
mu.Unlock()
// check that the header has been set
want := reverseProxyURL.Host
if got != want {
t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
}
Обратите внимание, что это все равно приведет к сбою, но на этот раз из-за неправильного тестируемого кода 🙂 r.Header.Get("Host")
следует заменить на r.Host
.
Приложение: полный пример
package example
import (
"net/http"
"net/http/httptest"
"net/http/httputil"
"net/url"
"sync"
"testing"
)
type Handler struct {
backend *url.URL
}
func NewHandler() (*Handler, error) {
backend, err := url.Parse("http://test.com")
if err != nil {
return nil, err
}
return amp;Handler{backend}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reverseProxy := amp;httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Host = h.backend.Host
r.URL.Path = "/"
r.URL.Scheme = h.backend.Scheme
r.Header.Set("X-Forwarded-Host", r.Host)
r.Host = h.backend.Host
},
}
reverseProxy.ServeHTTP(w, r)
}
func TestHandler(t *testing.T) {
var (
mu sync.Mutex
header string
)
// create a backend server that checks the incoming headers
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
mu.Lock()
defer mu.Unlock()
header = r.Header.Get("X-Forwarded-Host")
w.WriteHeader(http.StatusOK)
}))
defer backendServer.Close()
backendURL, err := url.Parse(backendServer.URL)
if err != nil {
t.Fatal(err)
}
// create a server for your reverse proxy
handler := amp;Handler{backend: backendURL}
reverseProxy := httptest.NewServer(handler)
defer reverseProxy.Close()
reverseProxyURL, err := url.Parse(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// make a request to the reverse proxy
res, err := http.Get(reverseProxy.URL)
if err != nil {
t.Fatal(err)
}
// todo optional: assert properties of the response
_ = res
mu.Lock()
got := header
mu.Unlock()
// check that the header has been set
want := reverseProxyURL.Host
if got != want {
t.Errorf("GET %s gives header %s, got %s", reverseProxy.URL, want, got)
}
}