ბიბლიოთეკა
00/07 · ~36 წთ
GUIDEDECK · PART 2 · ინჟინრებისთვის, ვინც უკვე ტესტავს

მოწინავე
ტესტირება —
მწვანე ნიშნულის მიღმა.

ტესტირება & TDD-ის მე-2 ნაწილი. შესავალმა მოგცათ პირამიდა, TDD-ის ციკლი და ხუთი სატესტო დუბლიორი. ეს სესია სიღრმეში მიდის: გენერატორები, რომლებიც სასაზღვრო შემთხვევებს ნადირობენ, კონტრაქტები სერვისებს შორის, თქვენივე ტესტების ტესტირება და დროის, პარალელურობისა და იმ საგნების შავი ხელოვნება, რომლებიც თქვენ არ გეკუთვნით.

~36 წთსაშუალო → მოწინავეენისგან დამოუკიდებელი
გადაახვიეთ
01 · თვისებაზე დაფუძნებული 6 წთ

ნუღარ გამოიცნობთ შემავალ მონაცემებს.
დააგენერირეთ ისინი ათასობით.

ეს ნაწილი 2-ია — პირდაპირ ეყრდნობა შესავალ დეკს, ამიტომ ვვარაუდობთ, რომ თავისუფლად ფლობთ მაგალითებზე დაფუძნებულ ტესტებსა და arrange-act-assert ფორმას. ნახტომი აქ ასეთია: ერთი ხელით შერჩეული შემავალი მნიშვნელობისთვის შედეგის მტკიცების ნაცვლად თქვენ აცხადებთ თვისებას, რომელიც ყველა შემავალისთვის უნდა სრულდებოდეს, შემდეგ კი ფრეიმვორკს ასობით მათგანს აწარმოებინებთ — მათ შორის ისეთ სასტიკებს, რომლებსაც არასდროს აკრეფდით.

თვისებაზე დაფუძნებული ტესტირება (PBT) — დაამტკიცეთ უნივერსალური წესი და არა კონკრეტული შედეგი. თქვენ აღწერთ გენერატორს (ვალიდური შემავალი მონაცემის ფორმას) და თვისებას (ლოგიკურ პირობას, რომელიც ჭეშმარიტი უნდა დარჩეს გენერატორის მიერ წარმოებული ყოველი მნიშვნელობისთვის). გამშვები ნიმუშებს არჩევს — ჩვეულებრივ 100–1000 შემთხვევას თითო გაშვებაზე — და როგორც კი ერთი ჩავარდება, კვეცავს მას უმცირეს გამეორებად მაგალითამდე. წარმოშობა: Haskell-ის QuickCheck (2000); დღეს fast-check, Hypothesis და jqwik ამ იდეას JS-ში, Python-სა და JVM-ში ატარებენ.
3 შემავალი, რაც მოიფიქრეთ
[], [1], [1,2,3]
შეკვეცა
[5,-3,9,2,-7] ✕
[-3,9] ✕
[-3] ✕
გენერატორი → 1000-ები
[], [NaN], [-0], [MAX_INT]…
[-1] მინიმალური
მაგალითებზე
თვისებებზე

მაგალითებზე დაფუძნებული ტესტები იქ იჭრებიან, სადაც უკვე იყურეთ. გენერატორები კი იქ, სადაც არა — შემდეგ კი ჩავარდნას ერთსტრიქონიან რეპროდუქციამდე კვეცავენ.

რატომ პოულობს ბაგებს, რომლებსაც თქვენ ვერ იპოვით

  • გენერატორი მიზანმიმართულად იკვლევს კიდეებს — ცარიელს, ნულს, უარყოფითს, Unicode-ს, უზარმაზარს, გამეორებულს, NaN-ს.
  • ჩავარდნა ბუნდოვანი სტეკ-ტრეისი არ არის — შეკვეცა ხელში გაძლევთ უმცირეს შემავალს, რომელიც თვისებას არღვევს.
  • ყოველი გაშვება ბეჭდავს seed-ს; ჩასვით ის უკან და ზუსტად იმავე ჩავარდნის თანმიმდევრობას დეტერმინისტულად გაიმეორებთ.
  • ის გაიძულებთ ჩამოაყალიბოთ, რა უნდა იყოს ყოველთვის ჭეშმარიტი — ხშირად სწორედ ეს არის ყველაზე ღირებული ნაწილი.
// fast-check (TypeScript)
import fc from "fast-check"

test("decode(encode(x)) === x for ALL orders", () => {
  fc.assert(
    fc.property(orderArb, (order) => {
      const back = decode(encode(order))
      expect(back).toEqual(order)   // the invariant
    }),
    { numRuns: 500, seed: 42 }     // reproducible
  )
})

გენერაცია → გაშვება → ჩავარდნისას შეკვეცა მინიმალურ კონტრმაგალითამდე, შემდეგ კი მისი მოხსენება seed-თან ერთად.

