ЁЯЧВя╕П рдзрдбрд╛ 04 тАФ Fixtures рдЖрдгрд┐ test data: рддрдпрд╛рд░ рдЙрддреНрддрд░рдкрддреНрд░рд┐рдХрд╛
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 04 ┬╖ рдорд╛рдЧреЗ: lesson-03-test-design ┬╖ рдкреБрдвреЗ: lesson-05-test-doubles
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ03, рдЖрдгрд┐ fixtures: test рд▓рд╛ рдЖрдкрд▓рд╛ рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рдгреНрдпрд╛рдкреВрд░реНрд╡реА рд▓рд╛рдЧрдгрд╛рд░рд╛ data рдЖрдгрд┐ setup.
Builders рдПрдХрд╛ рдУрд│реАрдд рд╡реИрдз test data рдмрдирд╡рддрд╛рдд; setup рдЖрдгрд┐ teardown рдкреНрд░рддреНрдпреЗрдХ test рд▓рд╛ рдирд╡реА
sheet рджреЗрддрд╛рдд рдЖрдгрд┐ рдирдВрддрд░ рд╕рд╛рдлрд╕рдлрд╛рдИ рдХрд░рддрд╛рдд, test crash рдЭрд╛рд▓реА рддрд░реАрд╣реА; рдЖрдгрд┐
рд╕реНрд╡рддрдВрддреНрд░ tests рдХреЛрдгрддреНрдпрд╛рд╣реА рдХреНрд░рдорд╛рдиреЗ pass рд╣реЛрддрд╛рдд. exam/lab.py рдордзрд▓реЗ StudentBuilder, a_student() рдЖрдгрд┐
Runner.run(setup=тАж, teardown=тАж); exam/demo.py рдордзрд▓реЗ
fixtures().
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдкрд░реАрдХреНрд╖реЗрдкреВрд░реНрд╡реА office рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрд╕рд╛рдареА рдПрдХ рдЙрддреНрддрд░рдкрддреНрд░рд┐рдХрд╛ рддрдпрд╛рд░ рдХрд░рддреЗ: рдирд╛рд╡ рдЫрд╛рдкрд▓реЗрд▓реЗ, рдЪреМрдХрдЯреА рддрдпрд╛рд░. ЁЯЧВя╕П рддреЛрдЪ fixture.
рдХрддрд░рд┐рдирд╛рдХрдбреЗ рдПрдХ рд╣реБрд╢рд╛рд░ рдпреБрдХреНрддреА рдЖрд╣реЗ: рдПрдХ рддрдпрд╛рд░ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА. a_student() рдореНрд╣рдгрдЬреЗ рд╕реНрд╡рддрдГ рдХрддрд░рд┐рдирд╛,
рдЪрд╛рдВрдЧрд▓реЗ рдЧреБрдг рдЖрдгрд┐ 90% attendance рд╕рд╣ тАФ pass рд╣реЛрдгрд╛рд░реА рд╕рд╛рдорд╛рдиреНрдп рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА. "рдХрдореА attendance" test рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреА
рдлрдХреНрдд рддреЗрд╡рдвреЗрдЪ рдмрджрд▓рддреЗ: a_student().with_attendance(60). рдмрд╛рдХреА рд╕рдЧрд│реЗ рд╕рд╛рдорд╛рдиреНрдп рд░рд╛рд╣рддреЗ, рдореНрд╣рдгреВрди
test рд╕реНрдкрд╖реНрдЯрдкрдгреЗ attendance рдмрджреНрджрд▓рдЪ рдЖрд╣реЗ.
рдордЧ рдПрдХ рдЧреЛрдВрдзрд│. рджреЛрди рдкреНрд░рд╢реНрди рдПрдХрдЪ sheet рд╡рд╛рдкрд░рддрд╛рдд. рдкрд╣рд┐рд▓рд╛ рдкреНрд░рд╢реНрди рддреНрдпрд╛рд╡рд░ рдРрд╢реНрд╡рд░реНрдпрд╛рдЪреЗ рдирд╛рд╡ рд▓рд┐рд╣рд┐рддреЛ. рджреБрд╕рд▒реНрдпрд╛ рдкреНрд░рд╢реНрдирд╛рд▓рд╛ рдХреЛрд░реА sheet рдЕрдкреЗрдХреНрд╖рд┐рдд рдЕрд╕рддреЗ тАФ рдЖрдгрд┐ рддреНрдпрд╛рд▓рд╛ рдирд╛рд╡ рд╕рд╛рдкрдбрддреЗ. тЭМ рдХреНрд░рдо рдЙрд▓рдЯрд╛ рдХрд░рд╛ рдЖрдгрд┐ рджреЛрдиреНрд╣реА pass. тЬЕ рдЖрдзреА рдХреЛрдг рдЧреЗрд▓реЗ рдпрд╛рд╡рд░ pass рдХрд┐рдВрд╡рд╛ fail рд╣реЛрдгрд╛рд░реА test рд╣реА рд╡рд╛рдИрдЯ test рдЖрд╣реЗ.
рджреБрд░реБрд╕реНрддреА: рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрдирд╛рд╕рд╛рдареА рдирд╡реА sheet (setup), рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрдирд╛рдирдВрддрд░ sheet рдкрд░рдд рдШреЗрддрд▓реА рдЬрд╛рддреЗ (teardown), рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдиреЗ рд╢рд╛рдИ рд╕рд╛рдВрдбрд▓реА рддрд░реАрд╣реА. ЁЯз╣
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
b["ЁЯПЧя╕П a_student()<br/>Katrina ┬╖ avg 75.0 ┬╖ A ┬╖ promoted"] --> b1[".with_attendance(60)<br/>promoted False"]
b1 --> b2[".with_medical_note()<br/>promoted True"]
subgraph shared["ЁЯШм one shared sheet"]
s1["enrol then empty: 1 passed, 1 failed"]
s2["empty then enrol: 2 passed"]
end
subgraph fresh["тЬЕ fresh sheet per test"]
f1["setup тЖТ test тЖТ teardown<br/>2 passed in both orders"]
end
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрд╡реГрддреНрддреА + рдПрдХ lab: https://school-edh.pages.dev/testing/lesson-diagrams.html#l04
тЭУ рдХрд╛рдп
- Fixture тАФ test рдиреЗ act рдХрд░рдгреНрдпрд╛рдкреВрд░реНрд╡реА рдЬрд╛рдЧреЗрд╡рд░ рдЕрд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ рддреЗ рд╕рдЧрд│реЗ: objects, files, rows рдЕрд╕рд▓реЗрд▓рд╛ database. "Test fixture" рд╣реЗ xUnit рдордзрд▓реЗ рдирд╛рд╡ рдЖрд╣реЗ; pytest рд╕реБрджреНрдзрд╛ рдЖрдкрд▓реНрдпрд╛ setup functions рдирд╛ fixtures рдореНрд╣рдгрддреЗ.
- Setup / teardown тАФ рдкреНрд░рддреНрдпреЗрдХ test рдЪреНрдпрд╛ рдЖрдзреА рдЖрдгрд┐ рдирдВрддрд░ рдЪрд╛рд▓рдгрд╛рд░рд╛ code. Test fail рдЭрд╛рд▓реА рддрд░реА
teardown рдЪрд╛рд▓рд▓рд╛рдЪ рдкрд╛рд╣рд┐рдЬреЗ, рдирд╛рд╣реАрддрд░ рдкреБрдврдЪреНрдпрд╛ test рд▓рд╛ рдЧреЛрдВрдзрд│ рд╡рд╛рд░рд╕рд╛ рдореНрд╣рдгреВрди рдорд┐рд│рддреЛ.
unittestрдордзреНрдпреЗ:setUpрдЖрдгрд┐tearDown; pytest рдордзреНрдпреЗ:yieldрдЕрд╕рд▓реЗрд▓рд╛ fixture. - Test data builder тАФ рд╡реИрдз default object рдкрд░рдд рджреЗрдгрд╛рд░рд╛ рдорджрддрдиреАрд╕, рдПрдХ field рдмрджрд▓рдгреНрдпрд╛рдЪреНрдпрд╛
methods рд╕рд╣:
a_student().with_marks(maths=10). Nat Pryce рдЖрдгрд┐ Steve Freeman рдпрд╛рдВрдЪреНрдпрд╛ Growing Object-Oriented Software, Guided by Tests рдордзреВрди. Test рдлрдХреНрдд рддрд┐рдЪреНрдпрд╛рд╕рд╛рдареА рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ рддреЗрдЪ рд╕рд╛рдВрдЧрддреЗ. - Object mother тАФ рдирд╛рд╡реЗ рджрд┐рд▓реЗрд▓реНрдпрд╛ рддрдпрд╛рд░ objects рдЪрд╛ рд╕рдВрдЪ (
failing_pupil(),top_pupil()). рд╕реЛрдкреЗ, рдкрдг рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рдХрд╛рд░рд╛рд╕рд╛рдареА рдПрдХ рдирд╡реЗ function рд╡рд╛рдврдд рдЬрд╛рддреЗ; builders рдЬрд╛рд╕реНрдд рдЪрд╛рдВрдЧрд▓реЗ рд╡рд╛рдврддрд╛рдд. - Test independence (isolation) тАФ рдкреНрд░рддреНрдпреЗрдХ test рдПрдХрдЯреА, рд╕рдЧрд│реНрдпрд╛рдВрд╕реЛрдмрдд, рдЖрдгрд┐ рдХреЛрдгрддреНрдпрд╛рд╣реА рдХреНрд░рдорд╛рдиреЗ pass рд╣реЛрддреЗ. Shared mutable state (рдПрдХ global list, рд╕рдЧрд│реНрдпрд╛ tests рд╕рд╛рдареА рдПрдХрдЪ database, disk рд╡рд░рдЪреА рдПрдХ file) рд╣реЗ рдореЛрдбрддреЗ.
- Scope тАФ fixture рдХрд┐рддреА рдХрд╛рд│ рдЬрдЧрддреЛ: рдкреНрд░рддреНрдпреЗрдХ test рд╕рд╛рдареА (рд╕рд░реНрд╡рд╛рдд рд╕реБрд░рдХреНрд╖рд┐рдд), рдкреНрд░рддреНрдпреЗрдХ class, рдкреНрд░рддреНрдпреЗрдХ module, рдкреНрд░рддреНрдпреЗрдХ session (рд╕рд░реНрд╡рд╛рдд рд╡реЗрдЧрд╡рд╛рди, рдЬреНрдпрд╛ рдорд╣рд╛рдЧ рдЧреЛрд╖реНрдЯреА tests рдлрдХреНрдд рд╡рд╛рдЪрддрд╛рдд рддреНрдпрд╛рдВрдЪреНрдпрд╛рд╕рд╛рдареА).
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг рдмрд╣реБрддреЗрдХ test suites рдордзрд▓рд╛ рдмрд╣реБрддрд╛рдВрд╢ code рдореНрд╣рдгрдЬреЗ test data рдЕрд╕рддреЛ. рдкреНрд░рддреНрдпреЗрдХ test 12 fields рд╕рд╣ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА рд╣рд╛рддрд╛рдиреЗ рдмрдирд╡рдд рдЕрд╕реЗрд▓, рддрд░ рдПрдХ рдирд╡реЗ required field 300 tests рдореЛрдбрддреЗ, рдЖрдгрд┐ test рдХреЛрдгрддреНрдпрд╛ field рдмрджреНрджрд▓ рдЖрд╣реЗ рддреЗ рдХреЛрдгрд╛рд▓рд╛рдЪ рджрд┐рд╕рдд рдирд╛рд╣реА. рдЖрдгрд┐ shared state рдореБрд│реЗ рдХреНрд░рдорд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди failures рдпреЗрддрд╛рдд тАФ рддреНрдпрд╛ рддреБрдордЪреНрдпрд╛ laptop рд╡рд░ pass рд╣реЛрддрд╛рдд рдЖрдгрд┐ CI рдордзреНрдпреЗ fail, рдЬреЗ tests рд╡реЗрдЧрд│реНрдпрд╛ рдХреНрд░рдорд╛рдиреЗ рдХрд┐рдВрд╡рд╛ parallel рдордзреНрдпреЗ рдЪрд╛рд▓рд╡рддреЗ (рдзрдбрд╛ 11).
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
exam/lab.py рдордзрд▓рд╛ StudentBuilder рдХрддрд░рд┐рдирд╛рдкрд╛рд╕реВрди рд╕реБрд░реВ рд╣реЛрддреЛ (maths 80,
science 70, english 75, attendance 90). named(), with_marks(**m), with_attendance()
рдЖрдгрд┐ with_medical_note() рдкреНрд░рддреНрдпреЗрдХреА рдПрдХ рдЧреЛрд╖реНрдЯ рдмрджрд▓рддрд╛рдд рдЖрдгрд┐ builder рдкрд░рдд рджреЗрддрд╛рдд; card() рдЦрд░реЗ
report_card() рдмреЛрд▓рд╡рддреЗ. Runner.run(setup=тАж, teardown=тАж) рдкреНрд░рддреНрдпреЗрдХ test рдЖрдзреА setup() рдмреЛрд▓рд╡рддреЛ,
рддреНрдпрд╛рдЪрд╛ рдирд┐рдХрд╛рд▓ test рд▓рд╛ рджреЗрддреЛ, рдЖрдгрд┐ finally: block рдордзреНрдпреЗ teardown(fixture) рдмреЛрд▓рд╡рддреЛ.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 exam/demo.py fixtures
python3 - <<'EOF'
from exam.lab import a_student, Runner, expect, summary
print(a_student().card())
print(a_student().with_marks(maths=10).card()["grade"], a_student().with_marks(maths=10, science=10).card()["grade"])
log = []
def setup(): log.append("setup"); return {"store": [], "open": True}
def teardown(fx): fx["open"] = False; log.append("teardown")
def saves_one(fx): fx["store"].append("Katrina"); expect(len(fx["store"]), 1)
def crashes(fx): fx["store"].append("Dipika"); raise RuntimeError("printer jammed")
def starts_empty(fx): expect(fx["store"], [])
r = Runner(); r.tests = [saves_one, crashes, starts_empty]
for n, o in r.run(setup=setup, teardown=teardown): print(f"{n:<13} {o}")
print(log)
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
fixtures рдЫрд╛рдкрддреЗ:
тФАтФА a_student() тАФ a prepared answer sheet with sensible defaults тЖТ Katrina ┬╖ average 75.0 ┬╖ A ┬╖ promoted True
.with_attendance(60) тЖТ promoted False ┬╖ + .with_medical_note() тЖТ promoted True
.named('Dipika').with_marks(maths=20, science=30, english=40) тЖТ average 30.0 ┬╖ F
тФАтФА one SHARED answer sheet: order enrolтЖТempty: 1 passed, 1 failed ┬╖ order emptyтЖТenrol: 2 passed, 0 failed
a FRESH sheet per test (setup): 2 passed, 0 failed ┬╖ reversed: 2 passed, 0 failed ┬╖ teardown ran 2 times
рддреБрдордЪрд╛ snippet рдЫрд╛рдкрддреЛ:
{'name': 'Katrina', 'average': 75.0, 'grade': 'A', 'attendance': 90, 'promoted': True}
C F
saves_one pass
crashes ERROR тАФ RuntimeError: printer jammed
starts_empty pass
['setup', 'teardown', 'setup', 'teardown', 'setup', 'teardown']
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
Builder рдиреЗ рдкреНрд░рддреНрдпреЗрдХ рдУрд│реАрдд рдПрдХрдЪ рдмрджрд▓ рдХреЗрд▓рд╛ рдЖрдгрд┐ рдмрд╛рдХреА рдХрд╛рд╣реАрдЪ рд╣рд▓рд▓реЗ рдирд╛рд╣реА: maths рдордзрд▓рд╛ рдПрдХрдЪ 10 рдХрддрд░рд┐рдирд╛рд▓рд╛
C рд╡рд░ рдЖрдгрддреЛ, рджреЛрди рддрд┐рд▓рд╛ F рд╡рд░ рдЖрдгрддрд╛рдд. Shared sheet рд╕рд╣, рдирд┐рдХрд╛рд▓ рдХреНрд░рдорд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рд╣реЛрддрд╛
(1 failed рд╡рд┐рд░реБрджреНрдз 0 failed). рдкреНрд░рддреНрдпреЗрдХ test рд▓рд╛ рдирд╡реА sheet рдЕрд╕рддрд╛рдирд╛, crashes рдиреЗ рджреАрдкрд┐рдХрд╛рдЪреЗ рдирд╛рд╡ рд▓рд┐рд╣реВрди
рдЙрдбреА рдорд╛рд░рд▓реНрдпрд╛рдирдВрддрд░ рд▓рдЧреЗрдЪрд╣реА starts_empty pass рдЭрд╛рд▓реА тАФ рдЖрдгрд┐ log рдордзреНрдпреЗ рддреАрди setups рдЖрдгрд┐ рддреАрди
teardowns рджрд┐рд╕рддрд╛рдд, рдореНрд╣рдгрдЬреЗ crash рдЭрд╛рд▓реЗрд▓реНрдпрд╛ test рдирдВрддрд░рд╣реА рд╕рд╛рдлрд╕рдлрд╛рдИ рдЭрд╛рд▓реА.
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
- рдПрдХрдЪ рдореЛрдард╛ shared fixture рдЬреНрдпрд╛рдд рдкреНрд░рддреНрдпреЗрдХ test рдереЛрдбрд╛ рдмрджрд▓ рдХрд░рддреЗ
- рдкреНрд░рддреНрдпреЗрдХ field рд▓рд┐рд╣реВрди test data рдмрдирд╡рдгреЗ, рдореНрд╣рдгрдЬреЗ test рдХрд╢рд╛рдмрджреНрджрд▓ рдЖрд╣реЗ рддреЗ рдХреЛрдгрд╛рд▓рд╛рдЪ рджрд┐рд╕рдд рдирд╛рд╣реА
- test body рдЪреНрдпрд╛ рд╢реЗрд╡рдЯреА cleanup тАФ test fail рдЭрд╛рд▓реА рдХреА рддреЛ рдХрдзреАрдЪ рдЪрд╛рд▓рдд рдирд╛рд╣реА
- session-scoped database рдЬреНрдпрд╛рдд tests рд▓рд┐рд╣рд┐рддрд╛рдд
- seed рд╢рд┐рд╡рд╛рдп random рдХрд┐рдВрд╡рд╛ рд╡реЗрд│реЗрд╡рд░ рдЖрдзрд╛рд░рд┐рдд test data (рдзрдбрд╛ 11)
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ project рд╡рд░ тАФ yield рдирдВрддрд░ teardown рдЕрд╕рд▓реЗрд▓рд╛ pytest fixture; test fail рдЭрд╛рд▓реА рддрд░реА pytest
yield рдирдВрддрд░рдЪрд╛ рднрд╛рдЧ рдЪрд╛рд▓рд╡рддреЗ:
import pytest
from exam.grades import GradeStore
@pytest.fixture
def store():
s = GradeStore(":memory:") # setup: a fresh database for THIS test
yield s
s.db.close() # teardown
def test_average(store):
store.save("Katrina", "maths", 68)
assert store.class_average("maths") == 68
рддреБрдореНрд╣рд╛рд▓рд╛ рднреЗрдЯрддреАрд▓ рдЕрд╕реЗ pytest рдЪреЗ built-in fixtures: tmp_path (рдкреНрд░рддреНрдпреЗрдХ test рд╕рд╛рдареА рдирд╡реА temporary directory),
monkeypatch (environment variable рдХрд┐рдВрд╡рд╛ attribute set рдХрд░рд╛, test рдирдВрддрд░ рдкрд░рдд рдкреВрд░реНрд╡рд╡рдд),
capsys (рдЫрд╛рдкрд▓реЗрд▓рд╛ output рдкрдХрдбрд╛). рд╣рд│реВ, read-only fixture рд▓рд╛ рдЬрд╛рд╕реНрдд рдореЛрдард╛ scope рджреЗрддрд╛ рдпреЗрддреЛ:
@pytest.fixture(scope="session")
def reference_marks():
return load_big_csv("marks-2025.csv") # loaded once, never modified
рдЗрддрд░ ecosystems: рд╡рд╛рд╕реНрддрд╡рд╡рд╛рджреА рдХрд┐рдорддреА рдЕрд╕рд▓реЗрд▓реНрдпрд╛ builders рд╕рд╛рдареА factory_boy (Python) рдЖрдгрд┐ Faker;
JUnit 5 @BeforeEach / @AfterEach; Jest beforeEach / afterEach.
ЁЯПн Production рдордзреНрдпреЗ рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдЕрдзреВрдирдордзреВрди рддреБрдордЪреА suite random рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рд╡рд╛ (
pytest-randomlyplugin рд╣реЗ рдкреНрд░рддреНрдпреЗрдХ run рд▓рд╛ рдХрд░рддреЗ). рдлрдХреНрдд рдХрд╛рд╣реА рдХреНрд░рдорд╛рдВрдордзреНрдпреЗрдЪ fail рд╣реЛрдгрд╛рд░реА рдХреЛрдгрддреАрд╣реА test state рдЧрд│рд╡рдд рдЖрд╣реЗ тАФ CI рд░рд╛рддреНрд░реА 2 рд╡рд╛рдЬрддрд╛ рддреБрдордЪреНрдпрд╛рд╕рд╛рдареА рддреА рд╢реЛрдзрдгреНрдпрд╛рдкреВрд░реНрд╡реА рддреБрдореНрд╣реАрдЪ рд╢реЛрдзрд╛.
тПня╕П рдкреБрдвреЗ
рдХрд╛рд╣реА рдЧреЛрд╖реНрдЯреА test рд▓рд╛ рдЦрд▒реНрдпрд╛ рд╕реНрд╡рд░реВрдкрд╛рдд рд╕рд╣рдЬ рд╡рд╛рдкрд░рддрд╛ рдпреЗрдд рдирд╛рд╣реАрдд: attendance service рджреБрд╕рд▒реНрдпрд╛ server рд╡рд░ рдЖрд╣реЗ. рдмрджрд▓реА рдкрд░реАрдХреНрд╖рдХ тАФ stubs, fakes, spies рдЖрдгрд┐ mocks тАФ рддрд┐рдЪреА рдЬрд╛рдЧрд╛ рдШреЗрддрд╛рдд.
git checkout lesson-05-test-doubles