آزمایش API با شبیه سازی جلد 2

در قسمت 1 ، ما Hoverfly را به عنوان ابزاری برای شبیه سازی API معرفی کردیم و به بررسی راه اندازی اصلی آن پرداختیم. ما دیدیم که چگونه می تواند تعامل API را ضبط کند و آنها را در طول آزمایش ها پخش کند و مجموعه تست ما را قابل اطمینان تر کند.
با این حال ، برنامه های دنیای واقعی برخی را ارائه می دهند چالش های دنیای واقعی که فراتر از آن نمونه های اساسی است. بیایید صادق باشیم – اگر سناریوهای ساده از قسمت اول برای نیازهای شما کافی بودند ، احتمالاً به این سریال احتیاج ندارید.
این بخش به بررسی راه حل های چالش های مشترک می پردازد. ما روی سناریوهای خاصی که اغلب در برنامه ها ظاهر می شوند تمرکز خواهیم کرد و معتقدم که می توانید این نمونه ها را برای نیازهای خود به مراتب فراتر از آنچه در اینجا پوشش خواهیم داد ، تطبیق دهید.
One One: داده های پویا
بیایید به یک سناریوی تست نگاه کنیم:
{
name: "create activity",
testFunc: func(t *testing.T, client *ClientWithResponses) {
var (
ctx = context.Background()
id = int32(1)
title = randomTitleForResource("activity")
dueDate = time.Now()
)
resp, err := client.PostApiV1ActivitiesWithApplicationJSONV10BodyWithResponse(
ctx,
PostApiV1ActivitiesApplicationJSONV10RequestBody{
Id: tests.ToPtr(id),
Title: tests.ToPtr(title),
DueDate: tests.ToPtr(dueDate),
Completed: tests.ToPtr(false),
},
)
require.NoError(t, err)
require.NotNil(t, resp, "expected resp, got nil")
require.NotNil(t, resp.ApplicationjsonV10200, "expected resp body, got nil")
assert.Equal(t, http.StatusOK, resp.StatusCode())
assert.Equal(t, id, *resp.ApplicationjsonV10200.Id)
assert.Equal(t, title, *resp.ApplicationjsonV10200.Title)
assert.Equal(
t,
dueDate.UTC().Format(time.RFC3339),
resp.ApplicationjsonV10200.DueDate.UTC().Format(time.RFC3339),
)
assert.Equal(t, false, *resp.ApplicationjsonV10200.Completed)
},
}
با یک عملکرد یاور برای تولید عناوین تصادفی:
// Generate a random title to test the possibility of random data in Hoverfly
func randomTitleForResource(resource string) string {
return fmt.Sprintf("%s-%s-title-%s", fakePrefix, resource, tests.RandomString(5))
}
چند نکته که ممکن است توجه ما را به خود جلب کند:
- تولید داده تصادفی: این آزمون یک عنوان تصادفی منحصر به فرد برای هر آزمون ایجاد می کند
-
جدول زمانی فعلی: آزمون از زمان فعلی به عنوان
dueDate
این سناریوها در آزمایش کاملاً متداول هستند.
اما وقتی این تست ها را با شبیه سازی اجرا می کنیم ، آنها شکست خوردنبشر چرا؟ از آنجا که شبیه سازی ضبط شده ما حاوی مقادیر خاصی است ، اما تست ما هر بار موارد جدیدی تولید می کند. این شبیه سازی نمی تواند با درخواست های جدید برای پاسخ های ضبط شده مطابقت داشته باشد.
به همین ترتیب ، به این آزمون نگاه کنید uuids:
{
name: "create activity with UUID in title",
testFunc: func(t *testing.T, client *ClientWithResponses) {
ctx := context.Background()
// Create multiple activities with different UUIDs
for i := 0; i < 3; i++ {
id := int32(1 + i)
title := uuid.New().String()
resp, err := client.PostApiV1ActivitiesWithApplicationJSONV10BodyWithResponse(
ctx,
PostApiV1ActivitiesApplicationJSONV10RequestBody{
Id: tests.ToPtr(id),
Title: tests.ToPtr(title),
DueDate: tests.ToPtr(time.Now()),
Completed: tests.ToPtr(false),
},
)
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, resp.ApplicationjsonV10200)
assert.Equal(t, http.StatusOK, resp.StatusCode())
assert.NotNil(t, resp.ApplicationjsonV10200)
assert.Equal(t, title, *resp.ApplicationjsonV10200.Title)
}
},
}
هر تکرار جدید تولید می کند uuid، ایجاد یک درخواست منحصر به فرد که با شبیه سازی ضبط شده ما مطابقت نخواهد داشت.
درک شبیه سازی های hoverfly
بیایید داخل پرونده شبیه سازی نگاهی بیندازیم. Hoverfly با ذخیره جفت درخواست ها و پاسخ ها کار می کند. هنگامی که یک درخواست دریافتی دریافت می کند ، آن را در برابر درخواست های ذخیره شده برای یافتن مسابقه مقایسه می کند ، سپس پاسخ مربوطه را برمی گرداند. در اینجا به نظر می رسد که یک جفت پاسخ درخواست ضبط شده:
{
"request": {
"path": [
{
"matcher": "exact",
"value": "/api/v1/Activities/1"
}
],
"method": [
{
"matcher": "exact",
"value": "GET"
}
],
"destination": [
{
"matcher": "exact",
"value": "fakerestapi.azurewebsites.net"
}
],
"scheme": [
{
"matcher": "exact",
"value": "https"
}
],
"body": [
{
"matcher": "exact",
"value": ""
}
]
},
"response": {
"status": 200,
"body": "{\"id\":1,\"title\":\"Activity 1\",\"dueDate\":\"2025-03-04T23:24:16.4781493+00:00\",\"completed\":false}",
"encodedBody": false,
"headers": {
"Api-Supported-Versions": [
"1.0"
],
"Content-Type": [
"application/json; charset=utf-8; v=1.0"
],
"Date": [
"Tue, 04 Mar 2025 22:24:15 GMT"
],
"Hoverfly": [
"Was-Here"
],
"Server": [
"Kestrel"
]
},
"templated": false
}
}
درخواست تطبیق
هنگامی که Hoverfly یک درخواست را ضبط می کند ، ایجاد می کند درخواست کننده برای هر قسمت در درخواست یک مسابقه درخواست شامل موارد زیر است:
- نام فیلد درخواست (مسیر ، روش ، مقصد و غیره)
- نوع مسابقه ای که برای مقایسه استفاده می شود
- مقدار قسمت درخواست
به طور پیش فرض ، Hoverfly نوع مسابقه را به exact
برای هر قسمت ، این بدان معنی است که درخواست ورودی دقیقاً باید با درخواست ذخیره شده برای هر قسمت مطابقت داشته باشد. به همین دلیل داده های تصادفی در تست های ما شبیه سازی را می شکند – مقادیر تصادفی جدید دقیقاً با موارد ضبط شده مطابقت نخواهد داشت.
Hoverfly از انواع مختلفی از جمله پشتیبانی می کند exact
با glob
با regex
، و بیشتر می توانید جزئیات را در مستندات رسمی Hoverfly پیدا کنید.
الگوهای پاسخ
در templated
زمینه در پاسخ تعیین می کند که آیا Hoverfly باید از قالب بندی استفاده کند. هنگامی که فعال شوید ، می توانید داده ها را از درخواست به پاسخ تزریق کنید با استفاده از نحو قالب بندی.
به عنوان مثال ، این پاسخ قالب بندی شده شامل مسیر URL از درخواست اصلی است:
"response": {
"status": 200,
"body": "You requested: {{ Request.Path.[0].Value }}",
"encodedBody": false,
"headers": {
"Content-Type": ["text/plain"]
},
"templated": true
}
شایان ذکر است که قالب بندی هورفلی به مراتب قدرتمندتر از تزریق ارزش ساده است. شما می توانید:
- از منطق مشروط استفاده کنید
{{if}}
با{{else}}
- استخراج داده ها با استفاده از
JSONPath
یاXPath
بیان - دستکاری های رشته ای را انجام دهید (
substring
باlowercase
، و غیره) - تولید داده های تصادفی و غیره
هنگامی که Hoverfly یک درخواست دریافت می کند ، مقادیر را از بدن درخواست استخراج می کند و آنها را به پاسخ تزریق می کند و حتی با داده های تصادفی یک تجربه مداوم ایجاد می کند.
این دقیقاً همان چیزی است که ما برای حل مشکلات خود نیاز داریم. با ترکیب متناسب با پاسخ با پاسخ های قالب بندی شده ، می توانیم شبیه سازی هایی را ایجاد کنیم که با هر مقادیر تصادفی کار می کنند ، تست های ما تولید می کنند.
برای مشکل داده پویا ما ، ما از این ویژگی ها استفاده خواهیم کرد:
- انواع تطبیق را تغییر دهید برای زمینه هایی با داده های پویا
- الگوی پاسخ را فعال کنید برای تزریق مقادیر درخواست به پاسخ
- این تغییرات را اعمال کنید به طور خودکار با پردازنده شبیه سازی ما
ایجاد یک پردازنده شبیه سازی
اکنون که ما می دانیم که چگونه Hoverfly درخواست ها و پاسخ ها را پردازش می کند ، بیایید راه حلی برای مشکلات خود بسازیم. در کد من ، از آنجا که منبع باز است ، از پیاده روی های داخلی از بسته Hoverfly استفاده می کنم. با این حال ، به یاد داشته باشید که این فقط یک فایل JSON است ، بنابراین اگر از هر زبانی غیر از رفتن استفاده می کنید ، می توانید به راحتی همان رویکرد را پیاده سازی کنید.
هدف
پردازنده ما باید به چندین هدف برسد:
- الگوهای داده پویا را شناسایی کنید در هر دو درخواست و پاسخ
- مسابقات دقیق را با تطبیق جایگزین کنید برای زمینه های حاوی UUID ، Timestamps ، رشته های تصادفی پیشوند و غیره.
- قالب بندی را فعال کنید در پاسخ به استفاده از مقادیر درخواست
- از پاسخ های استاتیک پشتیبانی کنید برای نقاط پایانی خاص که به رفتار از پیش تعیین شده نیاز دارند
ما از یک فایل پیکربندی اعلانی برای تعریف قوانین پردازش خود استفاده خواهیم کرد ، به عنوان مثال:
version: "1.0"
settings:
debug: false
patterns:
# All UUIDs will be replaced with the default regex pattern
- type: "uuid"
# Dates will be replaced
- type: "datetime"
formats:
- "2006-01-02T15:04:05Z07:00"
- "2006-01-02"
# Prefix matching
- type: "prefix"
pattern: "fake-api-test-"
length: 5
endpoints:
# Replace the response of the endpoint with the static_response
- method: "GET"
path: "/api/v1/Activities/30"
status: 200
static_response: |
{
"id": 77,
"title": "John Doe",
"dueDate": "2025-02-19T17:12:52.127Z",
"completed": false
}
با استفاده از این پیکربندی ، ما هدف ما این است که به طور خودکار پرونده های شبیه سازی خود را برای کنترل داده های پویا بدون ویرایش دستی آنها تغییر دهیم.
من نمی خواهم به جزئیات اجرای این مقاله عمیق شوم (اگر علاقه مند هستید ، می توانید همه چیز را در کد پیدا کنید) ، بنابراین من فقط مفاهیم اصلی را به شما نشان می دهم.
در اینجا نقطه ورود اجرای پردازنده آورده شده است:
func (p *PostProcessor) Process(simulation *v2.SimulationViewV5) error {
if simulation == nil {
return fmt.Errorf("nil simulation provided")
}
for i := range simulation.RequestResponsePairs {
pair := &simulation.RequestResponsePairs[i]
// 1. Check if this is a static endpoint rule
if p.endpointProcessor != nil {
if rule := p.endpointProcessor.FindMatchingRule(pair); rule != nil {
if err := p.endpointProcessor.ApplyRule(pair, rule); err != nil {
return fmt.Errorf("applying static response for pair %d: %w", i, err)
}
continue
}
}
// 2. Process the pair
if err := p.processingStrategy.Process(pair); err != nil {
return fmt.Errorf("processing pair %d: %w", i, err)
}
}
return nil
}
بیایید اکنون از نقطه پایانی استاتیک پرش کنیم و به پردازش هر جفت نزدیک تر نگاه کنیم.
برای الگوهای پیشوند در درخواست ها:
func (p *PrefixProcessor) ProcessRequest(value string) (string, bool) {
if p.replaceWith != "" {
return p.replaceWith, true
}
// Properly escape any regex special characters in the prefix
escapedPrefix := regexp.QuoteMeta(p.prefix)
// Check if we detected a resource name embedded in the value
if resourceName := p.detectResourceName(value); resourceName != "" {
p.includeResourceName = true
p.resourceName = resourceName
// Pattern with resource name included
return fmt.Sprintf("%s%s-[a-zA-Z0-9]{%d}", escapedPrefix, resourceName, p.randomLength), true
}
// Standard pattern without resource name
return fmt.Sprintf("%s[a-zA-Z0-9]{%d}", escapedPrefix, p.randomLength), true
}
و برای پاسخ ها:
func (p *PrefixProcessor) ProcessResponse(field string, value string, modifiedFields map[string]bool) (string, bool) {
if !p.Match(value) {
return value, false
}
// If this field was modified in the request and no fixed replacement
if modifiedFields[field] && p.replaceWith == "" {
return fmt.Sprintf("{{ Request.Body 'jsonpath' '$.%s' }}", field), true
}
if p.replaceWith != "" {
return p.replaceWith, true
}
return value, false
}
این رویکرد:
- برای استفاده از زمینه ها ، تطبیق را تغییر می دهد
regex
تطبیق کننده - از سیستم قالب بندی Hoverfly برای استخراج مقادیر از درخواست ورودی استفاده می کند
- از همان مقادیر در پاسخ استفاده می کند ، و ثبات را حفظ می کند
به دنبال یک اصل مشابه ، بیایید به مثال UUID نگاه کنیم:
func (p *UUIDProcessor) ProcessRequest(_ string) (string, bool) {
if p.replaceWith != "" {
return p.replaceWith, true
}
// Use proper regex pattern for UUIDs that will work with Hoverfly's RegexMatch
return `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`, true
}
func (p *UUIDProcessor) ProcessResponse(field string, value string, modifiedFields map[string]bool) (string, bool) {
if !p.Match(value) {
return value, false
}
// If we have a fixed replacement, use it regardless of modified fields
if p.replaceWith != "" {
return p.replaceWith, true
}
// If this field was modified in the request and modifiedFields is provided
if modifiedFields != nil && modifiedFields[field] {
return fmt.Sprintf("{{ Request.Body 'jsonpath' '$.%s' }}", field), true
}
return value, false
}
همه این پردازنده ها ارکستر شده اند:
// Process handles both the request and response processing for a pair
func (s *DefaultProcessingStrategy) Process(pair *v2.RequestMatcherResponsePairViewV5) error {
// First try to process the request
modifiedFields, err := s.ProcessRequest(pair)
if err != nil {
return err
}
// Then process the response
if len(modifiedFields) > 0 {
// If we modified fields in the request, process response with them
if err = s.ProcessResponse(pair, modifiedFields); err != nil {
return err
}
return nil
}
// If we didn't modify any request fields (e.g., no request body or no matches),
// try to process the response independently
if err = s.processResponseForRequestsWithoutBody(pair); err != nil {
return err
}
return nil
}
در اینجا ما:
- اولین پردازش ها را پردازش کنید و زمینه های پویا را شناسایی کنید
- آهنگ هایی که در آن زمینه اصلاح شده است
- پاسخ را پردازش کنید ، در صورت لزوم از قالب استفاده کنید
- موارد ویژه ای مانند درخواست های بدون بدن را کنترل می کند
توجه: من می دانم که قطعه های کد در بالا می توانند کمی پیچیده برای هضم در خواندن اول باشند. نگران نباشید اگر بلافاصله تمام اجرای را درک نکنید – آنها در درجه اول برای کسانی که می خواهند عمیق تر شیرجه بزنند ، گنجانده شده است. اگر ترجیح می دهید ، می توانید به جای کد خاص ، روی مفهوم کلی تمرکز کنید. اگر می خواهید بیشتر کاوش کنید ، کد منبع کامل در GitHub در دسترس است.
برای خلاصه کردن ایده اصلی: ما پرونده های خروجی JSON را تغییر می دهیم ، و به Hoverfly می گوییم که از داده های درخواست در پاسخ استفاده کندبشر البته می توانید این روش را اصلاح کنید تا با داده های استاتیک ، داده های تصادفی یا هر چیز دیگری که لازم دارید پاسخ دهید.
API احیا می کند
بعضی اوقات کارها کار نمی کند ، حداقل بعد از اولین تلاش. ممکن است درخواست ما با استفاده مجدد از درخواست ها نیاز به انجام خرابی های موقت داشته باشد.
بیایید به یک سرور تست نگاه کنیم که یک API غیرقابل اعتماد را شبیه سازی می کند:
func (ts *TestServer) handleGetActivity(w http.ResponseWriter, r *http.Request) {
attempt := ts.attempts.Add(1)
// Extract the id from the URL path
pathParts := strings.Split(r.URL.Path, "https://dev.to/")
if len(pathParts) < 5 {
http.Error(w, "Invalid URL", http.StatusBadRequest)
return
}
idStr := pathParts[4]
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
// Simulate different responses based on attempt number
switch attempt {
case 1:
// First attempt - Service Unavailable
w.WriteHeader(http.StatusServiceUnavailable)
json.NewEncoder(w).Encode(map[string]string{
"error": "Service Temporarily Unavailable",
"code": "SERVER_BUSY",
})
case 2:
// Second attempt - Gateway Timeout
w.WriteHeader(http.StatusGatewayTimeout)
case 3:
// Third attempt - Success
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"id": int32(id),
"title": "Test Activity",
"dueDate": "2025-02-20T09:58:59.009Z",
"completed": false,
})
default:
// Any subsequent attempts - Bad Request
w.WriteHeader(http.StatusTooManyRequests)
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "Too many attempts",
})
}
}
این سرور:
- در اولین تلاش 503 (سرویس موجود در دسترس) را برمی گرداند
- در تلاش دوم یک 504 (Timeout Gateway) را برمی گرداند
- در تلاش سوم با 200 (خوب) موفق می شود
به طور پیش فرض ، Hoverfly فقط پاسخ موفقیت آمیز نهایی را ضبط می کند. و البته تست ما شکست خواهد خورد ، اما مثل این که ما نیز یک ترفند برای آن داریم.
فعال کردن تصرف دولت
برای ضبط این دنباله پاسخ ها ، باید فعال کنیم حالت ضبط حالت در Hoverfly:
hoverctl mode capture --stateful
دستگیری مطبوع چقدر کار می کند
هنگامی که در حالت ضبط حالت ، Hoverfly ابرداده را به جفت های درخواست/پاسخ ضبط شده اضافه می کند تا دنباله آنها را ردیابی کند:
- نیاز به – به درخواست اضافه شده برای نشان دادن اینکه کدام حالت برای مطابقت با این مطلب لازم است اضافه شود
- انتقال – به پاسخ اضافه شده است تا نشان دهد چگونه دولت پس از این پاسخ تغییر می کند
بیایید ببینیم که چگونه این در پرونده شبیه سازی ظاهر می شود:
در اینجا نحوه عملکرد دنباله آورده شده است:
- hoverfly به طور خودکار یک متغیر حالت به نام ایجاد می کند
sequence:1
و آن را به “1” آغاز می کند - اولین درخواست زمانی مطابقت دارد که دولت “1” باشد
- پس از خدمت اولین پاسخ ، دولت به “2” منتقل می شود
- درخواست دوم زمانی مطابقت دارد که دولت “2” باشد
- و غیره …
- پس از خدمت آخرین پاسخ ، هیچ انتقال دیگری تعریف نشده است
آزمایش با شبیه سازی های دولتی
هنگامی که ما تست ها را علیه این شبیه سازی اجرا می کنیم ، Hoverfly دنباله پاسخ ها را به ترتیب باز می کند ، و سناریوی آزمایش مجدد ما را کاملاً شبیه سازی می کند:
func TestActivitiesWithRetries(t *testing.T) {
serverUrl, ok := os.LookupEnv("API_SERVER_URL")
if !ok || serverUrl == "" {
ts := tests.NewTestServer() // Start our test server
defer ts.Close()
serverUrl = ts.URL()
}
httpClient := tests.NewHttpClient(t)
client, err := NewClientWithResponses(serverUrl, WithHTTPClient(httpClient))
if err != nil {
t.Fatalf("failed to inistialize client: %v", err)
}
defer func() {
// Reset the server state
_, _ = httpClient.Post(serverUrl+"/reset", "application/json", nil)
}()
scenarios := []testCase{
{
name: "create activity with retries",
testFunc: func(t *testing.T, client *ClientWithResponses) {
var (
ctx = context.Background()
id = int32(7474)
title = "Test Activity"
)
var lastErr error
for attempt := 1; attempt <= 4; attempt++ {
resp, err := client.GetApiV1ActivitiesIdWithResponse(
ctx,
id,
)
if err != nil {
lastErr = err
continue
}
// Success!
if resp.StatusCode() == http.StatusOK {
require.NotNil(t, resp.ApplicationjsonV10200)
assert.Equal(t, id, *resp.ApplicationjsonV10200.Id)
assert.Equal(t, title, *resp.ApplicationjsonV10200.Title)
return
}
// Continue if it's a retryable status
if resp.StatusCode() == http.StatusServiceUnavailable ||
resp.StatusCode() == http.StatusGatewayTimeout {
continue
}
// Non-retryable error
t.Fatalf("Got non-retryable status: %d", resp.StatusCode())
}
// If we got here, all retries failed
t.Fatalf("All retries failed. Last error: %v", lastErr)
},
},
}
for _, tt := range scenarios {
t.Run(tt.name, func(t *testing.T) {
tt.testFunc(t, client)
})
}
}
مزایای شبیه سازی دولتی
استفاده از ضبط حالت ممکن است مزایای مختلفی را ارائه دهد:
- مکانیسم های آزمایش مجدد را به درستی آزمایش کنید – تأیید کنید که مشتری شما به درستی خطاهای موقت را انجام می دهد
- گردش کار را شبیه سازی کنید – فرآیندهای چند مرحله ای را که با هر درخواست حالت را تغییر می دهد آزمایش کنید
- بازتولید موارد لبه – شرایط خطای نادر را ضبط و پخش کنید
با ترکیب پردازنده های الگوی خود با ضبط حالت ، ما توانایی آزمایش داده های پویا و تعاملات پیچیده حالت را بدون تغییر کد آزمون خود به دست می آوریم.
تعویض پاسخ استاتیک
بعضی اوقات می خواهید یک پاسخ کاملاً متفاوت برای نقاط پایانی خاص ارائه دهید. این برای:
- ایجاد رفتار خاص تست – بازگشت پاسخ های از پیش تعریف شده برای موارد تست خاص
- رسیدگی به موارد لبه – شبیه سازی شرایط خطا یا سناریوهای خاص
- حفظ داده های آزمون مداوم – اطمینان از آزمایشات با مقادیر شناخته شده
پردازنده ما از این بخش از قسمت انتهای در پیکربندی پشتیبانی می کند:
endpoints:
- method: "GET"
path: "/api/v1/Activities/30"
status: 200
static_response: |
{
"id": 77,
"title": "John Doe",
"dueDate": "2025-02-19T17:12:52.127Z",
"completed": false
}
این کاملاً از هرگونه پاسخ ضبط شده برای نقطه پایانی مشخص شده است ، و کنترل دقیقی بر رفتار در تست های شما می دهد.
پایان
ما چندین تکنیک قدرتمند برای آزمایش API با شبیه سازی ها بررسی کرده ایم:
- درک سیستم تطبیق Hoverfly – یادگیری نحوه کار تطبیقی ها
- رسیدگی به داده های پویا – ساخت شبیه سازی ها با UUID ها ، رشته های تصادفی و Timestamps کار می کنند
- تست منطق آزمایش مجدد – استفاده از ضبط حالت برای شبیه سازی API های غیرقابل اعتماد
- تعویض پاسخ استاتیک – بر نقاط پایانی خاص برای سناریوهای آزمون
البته این رویکرد ممکن است برای همه نباشد. اگر به انعطاف پذیری بسیار بیشتری احتیاج دارید یا یک پروژه جدید را شروع می کنید ، ممکن است ابزارهایی مانند Wiremock انتخاب بهتری باشند. اما موارد استفاده خاصی وجود دارد که این رویکرد به ویژه مفید و مؤثرتر از گزینه های دیگر است و Hoverfly ابزاری مفید برای داشتن آرسنال آزمایش شما است.
در قسمت آخر این سری ، ما ادغام این ابزارها را در آن کشف خواهیم کرد سیخ خطوط لوله و گردش کار.
رمز منبع
کد منبع کامل این راه حل در GitHub موجود است.