რთული ტული კი არაა — რთული თვისების დასახელებაა

იშვიათად გაქვთ ორაკული, რომელიც სწორ პასუხს ხელახლა გამოთვლის. ეს ხუთი არქეტიპი სწორედ ის გზაა, რომლითაც გამოცდილი ტესტერები თვისებებს ორაკულის გარეშე პოულობენ.

↺
წრიული (იქით და უკან)
decode(encode(x)) === x
+

ყველაფერი, რაც სერიალიზდება, პარსდება, იკუმშება ან იშიფრება, უცვლელად უნდა დაბრუნდეს. ყველაზე ნაყოფიერი თვისება, რომლის დაწერაც შეიძლება — უფასოდ იჭერს დანაკარგიან სასაზღვრო შემთხვევებს (ბოლო ჰარეები, Unicode-ის ნორმალიზაცია, -0).

=
ინვარიანტი (რაღაც ყოველთვის მართალია)
სიმართლე შედეგზე, შემავალის მიუხედავად
+

sort(xs)-ის შემდეგ: შედეგს იგივე სიგრძე აქვს, შემავალის გადანაცვლებაა და ყოველი ელემენტი ≤ მომდევნოზე. თქვენ სისწორის ფორმას ამტკიცებთ პასუხის ხელახლა გამოთვლის გარეშე.

≡
იდემპოტენტურობა
f(f(x)) === f(x)
+

გზის ნორმალიზება, სტრიქონის მოკვეცა, მიგრაციის ორჯერ გაშვება — ხელახლა გაკეთებამ არაფერი უნდა შეცვალოს. კლასიკური წყარო რეალური ბაგებისა "გამეორებისთვის უსაფრთხო" კოდში.

⊕
ორაკული / მოდელზე
შედარება მარტივ, აშკარად სწორ ვერსიასთან
+

შეამოწმეთ სწრაფი იმპლემენტაცია ნელ, გულუბრყვილო და სანდო ვერსიასთან — ან ძველ კოდთან რეფაქტორინგის დროს. ასევე: კომუტაციურობა (add(a,b) === add(b,a)) და შედარება საეტალონო ბიბლიოთეკასთან.

∿
მეტამორფული მიმართება
როგორ იცვლება შედეგი შემავალის შეცვლისას
+

როცა ორაკული საერთოდ არ არსებობს: დაამტკიცეთ მიმართება. "კატის" ძებნამ სულ მცირე იმდენივე შედეგი უნდა დააბრუნოს, რამდენიც "შავი კატის" ძებნამ. ყველა ფასის 2-ზე გამრავლებამ კალათის ჯამი უნდა გააორმაგოს. ძლიერია ML-სა და ძებნისთვის.

გააგრძელეთ · stateful PBT

დააგენერირეთ ოპერაციების მთელი თანმიმდევრობები

მოდელზე დაფუძნებული / stateful ტესტირება აგენერირებს ბრძანებების შემთხვევით თანმიმდევრობებს თქვენი სისტემისა და პატარა in-memory მოდელის წინააღმდეგ და ამტკიცებს, რომ ისინი ყოველ ნაბიჯზე თანხმდებიან — fast-check-ის fc.commands, Hypothesis-ის RuleBasedStateMachine. ის პოულობს თანმიმდევრობისა და პარალელურობის ბაგებს, რომლებსაც ერთგამოძახებიანი თვისება ვერასდროს იპოვიდა.

02 · კონტრაქტული ტესტები 5 წთ

დაიჭირეთ გატეხილი ინტეგრაცია
ორივე სერვისის აწევის გარეშე.

ორი სერვისი API-ზე თანხმდება. სერვისი B ცვლის ველს; სერვისი A პროდაქშენში ტყდება — და არცერთმა იუნიტ-ტესტმა ეს ვერ დაიჭირა, რადგან თითოეულმა მხარემ მეორე საკუთარი დაშვებებით დაამოკა. კონტრაქტული ტესტირება ამ დაშვებებს საერთო, შემოწმებად არტეფაქტში ამაგრებს.

კონსიუმერით მართული კონტრაქტული (CDC) ტესტირება — კონსიუმერი იწერს ზუსტად იმას, რაც სჭირდება; პროვაიდერი კი ამტკიცებს, რომ ისევ აწვდის. კონსიუმერი წერს ტესტს მოკის წინააღმდეგ და გამოსცემს კონტრაქტს (pact-ფაილს: მოთხოვნებს, რომლებსაც აგზავნის, და პასუხებს, რომლებზეც ეყრდნობა). პროვაიდერი ამ კონტრაქტს საკუთარ პაიპლაინში, ნამდვილ კოდზე ატარებს. საერთო გარემო არ სჭირდება, ბოლომდე გაშვება არ სჭირდება — მხოლოდ ორი სწრაფი, დამოუკიდებელი ტესტების ნაკრები, რომლებიც ჩუმად ვერ დაშორდებიან ერთმანეთს.

