ЁЯХ░я╕П рдзрдбрд╛ 11 тАФ Flaky tests: рднрд┐рдВрддреАрд╡рд░рдЪреЗ рдбреБрдЧрдбреБрдЧрдгрд╛рд░реЗ рдШрдбреНрдпрд╛рд│
ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 11 ┬╖ рдорд╛рдЧреАрд▓: lesson-10-coverage-mutation ┬╖ рдкреБрдвреАрд▓: lesson-12-ci-tdd
ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ
рдзрдбреЗ 01тАУ10, рдЖрдгрд┐ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп flaky tests: рддреНрдпрд╛рдЪ code рд╡рд░ рдХрдзреА pass рддрд░ рдХрдзреА fail рд╣реЛрдгрд╛рд▒реНрдпрд╛
tests. рдЪрд╛рд░ рдиреЗрд╣рдореАрдЪреА рдХрд╛рд░рдгреЗ тАФ рд╡реЗрд│, рдХреНрд░рдо (shared state), randomness рдЖрдгрд┐ рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ тАФ
рдкреНрд░рддреНрдпреЗрдХ рдХрд╛рд░рдг рдХрд╛рд╣реАрд╣реА рди рдмрджрд▓рддрд╛ test 1000 рд╡реЗрд│рд╛ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡реВрди рджрд╛рдЦрд╡рд▓реЗрд▓реЗ, рдЖрдгрд┐ рдордЧ рдмрджрд▓рдгрд╛рд░реА рдЧреЛрд╖реНрдЯ рдкрдХреНрдХреА
рдХрд░реВрди рд╕реБрдзрд╛рд░рд▓реЗрд▓реЗ. flaky_time, fixed_time, order_runs, flaky_random, flaky_wait рдЖрдгрд┐
rerun exam/lab.py рдордзреНрдпреЗ; is_late() exam/grades.py
рдордзреНрдпреЗ; flaky() exam/demo.py рдордзреНрдпреЗ.
ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ
рдкрд░реАрдХреНрд╖рд╛ рд╣реЙрд▓рдордзреНрдпреЗ рднрд┐рдВрддреАрд╡рд░ рдПрдХ рдШрдбреНрдпрд╛рд│ рдЖрд╣реЗ рдЬреЗ рдбреБрдЧрдбреБрдЧрддреЗ. ЁЯХ░я╕П рдмрд╣реБрддреЗрдХ рджрд┐рд╡рд╕ рддреЗ рдмрд░реЛрдмрд░ рдЕрд╕рддреЗ. рдХрд╛рд╣реА рджрд┐рд╡рд╕ рддреЗ рдЙрдбреА рдорд╛рд░рддреЗ. рд▓рд┐рд╣рд┐рдгреЗ рдХрдзреА рдерд╛рдВрдмрд╡рд╛рдпрдЪреЗ рд╣реЗ рдард░рд╡рдгреНрдпрд╛рд╕рд╛рдареА рдШрдбреНрдпрд╛рд│рд╛рдХрдбреЗ рдкрд╛рд╣рдгрд╛рд▒реНрдпрд╛ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреЗ рдХрдзреА рдХрдзреА рдЪреБрдХрддреЗ тАФ рддрд┐рдЪреНрдпрд╛ рдЙрддреНрддрд░рд╛рдВрдореБрд│реЗ рдирд╡реНрд╣реЗ, рддрд░ рдШрдбреНрдпрд╛рд│рд╛рдореБрд│реЗ.
Flaky test рдЕрдЧрджреА рддрд╕рд╛рдЪ рдЕрд╕рддреЛ. Code рдмрджрд▓рд▓рд╛ рдирд╛рд╣реА, рдкрдг test рдЪрд╛ рдирд┐рдХрд╛рд▓ рдмрджрд▓рд▓рд╛.
рдкрд░реНрдпрд╡реЗрдХреНрд╖рдг рдХрд░рдгрд╛рд▒реНрдпрд╛ рдРрд╢реНрд╡рд░реНрдпрд╛рд▓рд╛ рдЪрд╛рд░ рдбреБрдЧрдбреБрдЧрдгрд╛рд░реА рдШрдбреНрдпрд╛рд│реЗ рд╕рд╛рдкрдбрддрд╛рдд:
- рд╡реЗрд│ тП░ тАФ рдПрдХ рдкреНрд░рд╢реНрди рддрдкрд╛рд╕рддреЛ "рддрд╛рд╕рд╛рднрд░рд╛рдд рджрд┐рд▓реЗрд▓рд╛ рдЧреГрд╣рдкрд╛рда рдЙрд╢рд┐рд░рд╛ рдирд╛рд╣реА". рддреЛ рджрд┐рд╡рд╕рднрд░ pass рд╣реЛрддреЛтАж рдЖрдгрд┐ 23:00 рдирдВрддрд░ fail рд╣реЛрддреЛ, рдЬреЗрд╡реНрд╣рд╛ "рддрд╛рд╕рд╛рднрд░рд╛рдд" рдореНрд╣рдгрдЬреЗ рдЙрджреНрдпрд╛ рдЕрд╕рддреЗ.
- рдХреНрд░рдо ЁЯФА тАФ рддреАрди рдкреНрд░рд╢реНрди рдПрдХрдЪ рд╡рд░реНрдЧ-рдпрд╛рджреА рд╡рд╛рдкрд░рддрд╛рдд. рдПрдХрд╛ рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рд╡рд▓реЗ рддрд░ рд╕рдЧрд│реЗ pass. рдЗрддрд░ 5 рдкреИрдХреА рдХреЛрдгрддреНрдпрд╛рд╣реА рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рд╡рд▓реЗ тАФ рдХрд╛рд╣реАрддрд░реА fail рд╣реЛрддреЗ.
- Randomness ЁЯО▓ тАФ рдПрдХ рдкреНрд░рд╢реНрди 3 random рдЧреБрдг рдирд┐рд╡рдбрддреЛ рдЖрдгрд┐ рдХрд┐рдорд╛рди рдПрдХ pass рдЕрдкреЗрдХреНрд╖рд┐рдд рдзрд░рддреЛ. рд╕рд╣рд╕рд╛ рдЦрд░реЗ. рдХрдзреА рдХрдзреА рддрд┐рдиреНрд╣реА 35 рдЪреНрдпрд╛ рдЦрд╛рд▓реА рдЕрд╕рддрд╛рдд.
- рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ тП│ тАФ рдПрдХ рдкреНрд░рд╢реНрди report card рдЫрд╛рдкрд╛рдпрд▓рд╛ рд╕реБрд░реВ рдХрд░рддреЛ, 200 ms рдерд╛рдВрдмрддреЛ, рдордЧ рддреЗ рдЭрд╛рд▓реЗ рдХрд╛ рддрдкрд╛рд╕рддреЛ. рд╕рд╣рд╕рд╛ рдЭрд╛рд▓реЗрд▓реЗ рдЕрд╕рддреЗ. рдХрдзреА рдХрдзреА printer рд╣рд│реВ рдЕрд╕рддреЛ.
рдРрд╢реНрд╡рд░реНрдпрд╛рдЪреА рдкрджреНрдзрдд: рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрди рдХрд╛рд╣реАрд╣реА рди рдмрджрд▓рддрд╛ 1000 рд╡реЗрд│рд╛ рдЪрд╛рд▓рд╡рд╛. рдЬреЛ рдкреНрд░рд╢реНрди рдХрдзреА pass рдЖрдгрд┐ рдХрдзреА fail рд╣реЛрддреЛ рддреЛ flaky. рдордЧ рддреА рдХрд╛рдп рдмрджрд▓рддреЗ рддреЗ рд╢реЛрдзрддреЗ тАФ рдЖрдгрд┐ рддреЗ рдкрдХреНрдХреЗ рдХрд░рддреЗ: рдард░рд▓реЗрд▓реЗ рдШрдбреНрдпрд╛рд│, рдирд╡реА рдпрд╛рджреА, рдард░рд▓реЗрд▓рд╛ seed, "рдкреВрд░реНрдг рд╣реЛрдИрдкрд░реНрдпрдВрдд рд╡рд╛рдЯ рдкрд╛рд╣рд╛".
ЁЯЧ║я╕П рдЖрдХреГрддреА
flowchart LR
r["ЁЯФБ rerun 1000 times<br/>no code change"] --> t["тП░ time: 33 failed<br/>after 23:00"]
r --> o["ЁЯФА order: 5 of 6 orders fail"]
r --> d["ЁЯО▓ randomness: 36 failed"]
r --> w["тП│ sleep 200 ms: 76 failed"]
t --> f1["inject a fixed clock тЖТ 0"]
o --> f2["fresh fixture per test тЖТ 0"]
d --> f3["fixed seed or fixed data"]
w --> f4["wait for done, not for time"]
ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/testing/lesson-diagrams.html#l11
тЭУ рдХрд╛рдп
- Flaky test тАФ non-deterministic рдирд┐рдХрд╛рд▓ рдЕрд╕рд▓реЗрд▓реА test: рддреЛрдЪ code, рддреАрдЪ test, рд╡реЗрдЧрд│рд╛ рдирд┐рдХрд╛рд▓. Google рдиреЗ (2016) рд╕рд╛рдВрдЧрд┐рддрд▓реЗ рдХреА рддреНрдпрд╛рдВрдЪреНрдпрд╛ рд╕реБрдорд╛рд░реЗ 1.5% test runs рдЪрд╛ рдирд┐рдХрд╛рд▓ flaky рд╣реЛрддрд╛ рдЖрдгрд┐ рдЬрд╡рд│рдкрд╛рд╕ 16% tests рдордзреНрдпреЗ рдХрд╛рд╣реА рдирд╛ рдХрд╛рд╣реА flakiness рджрд┐рд╕рд▓реА.
- рд╡реЗрд│ тАФ рдЦрд░реЗ рдШрдбреНрдпрд╛рд│ рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛ tests, time zones, daylight-saving рдмрджрд▓, рдорд╣рд┐рдиреНрдпрд╛рдЪрд╛ рдХрд┐рдВрд╡рд╛ рд╡рд░реНрд╖рд╛рдЪрд╛
рд╢реЗрд╡рдЯ. рдЙрдкрд╛рдп: рдШрдбреНрдпрд╛рд│ inject рдХрд░рд╛ (
nowрдЖрдд рдкрд╛рдард╡рд╛) рдХрд┐рдВрд╡рд╛ рддреЗ рдЧреЛрдард╡рд╛. - рдХреНрд░рдорд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рдгреЗ тАФ рджреБрд╕рд▒реНрдпрд╛ test рдиреЗ рдорд╛рдЧреЗ рд╕реЛрдбрд▓реЗрд▓реНрдпрд╛ state рд╡рд░ test рдЕрд╡рд▓рдВрдмреВрди рдЕрд╕рддреЗ (global list, database row, file, environment variable). рдЙрдкрд╛рдп: рдкреНрд░рддреНрдпреЗрдХ test рд▓рд╛ рдирд╡рд╛ fixture (рдзрдбрд╛ 04); random рдХреНрд░рдорд╛рдиреЗ рдЪрд╛рд▓рд╡реВрди рддреЗ рд╢реЛрдзрд╛.
- Randomness тАФ seed рдирд╕рд▓реЗрд▓рд╛ random data. рдЙрдкрд╛рдп: рдард░рд▓реЗрд▓рд╛ seed, рдард░рд▓реЗрд▓рд╛ data, рдХрд┐рдВрд╡рд╛ рд╕рд░реНрд╡ inputs рд╕рд╛рдареА рдЦрд░реА рдЕрд╕рдгрд╛рд░реА property (рдзрдбрд╛ 09) рдЖрдгрд┐ seed рдЫрд╛рдкрд▓реЗрд▓рд╛.
- рд╡рд╛рдЯ рдкрд╛рд╣рдгреЗ / concurrency тАФ background рдХрд╛рдо рд╕рдВрдкрд▓реЗ рдЕрд╕реЗрд▓ рдпрд╛ рдЖрд╢реЗрдиреЗ рдард░рд╛рд╡реАрдХ
sleep, threads рдордзреАрд▓ races, рдЖрдзреАрдЪ рд╡рд╛рдкрд░рд╛рдд рдЕрд╕рд▓реЗрд▓реЗ ports. рдЙрдкрд╛рдп: рдЙрджрд╛рд░ deadline рдареЗрд╡реВрди рдЕрдЯреАрдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рд╛, рдХрд┐рдВрд╡рд╛ test рдордзреНрдпреЗ рдХрд╛рдо synchronous рдХрд░рд╛. - рдмрд╛рд╣реЗрд░рдЪреНрдпрд╛ dependencies тАФ network, shared staging server, рджреБрд╕рд▒реНрдпрд╛ team рдЪрд╛ data. рдЙрдкрд╛рдп: рдмрд╣реБрддреЗрдХ tests рд╕рд╛рдареА doubles (рдзрдбрд╛ 05) рдЖрдгрд┐ contracts (рдзрдбрд╛ 07).
- Retry-to-green тАФ fail рдЭрд╛рд▓реЗрд▓реНрдпрд╛ tests pass рд╣реЛрдИрдкрд░реНрдпрдВрдд рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рдгреЗ. рдпрд╛рдореБрд│реЗ flake рд▓рдкрддреЛ; product рдордзрд▓рд╛ рдЦрд░рд╛ рдЕрдзреВрдирдордзреВрди рдпреЗрдгрд╛рд░рд╛ bug рд╕реБрджреНрдзрд╛ рд▓рдкреВ рд╢рдХрддреЛ. рдЙрдкрд╛рдп рдореНрд╣рдгреВрди рдирд╡реНрд╣реЗ, рддрд░ ticket рд╕рд╣ рдереЛрдбреНрдпрд╛ рдХрд╛рд│рд╛рдЪреЗ quarantine рдореНрд╣рдгреВрди рд╡рд╛рдкрд░рд╛.
ЁЯдФ рдХрд╛
рдХрд╛рд░рдг flaky suite рд▓реЛрдХрд╛рдВрдирд╛ рд▓рд╛рд▓ рд░рдВрдЧрд╛рдХрдбреЗ рджреБрд░реНрд▓рдХреНрд╖ рдХрд░рд╛рдпрд▓рд╛ рд╢рд┐рдХрд╡рддреЛ. рдПрдХрджрд╛ "рдлрдХреНрдд рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡" рд╣реЗ рдиреЗрд╣рдореАрдЪреЗ рдЙрддреНрддрд░ рдЭрд╛рд▓реЗ, рдХреА рдЦрд░реЗ failure рд╕реБрджреНрдзрд╛ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рд▓реЗ рдЬрд╛рддреЗ тАФ рдЖрдгрд┐ ship рд╣реЛрддреЗ. Flakiness рд╡реЗрд│рд╣реА рд╡рд╛рдпрд╛ рдШрд╛рд▓рд╡рддреЗ: рдзрдбрд╛ 08 рдЪреНрдпрд╛ model рдордзреНрдпреЗ 200 e2e tests рдЕрд╕рд▓реЗрд▓рд╛ suite 87% runs рдордзреНрдпреЗ рд╡рд┐рдирд╛рдХрд╛рд░рдг рд▓рд╛рд▓ рд╣реЛрддрд╛. рдЖрдгрд┐ flaky test рд╣реЗ рдХрдзреА рдХрдзреА flaky product рдЪреЗ рдПрдХрдореЗрд╡ рд▓рдХреНрд╖рдг рдЕрд╕рддреЗ тАФ рдЦрд░реА race condition, рдЦрд░рд╛ time-zone bug тАФ рдореНрд╣рдгреВрди рдкреНрд░рддреНрдпреЗрдХ flake рдХрдбреЗ рдПрдХрджрд╛ рдкрд╛рд╣рд╛рдпрд▓рд╛рдЪ рд╣рд╡реЗ.
ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)
exam/lab.py рдордзреАрд▓ рдкреНрд░рддреНрдпреЗрдХ flaky test рдмрджрд▓рдгрд╛рд░реА рдЧреЛрд╖реНрдЯ parameter рдореНрд╣рдгреВрди рдШреЗрддреЗ,
рдореНрд╣рдгрдЬреЗ рддреА рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рддрд╛ рдпреЗрддреЗ. flaky_time(clock) due date рд╕рд╛рдареА clock() рдмреЛрд▓рд╡рддреЗ рдЖрдгрд┐ рдкреБрдиреНрд╣рд╛
hand-in рд╡реЗрд│ рдЕрдзрд┐рдХ рдПрдХ рддрд╛рд╕ рдпрд╛рд╕рд╛рдареА; exam/grades.py рдордзреАрд▓ is_late() рдореНрд╣рдгрддреЗ
рдЙрд╢реАрд░ рдореНрд╣рдгрдЬреЗ "due date рдирдВрддрд░рдЪреНрдпрд╛ рдордзреНрдпрд░рд╛рддреНрд░реА рдХрд┐рдВрд╡рд╛ рддреНрдпрд╛рдирдВрддрд░". order_runs(fresh) module-level ROLL
list рд╡рд╛рдкрд░рдгрд╛рд▒реНрдпрд╛ рддреАрди tests рд╕рд░реНрд╡ 6 рдХреНрд░рдорд╛рдВрдиреА рдЪрд╛рд▓рд╡рддреЗ. flaky_random(rng) 3 рдЧреБрдг рдХрд╛рдврддреЗ; flaky_wait(rng)
200 ms sleep рд╡рд┐рд░реБрджреНрдз рдПрдХ job рд╡реЗрд│ рдХрд╛рдврддреЗ (90% рд╡реЗрд│рд╛ 50 рддреЗ 150 ms, 10% рд╡реЗрд│рд╛ 150 рддреЗ 400 ms).
rerun(test, n) test n рд╡реЗрд│рд╛ рдЪрд╛рд▓рд╡рддреЗ рдЖрдгрд┐ passes рдЖрдгрд┐ fails рдореЛрдЬрддреЗ. рдЦрд░реЗ рдШрдбреНрдпрд╛рд│ рдЖрдгрд┐ рдЦрд░реА randomness
рдпрд╛рдВрдЪреА рднреВрдорд┐рдХрд╛ demo рдордзреНрдпреЗ seeded generators рдХрд░рддрд╛рдд, рдореНрд╣рдгреВрди рддреНрдпрд╛рдЪреЗ рдЖрдХрдбреЗ рдкреНрд░рддреНрдпреЗрдХ run рдордзреНрдпреЗ рддреЗрдЪ рдпреЗрддрд╛рдд.
ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛
python3 exam/demo.py flaky
python3 - <<'EOF'
from datetime import datetime
from exam import lab
from exam.lab import Rng, rerun
for hour in (9, 22, 23):
t = datetime(2026, 9, 1, hour, 30)
print(f"flaky_time at {hour}:30 тЖТ", "pass" if lab.flaky_time(lambda: t) else "FAIL")
print("fixed_time at any hour тЖТ", "pass" if lab.fixed_time(None) else "FAIL")
print("same seed, 3 runs:", [lab.flaky_random(Rng(42)) for _ in range(3)])
print("fixed data, v3 of the test:", [any(lab.G.grade(s) != "F" for s in (20, 34, 50)) for _ in range(3)])
waits = Rng(13); p, f = rerun(lambda i: lab.flaky_wait(waits))
print(f"sleep 200 ms: passed {p}, failed {f}")
print("order: shared", lab.order_runs(False), "of 6 orders fail ┬╖ fresh", lab.order_runs(True))
EOF
тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ
flaky рд╣реЗ рдЫрд╛рдкрддреЗ:
time: due today, handed in within the hour passed 967 ┬╖ failed 33 тЖТ reads the real clock (fails after 23:00)
randomness: 3 unseeded scores, one passes passed 964 ┬╖ failed 36 тЖТ unseeded random data
waiting: sleep 200 ms, then check the job passed 924 ┬╖ failed 76 тЖТ a fixed sleep instead of waiting for done
order: shared ROLL list, all 6 orders of 3 tests тЖТ 5 orders fail ┬╖ fresh ROLL per test тЖТ 0 fail
тФАтФА fixed: an injected clock тЖТ passed 1000, failed 0 ┬╖ a seed тЖТ the same 3 scores every run ┬╖ wait for 'done', not for time
рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:
flaky_time at 9:30 тЖТ pass
flaky_time at 22:30 тЖТ pass
flaky_time at 23:30 тЖТ FAIL
fixed_time at any hour тЖТ pass
same seed, 3 runs: [True, True, True]
fixed data, v3 of the test: [True, True, True]
sleep 200 ms: passed 924, failed 76
order: shared 5 of 6 orders fail ┬╖ fresh 0
ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ
рдЪрд╛рд░рдкреИрдХреА рдкреНрд░рддреНрдпреЗрдХ test рдкрд╣рд┐рд▓реНрдпрд╛рдЪ рдкреНрд░рдпрддреНрдирд╛рдд pass рдЭрд╛рд▓реА рдЕрд╕рддреА тАФ failure rates рдкреНрд░рддреНрдпреЗрдХ run рдорд╛рдЧреЗ 3тАУ8%
рд╣реЛрддреЗ, рдХрд┐рдВрд╡рд╛ 6 рдкреИрдХреА рдЕрд╕реЗ 5 рдХреНрд░рдо рдЬреЗ runner рдЖрдЬ рдпреЛрдЧрд╛рдпреЛрдЧрд╛рдиреЗ рд╡рд╛рдкрд░рдд рдирд╛рд╣реА. рдлрдХреНрдд рдХрд╛рд╣реАрд╣реА рди рдмрджрд▓рддрд╛ рдкреБрдиреНрд╣рд╛
рдЪрд╛рд▓рд╡рд▓реНрдпрд╛рдиреЗ рддреЗ рдЙрдШрдб рдЭрд╛рд▓реЗ. Demo рдордзреНрдпреЗ time test рд╕реБрдорд╛рд░реЗ 30 рдкреИрдХреА 1 run рдордзреНрдпреЗ fail рдЭрд╛рд▓реА, "23:00 рдирдВрддрд░"
рд╡рд░реВрди рдЕрдкреЗрдХреНрд╖рд┐рдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ 24 рдкреИрдХреА 1 рдЪреНрдпрд╛ рдЬрд╡рд│. рдмрджрд▓рдгрд╛рд░реА рдЧреЛрд╖реНрдЯ рдПрдХрджрд╛ parameter рдЭрд╛рд▓реА тАФ рдШрдбреНрдпрд╛рд│,
рдпрд╛рджреА, seed тАФ рдХреА test рд╣рд╡реА рддреЗрд╡реНрд╣рд╛ 23:30 рд▓рд╛ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рддрд╛ рдЖрд▓реА, рдЖрдгрд┐ рд╕реБрдзрд╛рд░рддрд╛ рдЖрд▓реА. Seeded random
test рдХрдбреЗ рд▓рдХреНрд╖ рджреНрдпрд╛: seed рддрд┐рд▓рд╛ рдкреБрдиреНрд╣рд╛-рдкреБрдиреНрд╣рд╛ рд╕рд╛рд░рдЦреА рдХрд░рддреЛ, рдмрд░реЛрдмрд░ рдирд╛рд╣реА; рдкреНрд░рд╛рдорд╛рдгрд┐рдХ рдЙрдкрд╛рдп рд╣реЛрддрд╛
рдореБрджреНрджрд╛рдо рдирд┐рд╡рдбрд▓реЗрд▓рд╛ data (20, 34, 50).
тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛
time.sleep(0.2)рдЖрдгрд┐ рдЖрд╢рд╛ тАФ рддреНрдпрд╛рдРрд╡рдЬреА рдЕрдЯреАрдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рд╛- code рдордзреНрдпреЗ рдЦреЛрд▓рд╡рд░
datetime.now(), рдЬрд┐рдереЗ рдХреЛрдгрддреАрд╣реА test рддреЗ рдирд┐рдпрдВрддреНрд░рд┐рдд рдХрд░реВ рд╢рдХрдд рдирд╛рд╣реА - "fail рдЭрд╛рд▓реЗрд▓реНрдпрд╛ tests 3 рд╡реЗрд│рд╛ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рд╛" рд╣реЗ рдХрд╛рдпрдордЪреЗ setting рдореНрд╣рдгреВрди
- flaky test рдЪрд╛ assert delete рдХрд░реВрди рддреЛ "рд╕реБрдзрд╛рд░рдгреЗ"
- "рд╣реА рдлрдХреНрдд test рдЖрд╣реЗ" рдореНрд╣рдгреВрди product рдареАрдХ рдЖрд╣реЗ рдЕрд╕реЗ рдЧреГрд╣реАрдд рдзрд░рдгреЗ тАФ рдХрдзреА рдХрдзреА race рдЦрд░реА рдЕрд╕рддреЗ
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд
рдЦрд▒реНрдпрд╛ project рдордзреНрдпреЗ тАФ freezegun (рдХрд┐рдВрд╡рд╛ time-machine) рдиреЗ рд╡реЗрд│ рдЧреЛрдард╡рд╛, pytest-randomly рдиреЗ рдХреНрд░рдо random рдХрд░рд╛, рдЖрдгрд┐ рд╕рдВрд╢рдпрд┐рдд test pytest-repeat рдиреЗ рдкреБрдиреНрд╣рд╛ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рд╛:
from freezegun import freeze_time
from datetime import datetime, timedelta
from exam.grades import is_late
@freeze_time("2026-09-01 23:30:00")
def test_late_night_hand_in():
now = datetime.now() # frozen
assert is_late(now + timedelta(hours=1), now.date()) # the rule, stated for 23:30
pip install pytest-randomly pytest-repeat pytest-rerunfailures
pytest # with pytest-randomly installed: shuffled order, "Using --randomly-seed=тАж"
pytest --randomly-seed=1234 # replay the exact order that failed
pytest --count=200 -x tests/test_report.py # pytest-repeat: run one file 200 times, stop at the first failure
Quarantine рдордзрд▓реНрдпрд╛ рдорд╛рд╣реАрдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ flaky test рд╕рд╛рдареА, pytest-rerunfailures рдлрдХреНрдд рддреБрдореНрд╣реА mark рдХреЗрд▓реЗрд▓реЗрдЪ
рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рддреЗ тАФ рддреНрдпрд╛ рдПрдХрд╛ test рд╡рд░ @pytest.mark.flaky(reruns=2) тАФ рдЖрдгрд┐ рддреЗ mark рдХрд╛рдврдгреНрдпрд╛рд╕рд╛рдареА рдПрдХ ticket.
рдХрд╛рд▓рд╛рд╡рдзреАрдЪреА рдирд╡реНрд╣реЗ, рдЕрдЯреАрдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рд╛ тАФ Playwright рдЪреЗ expect(...).to_have_text(...) timeout рдкрд░реНрдпрдВрдд
рдкреБрдиреНрд╣рд╛ рдкреНрд░рдпрддреНрди рдХрд░рддреЗ; рд╕рд╛рдзреНрдпрд╛ code рдордзреНрдпреЗ, deadline рдареЗрд╡реВрди poll рдХрд░рд╛:
import time
def wait_until(cond, timeout=5.0, step=0.05):
end = time.monotonic() + timeout
while time.monotonic() < end:
if cond(): return True
time.sleep(step)
raise TimeoutError("condition not met")
ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: flaky tests bugs рд╕рд╛рд░рдЦреЗ track рдХрд░рд╛ тАФ рдорд╛рд▓рдХрд╛рдВрд╕рд╣ рдпрд╛рджреА. Flaky test рд▓рд╛ quarantine рдХрд░рд╛ (рддреА рдЪрд╛рд▓рддреЗ, рдкрдг merges рдЕрдбрд╡рдд рдирд╛рд╣реА) тАФ рдорд╣рд┐рдиреЗ рдирд╡реНрд╣реЗ, рджрд┐рд╡рд╕. рдкреНрд░рддреНрдпреЗрдХ failure рдЪрд╛ seed, рдХреНрд░рдо рдЖрдгрд┐ рд╡реЗрд│ рдиреЛрдВрджрд╡рд╛, рдореНрд╣рдгрдЬреЗ рддреЗ рдкреБрдиреНрд╣рд╛ рдЪрд╛рд▓рд╡рддрд╛ рдпреЗрдИрд▓.
тПня╕П рдкреБрдвреЗ
рд╕рдЧрд│реЗ рддреБрдХрдбреЗ рдЬрд╛рдЧреЗрд╡рд░ рдЖрд╣реЗрдд. рд╢реЗрд╡рдЯрдЪрд╛ рдзрдбрд╛: tests рдХрдзреА рдЪрд╛рд▓рддрд╛рдд тАФ TDD рддреНрдпрд╛ рдЖрдзреА рд▓рд┐рд╣рд┐рддреЗ, рдЖрдгрд┐ CI рдкреНрд░рддреНрдпреЗрдХ рдмрджрд▓рд╛рд╡рд░ рдпреЛрдЧреНрдп рддреНрдпрд╛ рдЪрд╛рд▓рд╡рддреЗ. рдЖрдгрд┐ рдкрд░реАрдХреНрд╖рд╛ рд╣реЙрд▓рдЪреЗ рд╕рдВрдкреВрд░реНрдг рдЪрд┐рддреНрд░.
git checkout lesson-12-ci-tdd