კონსიუმერის მოლოდინები იქცევა ფაილად, რომელიც პროვაიდერმა უნდა დააკმაყოფილოს — შემოწმებული ორ ცალკე პაიპლაინში, არასდროს ერთად.

სად დგას ის პირამიდაში

  • ის ცვლის სერვისთაშორისი ინტეგრაციული ტესტების უმეტესობას — იმ ნელ, არასტაბილურ ზოლს, რომელზეც შესავალი დეკი გაფრთხილებდათ.
  • დარღვეული კონტრაქტი ბილდის დროს ჩავარდება, იმ გუნდის პაიპლაინში, რომელმაც ის დაარღვია — და არა ღამის 2 საათზე პროდაქშენში.
  • ის ამოწმებს იმ ფორმასა და მნიშვნელობას, რომელსაც კონსიუმერი რეალურად იყენებს, და არა პროვაიდერის მთელ ზედაპირს.
  • can-i-deploy ეკითხება ბროკერს: "ამ ვერსიის ყოველი კონსიუმერი ისევ თავსებადია?" — გაშვებამდე.
// Pact v3 consumer test (the WEB side)
const pact = new PactV3({ consumer: "Web", provider: "Orders" })

pact
  .given("order 42 exists")                // provider state
  .uponReceiving("a request for order 42")
  .withRequest({ method: "GET", path: "/orders/42" })
  .willRespondWith({ status: 200,
    body: { id: integer(42), total: decimal(9.9) } })

await pact.executeTest(async (mock) => {
  const o = await fetchOrder(mock.url, 42)
  expect(o.id).toBe(42)              // drives the contract
})

დაამთხვიეთ ტიპი და არა ზუსტი მნიშვნელობა — კონტრაქტი ამაგრებს იმ ფორმას, რომელზეც კონსიუმერი დგას, და ითმენს ახალ მონაცემებს, რომლებსაც ის უგულებელყოფს.

მდგომარეობა

given("order 42 exists")

ყოველი ურთიერთქმედება ასახელებს წინაპირობას. პროვაიდერის მხარეს ამ მდგომარეობის სახელს ფიქსტურაზე აბამთ (ჩაწერეთ სტრიქონი, დაასტაბეთ დამოკიდებულება), რომ შემოწმება დეტერმინისტული იყოს.

ბროკერი · can-i-deploy

თავსებადობის მატრიცა

ბროკერი (PactFlow ან თვითჰოსტინგი) თვალს ადევნებს, კონსიუმერისა და პროვაიდერის რომელი ვერსიები შემოწმდა ერთმანეთთან. can-i-deploy რელიზს მწვანე მატრიცაზე აბამს.

არა მხოლოდ HTTP

ასინქრონული & სქემა-პირველი

Pact ფარავს შეტყობინებების კონტრაქტებსაც (Kafka, რიგები). მხოლოდ სქემის გარანტიებისთვის OpenAPI / Protobuf + buf-ის მრღვევი ცვლილებების შემოწმება ავსებს სურათს — ისინი ფორმას ამოწმებენ და არა კონსიუმერის განზრახვას.

03 · მუტაციური ტესტირება 5 წთ

100% დაფარვა,
ნული მტკიცება. ვინ ტესტავს ტესტებს?

დაფარვა გეუბნებათ, რომ ხაზი გაეშვა. ის არაფერს ამბობს იმაზე, შეამჩნევდა თუ არა თქვენი ტესტი, რომ ეს ხაზი მცდარია. მუტაციური ტესტირება სწორედ ამას ზომავს — თქვენს კოდს განზრახ ტეხავს და ამოწმებს, აყვირდება თუ არა ნაკრები.

მუტაციური ტესტირება — შეაფასეთ ტესტები კოდის გატეხვით. ინსტრუმენტი აგენერირებს მუტანტებს: თქვენი კოდის პაწაწინა, ერთცვლილებიან ვარიანტებს — < ხდება <=, + ხდება -, return true ბრუნდება, ინსტრუქცია იშლება. თითოეულზე უშვებს თქვენს ნაკრებს. თუ ტესტი ჩავარდა, მუტანტი მოკლულია (კარგია — თქვენმა ტესტებმა ბაგი დაიჭირეს). თუ ყველა ტესტი მაინც გადის, მუტანტი გადარჩა — ეს კი მტკიცების ნამდვილი ხარვეზია.

age >= 17 გადარჩა → არცერთი ტესტი არ ამოწმებს ზღვარს 17-ზე. ეს გადარჩენილი ზუსტი მითითებაა, რომელი ტესტი დაწეროთ შემდეგ.

მუტაციის ქულა

მუტაციის ქულა = killed / (total − equivalent). გადარჩენილი არის ადგილი, სადაც თქვენი კოდი შეიძლება მცდარი იყოს და არცერთი ტესტი ამას არ გეტყვით. ეს ხაზების დაფარვაზე ბევრად ძლიერი სიგნალია — და ბევრად ნელი გამოსათვლელი.
  • მოკლული — ტესტი მუტანტზე ჩავარდა. სწორედ ეს გინდათ.
  • გადარჩენილი — ყველა ტესტი გავიდა. ნამდვილი ხარვეზი: აკლია მტკიცება ან შეუმოწმებელია ზღვარი.
  • დაფარვის გარეშე — ხაზი საერთოდ არ გაშვებულა. ამას უფრო სწრაფმა ინსტრუმენტმა (დაფარვამ) უკვე გითხრათ.
  • ტაიმაუტი — მუტანტმა უსასრულო ციკლი გამოიწვია; ითვლება მოკლულად.
ეკვივალენტური მუტანტი — მუტაცია, რომელიც კოდს ცვლის, ქცევას კი არა. i < n და i != n ციკლში, რომელიც მხოლოდ ზრდის მთვლელს, იდენტურ შედეგს იძლევა, ამიტომ მისი მოკვლა არცერთ ტესტს არ შეუძლია. ეკვივალენტური მუტანტები ზოგად შემთხვევაში გადაუწყვეტელია და სწორედ ისინია მიზეზი იმისა, რომ 100% მცდარი სამიზნეა — ისინი მნიშვნელს მოუკვლელი ხმაურით ბერავენ. დაედევნეთ იმ გადარჩენილებს, რომლებიც ნამდვილ ქცევას ასახავენ, და არა ბოლო რამდენიმე პროცენტს.
მაღალი დაფარვა, სუსტი ტესტი
// runs every line — asserts almost nothing
test("applies discount", () => {
  const total = price(100, { vip: true })
  expect(total).toBeDefined()   // mutants survive freely
})
იგივე დაფარვა, კლავს მუტანტებს
// pins the value AND the boundary
test("VIP gets 20% off", () => {
  expect(price(100, { vip: true })).toBe(80)
  expect(price(100, { vip: false })).toBe(100)
})

ის ნელია — ყოველი მუტანტი თავიდან უშვებს თქვენი ნაკრების (ნაწილს), ამიტომ სრულ გაშვებას საათები შეიძლება დასჭირდეს. გახადეთ პრაქტიკული: CI-ში გაუშვით მხოლოდ შეცვლილ ფაილებზე (Stryker-ის ინკრემენტული რეჟიმი, PIT-ის history), დააყენეთ გონივრული ზღვარი (ჩავარდნა, ვთქვათ, შეხებულ კოდზე 60%-ის ქვემოთ) და გადარჩენილთა სია მიიღეთ სამუშაო სიად და არა ამაო მეტრიკად.

04 · ტესტის არქიტექტურა 5 წთ

ტესტების ნაკრები კოდის ბაზაა.
დააპროექტეთ მისი მონაცემები შესაბამისად.

ნაკრების ლპობის მიზეზი ცუდი მტკიცებები კი არა — უმართავი სატესტო მონაცემები და ფარული კავშირებია. 40-სტრიქონიანი მოსამზადებელი ბლოკი, საერთო ცვალებადი ფიქსტურა, თანმიმდევრობაზე დამოკიდებული გავლა: სწორედ ეს აქცევს მწვანე ნაკრებს არასანდოდ. დააპროექტეთ ისინი განზრახ.

სატესტო მონაცემების ბილდერი — პატარა დამხმარე, რომელიც ვალიდურ ობიექტს გონივრული ნაგულისხმევებით აწყობს და ყოველ ტესტს მხოლოდ იმის გადაწერის საშუალებას აძლევს, რაც მას აინტერესებს. ის ცვლის მყიფე, გამეორებად ლიტერალებს, ასე რომ ტესტი იკითხება როგორც "მომხმარებელი, ოღონდ დაბლოკილი" — და ერთადერთი მნიშვნელოვანი ცვლადი აშკარა ხდება. მისი ბიძაშვილები: Object Mother (დასახელებული მზა ინსტანციები: aVipCustomer()) და ფაბრიკები (Factory Boy, fishery, FactoryBot), რომლებიც ბაზაშიც ინახავენ.
// defaults make every test start from "valid"
const aUser = (over: Partial<User> = {}): User => ({
  id: randomId(), name: "Test User",
  status: "active", createdAt: clock.now(),
  ...over,                       // override ONLY what matters
})

// the test states its one relevant fact, nothing else
const banned = aUser({ status: "banned" })
expect(canCheckout(banned)).toBe(false)

ერთი ფაბრიკა, მრავალი ვარიანტი. ტესტი ასახელებს მხოლოდ იმ ველს, რომელსაც ამოწმებს — დანარჩენი სანდო ნაგულისხმევია.

ფიქსტურები vs ფაბრიკები — იცოდეთ განსხვავება

ფიქსტურა

მომზადებული გარემო

შემოსაზღვრული მომზადება/დაშლა — დროებითი ბაზა, შესული კლიენტი, ჩაწერილი სტრიქონი. pytest-ის @fixture function/module/session სკოუპით სწორედ ეს მოდელია: აცხადებთ დამოკიდებულებას, გამშვები კი აბამს და შემდეგ შლის მას. რისკი: session-სკოუპის ფიქსტურა, რომელიც ერთმა ტესტმა შეცვალა, მომდევნოში გადმოიღვრება.

ფაბრიკა

ბილდერი, რომელიც მონაცემებს ქმნის

მოთხოვნისთანავე აწარმოებს ახალ, დამოუკიდებელ ინსტანციებს — სურვილისამებრ შენახვითაც. ერთეულებისთვის არჩიეთ ფაბრიკები საერთო ფიქსტურებს: ყოველი ტესტი თავის მონაცემებს ფლობს, ამიტომ გადმოსაღვრელი არაფერია. ფიქსტურები დაიტოვეთ ძვირადღირებული ინფრასტრუქტურისთვის (კონტეინერი, კავშირების პული).

არასტაბილურობა დიზაინის პრობლემაა და არა გამეორების

ყოველი არასტაბილურობა არადეტერმინიზმამდე მიდის. მოაშორეთ წყარო; ნუ დაფარავთ მას retry(3)-ით.

დისციპლინა

  • იზოლაცია — ყოველი ტესტი თავის მონაცემებს თვითონ ქმნის და შლის; თანმიმდევრობაზე დამოკიდებულება არ არსებობს. აურიეთ თანმიმდევრობა (pytest-randomly), რომ ფარული კავშირი გამოჩნდეს.
  • დეტერმინიზმი — ჩაამაგრეთ ყოველი seed (random, PBT, faker), შეიყვანეთ საათი, გაყინეთ ID-ები. იგივე შემავალი, იგივე შედეგი, ყოველ გაშვებაზე.
  • კარანტინი და არა უგულებელყოფა — გადაიტანეთ ცნობილი არასტაბილური ტესტი მონიშნულ ზოლში, რომელიც ბილდს არ ბლოკავს, გახსენით ტიკეტი და გამოასწორეთ ძირეული მიზეზი. გამეორება, რომელიც არასტაბილურობას "ასწორებს", ნამდვილ რბოლას მალავს.
05 · რთულის ტესტირება 6 წთ

დრო, პარალელურობა და
სერვისები, რომლებიც არ გეკუთვნით.

შესავალმა დეკმა ხუთი დუბლიორი გაგაცნოთ. ახლა რთული შემთხვევები: კოდი, რომელიც კედლის საათზე, რბოლის პირობებზე ან მესამე მხარის API-ზეა დამოკიდებული. გამაერთიანებელი ნაბიჯი ყოველთვის ერთია — აქციეთ უკონტროლო რამ შეყვანილ დამოკიდებულებად, რომელსაც ტესტიდან თქვენ მართავთ.

დრო

არასდროს წაიკითხოთ საათი პირდაპირ

ბიზნეს-ლოგიკაში მიმოფანტული Date.now() / datetime.now() "იწურება 30 დღეში"-ს შეუმოწმებელს ხდის. შეიყვანეთ Clock და ხელით წაწიეთ — ან გააყინეთ ის ყალბი ტაიმერებით (vi.useFakeTimers, sinon), freezegun-ით (Python) ან ფიქსირებული java.time.Clock-ით.

ასინქრონი/პარალელურობა

დაელოდეთ პირობას და არა ძილს

sleep(500) არასტაბილურობის #1 მიზეზია — ძალიან მოკლე რბოლაში აგებს, ძალიან გრძელი კი ცოცავს. დაელოდეთ პირობას (ელემენტს, მოვლენას, რიგის სიღრმეს). რბოლებისთვის გამოიყენეთ დეტერმინისტული განრიგი ან მართვადი ყალბი ტაიმერები, რომ თანმიმდევრობა თქვენ განსაზღვროთ.

გარე სერვისები

დაიმორჩილეთ თქვენი საზღვარი

შეფუთეთ მესამე მხარის SDK თქვენს ინტერფეისში და შემდეგ ამ ინტერფეისის ფეიქზე ატესტეთ. პლუს რეალურთან მიახლოებული ვარიანტები: Testcontainers (ნამდვილი Postgres/Kafka Docker-ში), WireMock / MSW HTTP-სთვის, ჩაწერა-გამეორების კასეტები (VCR).

// the clock is a dependency, not a global
interface Clock { now(): Date }

class Token {
  constructor(private clock: Clock) {}
  isExpired(t: TokenData) {
    return t.expiresAt < this.clock.now()
  }
}
// test: a clock you control, no real waiting
const clock = { now: () => new Date("2030-01-01") }
expect(new Token(clock).isExpired(old)).toBe(true)

შეიყვანეთ საათი და "ვადის გასვლის" ლოგიკა უბრალოდ მონაცემები ხდება — ნამდვილი საათი პროდაქშენში, გაყინული ტესტში.

ჰერმეტული ტესტი — ტესტი, რომელიც საკუთარი პროცესის გარეთ არაფერზეა დამოკიდებული: არც ქსელი, არც ნამდვილი საათი, არც საერთო ბაზა, არც გარემომცველი მდგომარეობა. ჰერმეტული ტესტები სწრაფია, პარალელურად უსაფრთხო და არასდროს არასტაბილური. ამ სექციაში ყველაფერი იმას ემსახურება, რომ თქვენი ტესტების უფრო დიდი ნაწილი ჰერმეტული გახდეს.

ფეიქები vs მოკები — ღრმა ვერსია

მოკი — ებმის ურთიერთქმედებას
// asserts HOW the code calls the dep
const repo = { save: jest.fn() }
register(repo, user)
expect(repo.save).toHaveBeenCalledWith(user)
// rename/reorder the call → test breaks,
// even though behavior is identical
ფეიქი — ებმის ქცევას
// a real, in-memory implementation
const repo = new InMemoryUserRepo()
register(repo, user)
expect(repo.findByEmail(user.email)).toEqual(user)
// asserts the OUTCOME — refactor-proof

გაუშვით ერთი და იგივე კონტრაქტის ნაკრები ფეიქზეც და ნამდვილზეც. თუ ორივე გადის, თქვენი ფეიქი ერთგულია — და სწრაფი იუნიტ-ტესტები სანდო რჩება.

ორი წესი, რომელიც ფეიქებს პატიოსნად ინახავს

  • ნუ დაამოკებთ იმას, რაც არ გეკუთვნით. მესამე მხარის SDK-ის დამოკება ტესტში თქვენს ვარაუდს ამაგრებს მის ქცევაზე. შეფუთეთ ის საკუთარ პორტში და ის გააფეიქეთ.
  • შეამოწმეთ ფეიქი რეალობასთან. ფეიქი მხოლოდ მაშინაა სასარგებლო, თუ ნამდვილივით იქცევა — ჩაამაგრეთ ეს საერთო კონტრაქტული ტესტით, რომელიც ორივეზე გადის (იგივე იდეა, რაც სექცია 2-ში, ოღონდ პროცესის შიგნით).
  • გამოიყენეთ მოკები ზომიერად — იმ ურთიერთქმედების შესამოწმებლად, რომელიც თავად არის ქცევა (წერილი გაიგზავნა), და არასდროს ნაგულისხმევად.
06 · მოწინავე ინსტრუმენტები 4 წთ

კომპლექტი ყველაფრისთვის,
რაც ამ დეკშია.

თითო წარმომადგენლობითი ინსტრუმენტი თითო ტექნიკაზე, თითო ეკოსისტემაში — გულახდილი კომპრომისითა და იმ მომენტით, როცა მას მიმართავთ. ყველა მომწიფებულია და აქტიურად ვითარდება 2026 წლის მდგომარეობით.

JS / TS · fast-check

fast-check

დადებითი: პირველხარისხოვანი TS-ტიპები, შესანიშნავი შეკვეცა, ინტეგრირდება Jest/Vitest-თან, stateful fc.commands.
უარყოფითი: რთული დომენებისთვის კარგი გენერატორების წერას პრაქტიკა სჭირდება.
მიმართეთ მას ნებისმიერ JS/TS სუფთა ლოგიკაზე: პარსერები, ფასწარმოქმნა, კოდერები.

Python · Hypothesis

Hypothesis

დადებითი: ოქროს სტანდარტი — მდიდარი სტრატეგიები, შენახული მაგალითების ბაზა, რომელიც წარსულ ჩავარდნებს იმეორებს, RuleBasedStateMachine.
უარყოფითი: გენერაცია შეიძლება ნელი იყოს; მოარგეთsettings-ის ვადები.
მიმართეთ მას ნაგულისხმევად ნებისმიერ Python-ლოგიკაზე, რომელიც სერიოზულ ტესტირებას იმსახურებს.

JVM · jqwik

jqwik

დადებითი: JUnit 5-ის ძრავია, ამიტომ არსებულ Java/Kotlin ნაკრებებში ჯდება; ინტეგრირებული შეკვეცა.
უარყოფითი: უფრო მცირე საზოგადოება; Hypothesis-ზე მეტი შაბლონური კოდი.
მიმართეთ მას როცა უკვე JUnit 5-ზე ხართ და გვერდით PBT გინდათ.

Polyglot · Pact

Pact

დადებითი: კონსიუმერით მართული დე-ფაქტო სტანდარტი; კლიენტები ყველა მთავარ ენაზე; HTTP + შეტყობინებები; ბროკერი can-i-deploy-ით.
უარყოფითი: რეალური სასწავლი მრუდი; პროვაიდერის მდგომარეობები და ბროკერის ექსპლუატაცია მოძრავ ნაწილებს ამატებს.
მიმართეთ მას როცა დამოუკიდებელი გუნდები სერვისებს სხვადასხვა რიტმით უშვებენ.

JVM · Spring Cloud Contract

Spring Cloud Contract

დადებითი: პროდიუსერით მართული, ღრმად ინტეგრირებული Spring-თან; კონსიუმერებისთვის სტაბებს აგენერირებს.
უარყოფითი: Spring-ცენტრული; JVM-ეკოსისტემის გარეთ მოუხერხებელი.
მიმართეთ მას მთლიანად Spring-ის მეურნეობაში, სადაც პროვაიდერი წინ მიდის.

Schema · OpenAPI / buf

სქემის შემოწმება

დადებითი: იაფი მრღვევი ცვლილებების აღმოჩენა REST/gRPC-ზე; კონსიუმერის ტესტი არ სჭირდება.
უარყოფითი: ამოწმებს ფორმას და არა იმას, რაზეც კონსიუმერი რეალურად დგას.
მიმართეთ მას სწრაფ პირველ ხაზად; განზრახვისთვის დააწყვილეთ Pact-თან.

JS / TS · Stryker

Stryker

დადებითი: მომწიფებული, შესანიშნავი HTML-რეპორტები, ინკრემენტული რეჟიმი CI-სთვის, ასევე .NET & Scala ვარიანტები.
უარყოფითი: ნელია დიდ ნაკრებებზე, თუ შეცვლილი ფაილებით არ შემოსაზღვრავთ.
მიმართეთ მას კრიტიკული მოდულის ტესტების აუდიტისთვის, CI-ში დიფზე.

JVM · PIT

PIT (pitest)

დადებითი: სწრაფი (ბაიტკოდის მუტაცია), JVM-ის სტანდარტი, ისტორიაზე დაფუძნებული ინკრემენტული გაშვებები.
უარყოფითი: Java-ცენტრული კონფიგურაცია; რეპორტების მორგებას ძალისხმევა სჭირდება.
მიმართეთ მას ნაგულისხმევ მუტაციურ ინსტრუმენტად ნებისმიერ JVM-პროექტზე.

Python · mutmut

mutmut / cosmic-ray

დადებითი: მარტივი ჩასართავი; გასაგები დიფები გადარჩენილებზე, რომლებზეც მოქმედება შეიძლება.
უარყოფითი: შესამჩნევად ნელი; საუკეთესოა ერთ პაკეტზე შემოსაზღვრული და არა მთელ რეპოზიტორიაზე.
მიმართეთ მას ერთი მაღალფასიანი Python-მოდულის ტესტების გასამაგრებლად.

E2E · Playwright

Playwright

დადებითი: მრავალბრაუზერული, ავტომატური ლოდინი (არასტაბილურობის დიდ ნაწილს კლავს), ტრეისები & ვიდეო, პარალელური დაშარდვა, პირველხარისხოვანი API- და კომპონენტების ტესტირება.
უარყოფითი: ისევ ყველაზე ნელი და ძვირი ზოლია — შეინახეთ თხელი.
მიმართეთ მას მომხმარებლის კრიტიკული გზების რამდენიმე სმოუკ-ტესტისთვის და არა ფუნქციონალის დასაფარად.

Infra · Testcontainers

Testcontainers

დადებითი: ნამდვილი Postgres / Kafka / Redis Docker-ში, ერთჯერადი თითო გაშვებაზე — მაღალი ერთგულება, საერთო გარემოს გარეშე.
უარყოფითი: სჭირდება Docker; in-memory ფეიქზე ნელია.
მიმართეთ მას ნამდვილი ადაპტერის (და თქვენი ფეიქის) ნამდვილ ძრავთან შესამოწმებლად.

HTTP · WireMock / MSW

WireMock · MSW

დადებითი: გარე HTTP-ს ქსელის კიდეზე დაასტაბებს; MSW მოკებს ტესტებსა და ბრაუზერს შორის აზიარებს.
უარყოფითი: სტაბები თქვენს ვარაუდებს ინახავს — თქვენს საკუთარ სერვისებზე დააწყვილეთ კონტრაქტულ ტესტებთან.
მიმართეთ მას მესამე მხარის HTTP-სგან იზოლაციისთვის, რომელსაც ლოკალურად ვერ უშვებთ.

ორსტრიქონიანი გადაწყვეტილების გზამკვლევი

  • სუფთა ლოგიკა ნათელი ინვარიანტებით? თვისებაზე დაფუძნებული (fast-check / Hypothesis / jqwik) — სანამ 20 მაგალითს ხელით დაწერთ.
  • რამდენიმე სერვისი, სხვადასხვა გუნდი? კონტრაქტული ტესტები (Pact) მყიფე სერვისთაშორისი E2E-ის ნაცვლად.
  • ტესტები, რომლებსაც კრიტიკულ კოდზე არ ენდობით? მუტაციური ტესტირება (Stryker / PIT / mutmut) დიფზე, ხარვეზების საპოვნელად.
  • ნამდვილი დამოკიდებულება ციკლში? Testcontainers ან WireMock / MSW ერთგულებისთვის; Playwright კი მხოლოდ პირამიდის თხელი წვერისთვის.
07 · მაგალითი · შეჯამება 5 წთ

ორი ნამდვილი ტესტი,
შემდეგ კი ხუთი დასამახსოვრებელი რამ.

თვისების ტესტი და კონტრაქტული ტესტი, თავიდან ბოლომდე — ორი ტექნიკა, რომლებიც ამ კვარტალში ყველაზე დიდი ალბათობით შეცვლის თქვენი გუნდის მუშაობას.

1 · თვისების ტესტი, რომელმაც ნამდვილი ბაგი იპოვა

// PROPERTY: splitting a bill loses no cents
import fc from "fast-check"

test("split(total, n) always sums back to total", () => {
  fc.assert(fc.property(
    fc.integer({ min: 0, max: 1e6 }),   // cents
    fc.integer({ min: 1, max: 50 }),    // people
    (total, n) => {
      const parts = split(total, n)
      const sum = parts.reduce((a, b) => a + b, 0)
      expect(sum).toBe(total)        // invariant
      expect(parts).toHaveLength(n)
    }))
})
// shrinks to split(1, 3) → [0,0,0], sum 0 ≠ 1  ✕

არცერთი მაგალითის ტესტი 1 ÷ 3-ს არ სცდიდა. შემკვეცავი ზუსტ მინიმალურ რეპროდუქციას გაძლევთ — კლასიკური ბაგი ნაშთზე.

2 · კონტრაქტული ტესტი, ორივე მხრიდან

კონსიუმერი აქვეყნებს პაქტს
pact.given("user 7 exists")
  .uponReceiving("GET /users/7")
  .withRequest({ method: "GET", path: "/users/7" })
  .willRespondWith({ status: 200,
    body: { id: integer(7), email: like("a@b.co") } })
// → publishes users-web.json to the broker
პროვაიდერი ამოწმებს
await new Verifier({
  provider: "Users",
  pactBrokerUrl: BROKER,
  stateHandlers: {
    "user 7 exists": () => db.seed({ id: 7 }),
  },
}).verifyProvider()
// fails the build if Users drops/renames a field
1დააფიქსირეთ თვისებები, დააგენერირეთ შემავალები. წრიული, ინვარიანტი, იდემპოტენტურობა, ორაკული, მეტამორფული — შემდეგ კი შეკვეცა თავად მოგცემთ მინიმალურ ბაგს.
2დადეთ კონტრაქტი და ნუ ინტეგრირდებით. ჩაამაგრეთ სერვისთაშორისი დაშვებები კონსიუმერით მართულ პაქტში; რელიზები კი can-i-deploy-ით შემოსაზღვრეთ.
3იმუტირეთ ტესტების შესაფასებლად. დაფარვა ამტკიცებს, რომ ხაზი გაეშვა; მუტაციის ქულა ამტკიცებს, რომ ტესტი გატეხვას დაიჭერს. დაედევნეთ გადარჩენილებს, უგულებელყავით ეკვივალენტურები.
4დააპროექტეთ მონაცემები და დეტერმინიზმი. ბილდერები მონაცემებისთვის, ფაბრიკები საერთო ფიქსტურების ნაცვლად, შეყვანილი საათები და ჩამაგრებული seed-ები — მოკალით არასტაბილურობა წყაროსთან.
5დაიმორჩილეთ საზღვრები. გააფეიქეთ ის ინტერფეისები, რომლებსაც ფლობთ; არასდროს დაამოკოთ ის, რაც არ გეკუთვნით; ყოველი ფეიქი შეამოწმეთ ნამდვილთან.
ცოდნის შემოწმება

დაგამახსოვრდათ?

ხუთი შეკითხვა თვისებაზე დაფუძნებულ, კონტრაქტულ და მუტაციურ ტესტირებაზე, პლუს რთული ნაწილის ტექნიკები — მყისიერი უკუკავშირი, შესვლის გარეშე.

შეაფასეთ ეს დასტა
იყავით პირველი

ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში