Chapterبەش 6 · Lectureوانە 6.1

Course Project: Ship Bazaar Cart 1.0.0پڕۆژەی خولەکە: ⁦Bazaar Cart 1.0.0⁩ بڵاو بکەرەوە

A guided capstone that puts every lesson of the course to work: six user stories with acceptance examples and MoSCoW, two sprints on a Kanban board, a Git repository with a feature branch and a resolved merge conflict, a layered design with MVP and dependency injection, tests written first for rules R1–R5, release 1.0.0 with a CHANGELOG, an annotated tag and a zipapp build, then a bug report with a regression test (1.0.1) and a new feature (1.1.0) — with checkpoints, a marking checklist and worked answers.

پڕۆژەیەکی کۆتایی بە ڕێنمایی کە هەموو وانەکانی خولەکە دەخاتە کار: شەش بە نموونەی پەسەندکردن و MoSCoW، دوو لەسەر تەختەی ، Git بە و چارەسەرکراو، دیزاینێکی چینچین بە MVP و ، تاقیکردنەوە لە پێشەوە بۆ یاساکانی ⁦R1–R5⁩، ⁦1.0.0⁩ بە CHANGELOG، تاگی annotated و بیلدی zipapp، پاشان ڕاپۆرتی بە ⁦(1.0.1)⁩ و تایبەتمەندییەکی نوێ ⁦(1.1.0)⁩ — لەگەڵ خاڵی پشکنین، لیستی نمرەدان و وەڵامی ڕوونکراوە.

21 min read19 خولەک خوێندنەوە · 21 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Turn a shop's needs into user stories with acceptance examples, non-functional requirements and a MoSCoW plan.
  • Keep a clean Git history with a feature branch, a resolved conflict and annotated release tags.
  • Build a layered Python app with MVP and dependency injection, tests first.
  • Release 1.0.0, a patch and a minor version with semantic versioning, a changelog and a zipapp build.
  • Handle a bug report with a regression test, and judge a project with a marking checklist and a retrospective.
  • پێویستییەکانی دوکانێک بکەیتە بە نموونەی پەسەندکردن، و پلانێکی MoSCoW.
  • مێژوویەکی پاکی Git بهێڵیتەوە بە ، ناکۆکییەکی چارەسەرکراو و تاگی annotated بۆ .
  • Pythonـی چینچین بە MVP و دروست بکەیت، تاقیکردنەوە لە پێشەوە.
  • ⁦1.0.0⁩، و بە ، و بیلدی zipapp بڵاو بکەیتەوە.
  • ڕاپۆرتی بە چارەسەر بکەیت و بە لیستی نمرەدان و ئاوڕدانەوە هەڵسەنگاندن بۆ پڕۆژەیەک بکەیت.

The brief: ship Bazaar Cartئەرکەکە: Bazaar Cart بڵاو بکەرەوە

This project uses the whole course. You build a small but complete Bazaar Cart from an empty folder: a console app for Soran's dried-fruit and spice shop in the Sulaimani bazaar (made-up prices in IQD). You plan it, keep its history in Git, test it, organise it in layers and release it — 1.0.0, then a bug fix 1.0.1 and a new feature 1.1.0. Nothing here is new: every step links the lesson that teaches it.

ئەم پڕۆژەیە هەموو خولەکە بەکار دەهێنێت. Bazaar Cartـێکی بچووک بەڵام تەواو لە بەتاڵەوە دروست دەکەیت: کۆنسۆڵ بۆ دوکانی میوەی وشک و بەهاراتی سۆران لە بازاڕی سلێمانی (نرخەکان دروستکراون، بە دینار). پلانی بۆ دادەنێیت، مێژووەکەی لە Git هەڵدەگریت، تاقیی دەکەیتەوە، بە ڕێکی دەخەیت و بڵاوی دەکەیتەوە — ⁦1.0.0⁩، پاشان چاکسازییەکی بە ⁦1.0.1⁩ و تایبەتمەندییەکی نوێ بە ⁦1.1.0⁩. هیچ شتێک لێرەدا نوێ نییە: هەر هەنگاوێک بەستەری ئەو وانەیەی هەیە کە فێری دەکات.

  • Work alone or in a team of 3–4 (Aram leads and owns the product, Shilan and Karwan write code, Dilan tests). The worked run below is done by one person on one computer; in a team each person works in a clone and opens pull requests (GitHub and teamwork).
  • Keep the repository private and use no real customer data: the phone numbers are fake (0770 000 0001) and no secret key ever enters Git.
  • Hand in: the repository with its history and tags; README and CHANGELOG; a test suite that passes; the .pyz build; a one-page plan (stories, MoSCoW, board); and a short retrospective.
  • بە تەنیا کار بکە یان لە تیمێکی ⁦3–4⁩ کەسیدا (ئارام سەرپەرشتی دەکات و ، شیلان و کاروان کۆد دەنووسن، دیلان تاقی دەکاتەوە). ئەو جێبەجێکردنە نموونەییەی خوارەوە یەک کەس لەسەر یەک کردوویەتی؛ لە تیمێکدا هەر کەسێک لە کار دەکات و دەکاتەوە (GitHub و کاری تیمی).
  • تایبەت ڕابگرە و هیچ ڕاستەقینەی کڕیار بەکار مەهێنە: ژمارە تەلەفۆنەکان ساختەن (0770 000 0001) و هیچ کلیلێکی نهێنی هەرگیز ناچێتە ناو Gitـەوە.
  • ئەمانە ڕادەست بکە: لەگەڵ مێژوو و ؛ README و CHANGELOG؛ کە سەردەکەوێت؛ .pyz؛ پلانێکی یەک لاپەڕەیی (چیرۆکەکان، MoSCoW، تەختە)؛ و ئاوڕدانەوەیەکی کورت.
A road with twelve numbered stops, each with the lesson it uses: 1 stories (requirements), 2 plan and board (life cycle), 3 repository (Git basics), 4 design (layers, patterns, DI), 5 data layer, 6 business layer test-first (testing), 7 presenter and wiring, 8 all tests green, 9 feature branch and conflict (branches), 10 release 1.0.0 (versions), 11 bug report and 1.0.1, 12 feature and 1.1.0.ڕێگایەک بە دوازدە وێستگەی ژمارەدار، هەریەکە لەگەڵ ئەو وانەیەی بەکاری دەهێنێت: 1 چیرۆکەکان (پێداویستییەکان)، 2 پلان و تەختە (خولی ژیان)، 3 کۆگای کۆد (بنەماکانی Git)، 4 دیزاین (چینەکان، شێوازەکان، DI)، 5 چینی دەستڕاگەیشتن بە دراوە، 6 چینی لۆجیکی کاروبار بە تاقیکردنەوە لە پێشەوە، 7 پێشکەشکەر و بەستنەوە، 8 هەموو تاقیکردنەوەکان سەوز، 9 لقی تایبەتمەندی و ناکۆکی، 10 وەشانی بڵاوکراوە 1.0.0، 11 ڕاپۆرتی باگ و 1.0.1، 12 تایبەتمەندی و 1.1.0.
Twelve steps, each one a lesson of this course put to work.دوازدە هەنگاو، هەریەکە وانەیەکی ئەم خولە کە دەخرێتە کار.CêIT

Step 1: stories and requirementsهەنگاوی 1: چیرۆکەکان و پێداویستییەکان

Start with what Soran needs, not with code (requirements and design). Write each need as a user story with acceptance criteria — real numbers that a test can check later — and rank the stories with MoSCoW. The delivery story keeps the label it has in the requirements lesson, BC-7.

بە ئەوە دەست پێبکە کە سۆران پێویستیەتی، نەک بە کۆد (پێداویستییەکان و دیزاین). هەر پێویستییەک وەک لەگەڵ بنووسە — ژمارەی ڕاستەقینە کە دواتر تاقیکردنەوەیەک بتوانێت بیپشکنێت — و بە ڕیزبەندیی چیرۆکەکان بکە. چیرۆکی گەیاندن هەمان ناونیشانی وانەی دەهێڵێتەوە: ⁦BC-7⁩.

The six stories of the projectشەش چیرۆکەکەی پڕۆژەکە
StoryچیرۆکAs a … I want … so that …وەک … دەمەوێت … بۆ ئەوەی …Acceptance examplesنموونەکانی پەسەندکردنMoSCoW
BC-1As a customer, I want each line priced, so that I know what I pay (R1, R2).وەک کڕیار، دەمەوێت نرخی هەر دێڕێک ببینم، بۆ ئەوەی بزانم چی دەدەم (⁦R1⁩، ⁦R2⁩).walnuts 500 g → 9,000; sumac 350 g → 3,000; 99 g and 10,050 g refusedگوێز ⁦500⁩ گرام ← ⁦9,000⁩؛ سوماق ⁦350⁩ گرام ← ⁦3,000⁩؛ ⁦99⁩ گرام و ⁦10,050⁩ گرام ڕەت دەکرێنەوەMust
BC-2As Soran, I want orders never to take more than my stock (R3).وەک سۆران، دەمەوێت هیچ داواکارییەک لە کۆگاکەم زیاتر نەبات ⁦(R3)⁩.honey 3,500 g refused when 3,000 g are in stockهەنگوین ⁦3,500⁩ گرام ڕەت دەکرێتەوە کاتێک ⁦3,000⁩ گرام لە کۆگادایەMust
BC-3As a customer, I want to build my cart line by line and confirm it.وەک کڕیار، دەمەوێت سەبەتەکەم دێڕ بە دێڕ دروست بکەم و پشتڕاستی بکەمەوە."walnuts 500" shows the line and the total; letters for grams show an error«⁦walnuts 500⁩» دێڕەکە و کۆکە پیشان دەدات؛ پیت لەبری گرام پیشان دەداتMust
BC-7As a customer, I want to see the delivery fee before I order (R4).وەک کڕیار، دەمەوێت پێش داواکردن کرێی گەیاندن ببینم ⁦(R4)⁩.goods 49,750 → fee 3,000; goods 50,000 → freeکاڵای ⁦49,750⁩ ← کرێ ⁦3,000⁩؛ کاڵای ⁦50,000⁩ ← بێبەرانبەرMust
BC-4As a customer, I want an SMS when my order is placed.وەک کڕیار، دەمەوێت کاتێک داواکارییەکەم تۆمار دەکرێت وەربگرم.exactly one SMS with the total; none when a rule says noڕێک یەک نامە بە کۆکەوە؛ هیچ نامەیەک کاتێک یاسایەک ڕەتی دەکاتەوەShould
BC-8As a regular customer, I want 5 % off big orders (R5).وەک کڕیاری بەردەوام، دەمەوێت 5 لە سەد داشکاندن بۆ داواکاریی گەورە ⁦(R5)⁩.goods 100,000 → 5,000 off; 99,750 → none (planned for 1.1.0)کاڵای ⁦100,000⁩ ← ⁦5,000⁩ داشکاندن؛ ⁦99,750⁩ ← هیچ (بۆ ⁦1.1.0⁩ پلان کراوە)Could

Three non-functional requirements: N1 Python 3.13 and its standard library only, shipped as one .pyz file; N2 the whole test suite runs in under 5 seconds; N3 no real customer data, and secrets never enter the repository. Won't have this time: card payment, delivery outside Sulaimani and Soran's list of new orders.

سێ : ⁦N1⁩ تەنیا ⁦Python 3.13⁩ و ، وەک یەک فایلی .pyz؛ ⁦N2⁩ هەموو لە کەمتر لە 5 چرکەدا جێبەجێ دەبێت؛ ⁦N3⁩ هیچ ڕاستەقینەی کڕیار نییە و نهێنییەکان هەرگیز ناچنە ناو . ⁦Won't have this time⁩: پارەدان بە کارت، گەیاندن بۆ دەرەوەی سلێمانی و لیستی داواکارییە نوێیەکانی سۆران.

Four MoSCoW buckets with story cards. Must: BC-1 line prices (R1, R2), BC-2 stock (R3), BC-3 cart on screen, BC-7 delivery fee (R4). Should: BC-4 SMS confirmation. Could: BC-8 5 % discount (R5), marked 1.1.0. Won't have this time: card payment, delivery outside Sulaimani, Soran's order list.چوار سەبەتەی MoSCoW بە کارتی چیرۆکەوە. Must: BC-1 نرخی دێڕەکان (R1، R2)، BC-2 کۆگا (R3)، BC-3 سەبەتە لەسەر شاشە، BC-7 کرێی گەیاندن (R4). Should: BC-4 پشتڕاستکردنەوە بە نامەی کورت. Could: BC-8 داشکاندنی 5 لە سەد (R5)، بە نیشانەی 1.1.0. Won't have this time: پارەدان بە کارت، گەیاندن بۆ دەرەوەی سلێمانی، لیستی داواکارییەکانی سۆران.
Version 1.0.0 must contain every Must; the Could waits for 1.1.0. ⁦1.0.0⁩ دەبێت هەموو Mustـەکانی تێدا بێت؛ Couldـەکە چاوەڕێی ⁦1.1.0⁩ دەکات.CêIT

Step 2: two sprints on a boardهەنگاوی 2: دوو سپرینت لەسەر تەختەیەک

Split the work into two-week sprints (the development life cycle). Sprint 1 (25 October – 7 November 2026) delivers every Must and the Should, and ends with release 1.0.0 on 7 November. Sprint 2 (8 – 21 November) handles the first bug reports and BC-8, released as 1.1.0 on 20 November — the same dates as Bazaar Cart's story in versions and builds. Your own dates will be the days you work.

کارەکە بۆ دوو هەفتەیی دابەش بکە (خولی ژیانی گەشەپێدان). 1 (⁦25⁩ی تشرینی یەکەم – 7ی تشرینی دووەمی ⁦2026⁩) هەموو Mustـەکان و Shouldـەکە دەگەیەنێت و بە ⁦1.0.0⁩ لە 7ی تشرینی دووەم کۆتایی دێت. 2 (⁦8 – 21⁩ی تشرینی دووەم) یەکەم ڕاپۆرتەکانی و ⁦BC-8⁩ دەگرێتە ئەستۆ کە وەک ⁦1.1.0⁩ لە ⁦20⁩ی تشرینی دووەم بڵاو دەکرێتەوە — هەمان ڕێکەوتەکانی چیرۆکی Bazaar Cart لە وەشانەکان و بیلدەکان. ڕێکەوتەکانی خۆت ئەو ڕۆژانە دەبن کە کاری تێدا دەکەیت.

Track the cards on a Kanban board with the columns To do → Doing → Testing → Done and a WIP limit of 2 in Doing. A card is done only when it meets the team's Definition of Done:

کارتەکان لەسەر تەختەیەکی بە ستوونەکانی To do ← Doing ← Testing ← Done و سنووری کاری لە دەستدای 2 لە Doing بەدواداچوونیان بۆ بکە. کارتێک تەنیا ئەو کاتە تەواوە کە لەگەڵ تیمەکە بگونجێت:

  • all tests pass (python3.13 -m unittest);
  • someone else reviewed the change (code review);
  • README and CHANGELOG say what changed.
  • هەموو تاقیکردنەوەکان سەردەکەون (python3.13 -m unittest)؛
  • کەسێکی تر گۆڕانکارییەکەی پێداچووەتەوە (پێداچوونەوەی کۆد)؛
  • README و CHANGELOG دەڵێن چی گۆڕاوە.
A Kanban board in the middle of sprint 1. To do: BC-7 delivery fee, BC-4 SMS. Doing (WIP limit 2): BC-3 cart screen (Shilan), README (Aram). Testing: BC-2 stock check (Dilan). Done: repository and .gitignore, BC-1 line prices. Under the board, the Definition of Done: tests pass, reviewed, README and CHANGELOG updated.تەختەیەکی کانبان لە ناوەڕاستی سپرینت 1. To do: BC-7 کرێی گەیاندن، BC-4 نامەی کورت. Doing (سنووری 2): BC-3 شاشەی سەبەتە (شیلان)، README (ئارام). Testing: BC-2 پشکنینی کۆگا (دیلان). Done: کۆگای کۆد و .gitignore، BC-1 نرخی دێڕەکان. لە ژێر تەختەکە، پێناسەی تەواوبوون: تاقیکردنەوەکان سەردەکەون، پێداچوونەوە کراوە، README و CHANGELOG نوێ کراونەتەوە.
Sprint 1 on the board: at most two cards in Doing. 1 لەسەر تەختەکە: زۆرترین دوو کارت لە Doing.CêIT

Step 3: an empty repository with its first commitهەنگاوی 3: کۆگایەکی کۆدی بەتاڵ و یەکەم کۆمیتەکەی

Create the repository and three small files before any code (Git basics): a README that says how to run and test the app, a .gitignore that keeps caches, builds and secrets out, and a CHANGELOG with an empty Unreleased section.

پێش هەر کۆدێک و سێ فایلی بچووک دروست بکە (بنەماکانی Git): README کە دەڵێت چۆن جێبەجێ دەکرێت و تاقی دەکرێتەوە، .gitignore کە کاش، و نهێنییەکان لە دەرەوە دەهێڵێتەوە و CHANGELOG بە بەشێکی Unreleasedـی بەتاڵ.

Who you are, and a new repositoryتۆ کێیت و کۆگایەکی کۆدی نوێBASHType in a terminalلە تێرمیناڵدا بینووسە
git config --global user.name "Aram"
git config --global user.email "aram@example.com"
git init -b main bazaar-cart
cd bazaar-cart
Files Git should not trackئەو فایلانەی Git نابێت بەدواداچوونیان بۆ بکات.gitignoreTEXT
# Python's cache and virtual environments
__pycache__/
*.pyc
.venv/

# Built files: made again from the source at any time
build/
dist/

# Secrets stay on your computer
.env
The changelog starts emptyتۆماری گۆڕانکارییەکان بە بەتاڵی دەست پێدەکاتCHANGELOG.mdTEXT
# Changelog

All notable changes to Bazaar Cart are written here.
This project follows Semantic Versioning (https://semver.org).

## [Unreleased]
The first commitیەکەم کۆمیتBASHType in a terminalلە تێرمیناڵدا بینووسە
git status --short
git add .
git commit -m "Start Bazaar Cart: README, .gitignore and CHANGELOG"

Step 4: design — layers, MVP and DI togetherهەنگاوی 4: دیزاین — چینەکان، MVP و DI پێکەوە

Before writing classes, draw how the parts fit. Three layers with one direction (layers and business logic): the data access layer knows SQL, the business logic layer knows Soran's rules R1–R5, and the user interface layer asks and shows. Inside the user interface, the screen follows MVP (MVC, MVP and MVVM): a passive console view and a presenter. Every object gets its parts by constructor injection, and one composition root, main() in app.py, builds them all (dependency injection).

پێش نووسینی ، بکێشە کە بەشەکان چۆن لەگەڵ یەکتر دەگونجێن. سێ بە یەک ئاراستە (چینەکان و لۆجیکی کاروبار): دەزانێت، یاساکانی سۆران ⁦R1–R5⁩ دەزانێت و دەپرسێت و پیشان دەدات. لە ناو شاشەکە پەیڕەوی MVP دەکات (MVC، MVP و MVVM): کۆنسۆڵی ناچالاک و . هەر پارچەکانی بە وەردەگرێت و یەک ، main() لە app.py، هەموویان دروست دەکات (تێخستنی وابەستەیی).

Three horizontal layers. User interface layer: ConsoleCartView and CartPresenter (MVP). Business logic layer: OrderService with rules R1–R5 and the Notifier shape. Data access layer: ProductRepository with an SQLite database. Arrows go down only. ConsoleNotifier sits beside the layers and fits the Notifier shape. On the left, main() in app.py has dashed arrows to every object it creates.سێ چینی ئاسۆیی. چینی نێوبەستەی بەکارهێنەر: ConsoleCartView و CartPresenter (MVP). چینی لۆجیکی کاروبار: OrderService بە یاساکانی R1–R5 و شێوەی Notifier. چینی دەستڕاگەیشتن بە دراوە: ProductRepository لەگەڵ بنکەدراوەیەکی SQLite. تیرەکان تەنیا بەرەو خوارەوە دەچن. ConsoleNotifier لە تەنیشت چینەکانەوەیە و لەگەڵ شێوەی Notifier دەگونجێت. لە چەپ، main() لە app.py تیری پچڕپچڕی بۆ هەموو ئەو ئۆبجێکتانەی هەیە کە دروستیان دەکات.
One picture of the whole design: layers, MVP in the top layer, and main() wiring everything.یەک وێنەی هەموو دیزاینەکە: ، MVP لە سەرەوە و ⁦main()⁩ کە هەموو شتێک پێکەوە دەبەستێت.CêIT

Step 4: design — layers, MVP and DI togetherهەنگاوی 4: دیزاین — چینەکان، MVP و DI پێکەوە

Classes and filesکلاسەکان و فایلەکان

The class diagram extends the one from the requirements lesson (one class diagram) with the classes of the design, and the folder tree puts each class in its file. Each file has one job, so a change to one rule touches one file.

ئەوەی وانەی (یەک هێڵکاریی کلاسی UML) بە دیزاینەکە فراوان دەکات و دارە فۆڵدەرەکە هەر لە فایلی خۆیدا دادەنێت. هەر فایلێک یەک کاری هەیە، بۆیە گۆڕینی یەک یاسا دەست لە یەک فایل دەدات.

Left, a class diagram: Product (code, name, price_per_kg, stock_g); Order holds OrderLine items (name, grams, price) and has goods_total, delivery and total; OrderService (price_order, place_order) uses a ProductRepository and a Notifier; CartPresenter (on_add, on_confirm) uses OrderService and a CartView. Right, the folder tree: bazaar_cart with __init__.py, __main__.py, app.py, cli.py, models.py, notifiers.py, presenter.py, repository.py, services.py; tests with test_presenter.py, test_repository.py, test_services.py; build.py, README.md, CHANGELOG.md, .gitignore.لە چەپ، هێڵکاریی کلاسی UML: Product (code، name، price_per_kg، stock_g)؛ Order کۆمەڵێک OrderLine (name، grams، price) هەڵدەگرێت و goods_total، delivery و totalـی هەیە؛ OrderService (price_order، place_order) ProductRepository و Notifier بەکار دەهێنێت؛ CartPresenter (on_add، on_confirm) OrderService و CartView بەکار دەهێنێت. لە ڕاست، دارە فۆڵدەرەکە: bazaar_cart بە __init__.py، __main__.py، app.py، cli.py، models.py، notifiers.py، presenter.py، repository.py، services.py؛ tests بە test_presenter.py، test_repository.py، test_services.py؛ build.py، README.md، CHANGELOG.md، .gitignore.
Each class in its own file, each file in its layer.هەر لە فایلی خۆیدا و هەر فایلێک لە خۆیدا.CêIT

Step 5: the data access layerهەنگاوی 5: چینی دەستڕاگەیشتن بە دراوە

models.py and repository.py are exactly the files of the layers lesson: the dataclasses Product, OrderLine and Order, and a ProductRepository over an SQLite database in memory with Soran's five products. Copy them, then prove that the repository and the real database work together with an integration test:

models.py و repository.py ڕێک فایلەکانی وانەی چینەکانن: dataclassـەکانی Product، OrderLine و Order و ProductRepositoryـێک لەسەر لە ناو بیرگە بە پێنج کاڵاکەی سۆران. کۆپییان بکە، پاشان بە بیسەلمێنە کە و ڕاستەقینەکە پێکەوە کار دەکەن:

An integration test: the repository with a real SQLite databaseتاقیکردنەوەی پێکەوەبەستن: کۆگای دراوە لەگەڵ بنکەدراوەیەکی SQLiteـی ڕاستەقینەtests/test_repository.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
"""Integration tests: the repository with a real SQLite database (in memory)."""
import unittest

from bazaar_cart.repository import ProductRepository, open_demo_db


class ProductRepositoryTest(unittest.TestCase):
    def setUp(self):
        self.repo = ProductRepository(open_demo_db())   # a fresh database per test

    def test_get_returns_the_product(self):
        honey = self.repo.get("honey")
        self.assertEqual((honey.name, honey.price_per_kg, honey.stock_g),
                         ("Honey", 30000, 3000))

    def test_unknown_code_returns_none(self):
        self.assertIsNone(self.repo.get("saffron"))

    def test_reduce_stock_is_saved(self):
        self.repo.reduce_stock("sumac", 350)
        self.assertEqual(self.repo.get("sumac").stock_g, 4650)


if __name__ == "__main__":
    unittest.main()
The products table in the SQLite database: columns code, name, price_per_kg, stock_g. Rows: walnuts, Walnuts, 18000, 20000; sumac, Sumac, 8000, 5000; figs, Dried figs, 12000, 8000; tea, Black tea, 14000, 10000; honey, Honey, 30000, 3000. A note: made-up prices in IQD per kg; stock in grams.خشتەی products لە بنکەدراوەی SQLite: ستوونەکانی code، name، price_per_kg، stock_g. ڕیزەکان: walnuts، Walnuts، 18000، 20000؛ sumac، Sumac، 8000، 5000؛ figs، Dried figs، 12000، 8000؛ tea، Black tea، 14000، 10000؛ honey، Honey، 30000، 3000. تێبینی: نرخی دروستکراو بە دینار بۆ هەر کیلۆیەک؛ کۆگا بە گرام.
Soran's five products: the data every test and every run starts from.پێنج کاڵاکەی سۆران: ئەو کە هەموو تاقیکردنەوەیەک و هەموو جێبەجێکردنێک لێیەوە دەست پێدەکات.CêIT
Commit the data layerچینی دەستڕاگەیشتن بە دراوە کۆمیت بکەBASHType in a terminalلە تێرمیناڵدا بینووسە
git add .
git commit -m "Add the product models and the SQLite repository"

Step 6: the business layer, test firstهەنگاوی 6: چینی لۆجیکی کاروبار، تاقیکردنەوە لە پێشەوە

Write the tests for R1–R3 before the code (test first: red, green, refactor). Each acceptance example of BC-1 and BC-2 becomes one check, with the boundaries 99, 100, 10,000 and 10,050 g. A small fake repository stands in for the database, as in the layers lesson.

تاقیکردنەوەکانی ⁦R1–R3⁩ پێش کۆدەکە بنووسە (تاقیکردنەوە لە پێشەوە: سوور، سەوز، ڕێکخستنەوە). هەر نموونەیەکی پەسەندکردنی ⁦BC-1⁩ و ⁦BC-2⁩ دەبێتە یەک پشکنین، لەگەڵ سنوورەکانی ⁦99⁩، ⁦100⁩، ⁦10,000⁩ و ⁦10,050⁩ گرام. ساختەی بچووک جێگەی دەگرێتەوە، وەک وانەی .

The tests come first (part of the file)تاقیکردنەوەکان پێشتر دێن (بەشێک لە فایلەکە)tests/test_services.pyPYTHON
class OrderServiceTest(unittest.TestCase):
    def setUp(self):
        self.repo = InMemoryProductRepository([
            Product("walnuts", "Walnuts", 18000, 20000),
            Product("sumac", "Sumac", 8000, 5000),
            Product("honey", "Honey", 30000, 3000)])
        self.service = OrderService(self.repo)

    def price(self, code, grams):
        return self.service.price_order([(code, grams)]).lines[0].price

    def test_r1_exact_price(self):                    # story BC-1, criterion 1
        self.assertEqual(self.price("walnuts", 500), 9000)

    def test_r1_rounds_up_to_250(self):               # story BC-1, criterion 2
        self.assertEqual(self.price("sumac", 350), 3000)   # 2,800 -> 3,000

    def test_r2_boundaries(self):
        self.assertEqual(self.price("sumac", 100), 1000)
        self.assertEqual(self.price("walnuts", 10_000), 180_000)
        for grams in (99, 10_050):
            with self.assertRaises(ValueError):
                self.service.price_order([("sumac", grams)])

    def test_r3_not_more_than_the_stock(self):
        self.assertEqual(self.price("honey", 3000), 90_000)
        with self.assertRaises(ValueError):
            self.service.price_order([("honey", 3500)])
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 12-13

Story BC-1, criterion 1: an exact price.

چیرۆکی ⁦BC-1⁩، پێوەری 1: نرخێکی ڕێک.

Lineدێڕی 15-16

Story BC-1, criterion 2: 2,800 IQD is rounded up to 3,000.

چیرۆکی ⁦BC-1⁩، پێوەری ⁦2: 2,800⁩ دینار بەرز دەکرێتەوە بۆ ⁦3,000⁩.

Red: services.py does not exist yetسوور: ⁦services.py⁩ هێشتا نییە
E
======================================================================
ERROR: test_services (unittest.loader._FailedTest.test_services)
----------------------------------------------------------------------
ImportError: Failed to import test module: test_services
Traceback (most recent call last):
  File "/opt/homebrew/Cellar/python@3.13/3.13.17/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/loader.py", line 141, in loadTestsFromName
    module = __import__(module_name)
  File "~/se/bazaar-cart/tests/test_services.py", line 5, in <module>
    from bazaar_cart.services import OrderService
ModuleNotFoundError: No module named 'bazaar_cart.services'


----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

Now write services.py as in the layers lesson: round_up_250() with math.ceil, line_price(), and OrderService.price_order() checking R2 and R3. Delivery stays 0 until R4 arrives on its own branch in step 9. Run the tests again:

ئێستا services.py وەک وانەی چینەکان بنووسە: round_up_250() بە math.ceil، line_price() و OrderService.price_order() کە ⁦R2⁩ و ⁦R3⁩ دەپشکنێت. کرێی گەیاندن 0 دەمێنێتەوە تا ⁦R4⁩ لە هەنگاوی 9دا لەسەر خۆی دێت. تاقیکردنەوەکان دیسان جێبەجێ بکە:

Green: python3.13 -m unittest -v tests.test_servicesسەوز: ⁦python3.13 -m unittest -v tests.test_services⁩
test_r1_exact_price (tests.test_services.OrderServiceTest.test_r1_exact_price) ... ok
test_r1_rounds_up_to_250 (tests.test_services.OrderServiceTest.test_r1_rounds_up_to_250) ... ok
test_r2_boundaries (tests.test_services.OrderServiceTest.test_r2_boundaries) ... ok
test_r3_not_more_than_the_stock (tests.test_services.OrderServiceTest.test_r3_not_more_than_the_stock) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
The red, green, refactor loop of test-driven development, from the automated testing lesson.
The red, green, refactor loop of test-driven development, from the automated testing lesson.
Red, green, refactor: the loop you just ran for R1–R3.سوور، سەوز، ڕێکخستنەوە: ئەو خولەی ئێستا بۆ ⁦R1–R3⁩ جێبەجێت کرد.CêIT
Commit when greenکاتێک سەوز بوو کۆمیت بکەBASHType in a terminalلە تێرمیناڵدا بینووسە
git add .
git commit -m "Add line prices and checks for R1, R2 and R3"

Step 7: the presenter and the console viewهەنگاوی 7: پێشکەشکەر و دیمەنی کۆنسۆڵ

The screen follows the MVP code of the patterns lesson, with one addition for BC-3 and BC-4: on_confirm(phone) places the order. The presenter never calls input() or print(); the view never knows a rule.

شاشەکە پەیڕەوی کۆدی MVPـی وانەی شێوازەکان دەکات، بە یەک زیادکردن بۆ ⁦BC-3⁩ و ⁦BC-4⁩: on_confirm(phone) داواکارییەکە تۆمار دەکات. هەرگیز input() یان print() بانگ ناکات؛ هەرگیز یاسایەک نازانێت.

The presenter: decides what the screen showsپێشکەشکەر: بڕیار دەدات شاشەکە چی پیشان بداتbazaar_cart/presenter.pyPYTHON
"""Presenter (MVP): decides what the cart screen shows. No input(), no print()."""
from typing import Protocol


class CartView(Protocol):               # what every cart view must offer
    def show_lines(self, lines: list[str]) -> None: ...
    def show_total(self, text: str) -> None: ...
    def show_error(self, message: str) -> None: ...
    def show_message(self, text: str) -> None: ...


class CartPresenter:
    def __init__(self, service, view: CartView) -> None:
        self.service = service
        self.view = view
        self.items = []

    def on_add(self, code, grams_text):   # the view calls this for every "Add"
        if not grams_text.isdigit():
            self.view.show_error("Type the grams as a whole number.")
            return
        try:
            order = self.service.price_order(self.items + [(code, int(grams_text))])
        except ValueError as error:       # a business rule said no
            self.view.show_error(str(error))
            return
        self.items.append((code, int(grams_text)))
        self.view.show_lines([f"{line.name} {line.grams} g: {line.price:,} IQD"
                              for line in order.lines])
        self.view.show_total(f"Total {order.total:,} IQD (delivery {order.delivery:,})")

    def on_confirm(self, phone):          # the view calls this at the end
        if not self.items:
            self.view.show_error("The cart is empty.")
            return
        if not phone:
            self.view.show_error("Type a phone number for the SMS.")
            return
        try:
            total = self.service.place_order(self.items, phone)
        except ValueError as error:
            self.view.show_error(str(error))
            return
        self.items = []
        self.view.show_message(f"Order placed: {total:,} IQD. Thank you!")
The passive console viewدیمەنی کۆنسۆڵی ناچالاکbazaar_cart/cli.pyPYTHON
"""Console view (MVP): it reads keys and prints what the presenter tells it."""


class ConsoleCartView:
    def show_lines(self, lines):
        for line in lines:
            print("  " + line)

    def show_total(self, text):
        print("  " + text)

    def show_error(self, message):
        print("  ! " + message)

    def show_message(self, text):
        print(text)

    def run(self, presenter):
        while command := input("Add (code grams), empty to stop: ").strip():
            code, _, grams = command.partition(" ")
            presenter.on_add(code, grams)
        presenter.on_confirm(input("Phone for the SMS: ").strip())

Step 7: the presenter and the console viewهەنگاوی 7: پێشکەشکەر و دیمەنی کۆنسۆڵ

Wiring it in app.pyبەستنەوەی لە ⁦app.py⁩

OrderService now takes a notifier, exactly as in the dependency injection lesson, and app.py is the composition root. With no real customers, the project uses ConsoleNotifier; __main__.py lets you start the app with python3.13 -m bazaar_cart.

ئێستا OrderService ئاگادارکەرەوەیەک وەردەگرێت، ڕێک وەک وانەی تێخستنی وابەستەیی و app.py . چونکە کڕیاری ڕاستەقینە نییە، پڕۆژەکە ConsoleNotifier بەکار دەهێنێت؛ __main__.py ڕێگە دەدات بە python3.13 -m bazaar_cart دەست پێبکەیت.

The composition rootڕەگی پێکهێنانbazaar_cart/app.pyPYTHON
"""Composition root: the one place where Bazaar Cart's objects are made and connected."""
from .cli import ConsoleCartView
from .notifiers import ConsoleNotifier
from .presenter import CartPresenter
from .repository import ProductRepository, open_demo_db
from .services import OrderService


def main():
    repo = ProductRepository(open_demo_db())          # data access layer
    service = OrderService(repo, ConsoleNotifier())   # business layer + its notifier
    view = ConsoleCartView()                          # presentation: the view ...
    view.run(CartPresenter(service, view))            # ... and its presenter
bazaar_cart/__main__.pyPYTHON
"""Lets you start the app with: python3.13 -m bazaar_cart"""
from .app import main

main()
main() at the top makes repo, notifier and view; repo and notifier go into OrderService; OrderService and the view go into CartPresenter.main() لە سەرەوە repo، notifier و view دروست دەکات؛ repo و notifier دەچنە ناو OrderService؛ OrderService و view دەچنە ناو CartPresenter.
The project's object graph, built once by main().تۆڕی پڕۆژەکە، کە یەک جار ⁦main()⁩ دروستی دەکات.CêIT
A sample run (no delivery fee yet: R4 comes in step 9)نموونەیەکی جێبەجێکردن (هێشتا کرێی گەیاندن نییە: ⁦R4⁩ لە هەنگاوی 9دا دێت)
$ python3.13 -m bazaar_cart
Add (code grams), empty to stop: walnuts 500
  Walnuts 500 g: 9,000 IQD
  Total 9,000 IQD (delivery 0)
Add (code grams), empty to stop: sumac half
  ! Type the grams as a whole number.
Add (code grams), empty to stop: sumac 350
  Walnuts 500 g: 9,000 IQD
  Sumac 350 g: 3,000 IQD
  Total 12,000 IQD (delivery 0)
Add (code grams), empty to stop: 
Phone for the SMS: 0770 000 0001
[SMS to 0770 000 0001] Bazaar Cart: your order of 12,000 IQD is on its way.
Order placed: 12,000 IQD. Thank you!

Step 8: every test green, every layer testedهەنگاوی 8: هەموو تاقیکردنەوەکان سەوز، هەموو چینێک تاقیکراوە

Add the presenter tests (a fake view, as in testing a presenter) and two notifier tests with a FakeNotifier. Then run the whole suite. python3.13 -m unittest finds every test*.py file by itself:

تاقیکردنەوەکانی زیاد بکە ( ساختە، وەک تاقیکردنەوەی پێشکەشکەرێک) و دوو تاقیکردنەوەی ئاگادارکەرەوە بە FakeNotifier. پاشان هەموو جێبەجێ بکە. python3.13 -m unittest خۆی هەموو فایلێکی test*.py دەدۆزێتەوە:

The whole test suiteهەموو کۆمەڵەی تاقیکردنەوەکەBASHType in a terminalلە تێرمیناڵدا بینووسە
python3.13 -m unittest -v
The 13 tests sorted by layer. User interface layer: 4 presenter tests with a real service and a fake view. Business logic layer: 6 unit tests of OrderService with a fake repository and a fake notifier (R1, R2, R3, SMS). Data access layer: 3 integration tests with a real SQLite database. Time for all 13: 0.002 seconds.13 تاقیکردنەوەکە بەپێی چین ڕیز کراون. چینی نێوبەستەی بەکارهێنەر: 4 تاقیکردنەوەی پێشکەشکەر بە خزمەتگوزارییەکی ڕاستەقینە و دیمەنێکی ساختە. چینی لۆجیکی کاروبار: 6 تاقیکردنەوەی یەکەی OrderService بە کۆگایەکی دراوەی ساختە و ئاگادارکەرەوەیەکی ساختە (R1، R2، R3، نامەی کورت). چینی دەستڕاگەیشتن بە دراوە: 3 تاقیکردنەوەی پێکەوەبەستن بە بنکەدراوەیەکی SQLiteی ڕاستەقینە. کاتی هەموو 13ـەکە: 0.002 چرکە.
Every layer has tests, and all 13 run in two thousandths of a second (N2).هەموو تاقیکردنەوەی هەیە و هەموو ⁦13⁩ـەکە لە دوو هەزارەمی چرکەیەکدا جێبەجێ دەبن ⁦(N2)⁩.CêIT
Commit, and look at the historyکۆمیت بکە و سەیری مێژووەکە بکەBASHType in a terminalلە تێرمیناڵدا بینووسە
git add .
git commit -m "Add the console cart (MVP), wired in app.py"
git log --oneline

Step 9: the delivery fee on its own branchهەنگاوی 9: کرێی گەیاندن لەسەر لقی خۆی

BC-7 (R4) is built on a feature branch, so main keeps working while it grows (branches and merging). Karwan writes the two tests from BC-7's acceptance criteria first — 49,750 IQD pays 3,000, exactly 50,000 travels free — and updates the presenter tests, whose totals now include delivery:

⁦BC-7 (R4)⁩ لەسەر دروست دەکرێت، بۆیە main لە کاتی گەشەکردنیدا هەر کار دەکات (لقەکان و بەیەککردن). کاروان سەرەتا دوو تاقیکردنەوەکە لە ⁦BC-7⁩ دەنووسێت — ⁦49,750⁩ دینار ⁦3,000⁩ دەدات، ڕێک ⁦50,000⁩ بێبەرانبەر دەگات — و تاقیکردنەوەکانی نوێ دەکاتەوە کە ئێستا کۆکانیان کرێی گەیاندنیش دەگرێتەوە:

BASHType in a terminalلە تێرمیناڵدا بینووسە
git switch -c feature/delivery-fee
R4's acceptance criteria as testsپێوەرەکانی پەسەندکردنی ⁦R4⁩ وەک تاقیکردنەوەtests/test_services.pyPYTHON
    def test_r4_delivery_below_50000(self):        # 49,750 IQD of goods
        order = self.service.price_order([("honey", 1000), ("walnuts", 1000),
                                          ("sumac", 200)])
        self.assertEqual((order.goods_total, order.delivery), (49_750, 3_000))

    def test_r4_free_delivery_from_50000(self):     # exactly 50,000 IQD
        order = self.service.price_order([("honey", 1000), ("walnuts", 1000),
                                          ("sumac", 250)])
        self.assertEqual((order.goods_total, order.delivery), (50_000, 0))
Red: python3.13 -m unittest (shortened)سوور: ⁦python3.13 -m unittest⁩ (کورتکراوە)
FF...........F.
======================================================================
FAIL: test_r4_delivery_below_50000 (tests.test_services.OrderServiceTest.test_r4_delivery_below_50000)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "~/se/bazaar-cart/tests/test_services.py", line 64, in test_r4_delivery_below_50000
    self.assertEqual((order.goods_total, order.delivery), (49_750, 3_000))
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: Tuples differ: (49750, 0) != (49750, 3000)

First differing element 1:
0
3000

- (49750, 0)
+ (49750, 3000)
?         +++


[… 2 more failure(s) cut here …]

----------------------------------------------------------------------
Ran 15 tests in 0.013s

FAILED (failures=3)
Then the code: R4پاشان کۆدەکە: ⁦R4⁩bazaar_cart/services.pyPYTHON
def delivery_fee(goods_total):
    """R4: 3,000 IQD, free from 50,000 IQD of goods."""
    return 0 if goods_total >= FREE_DELIVERY_FROM else DELIVERY_FEE
Green, then commit on the branchسەوز، پاشان کۆمیت لەسەر لقەکەBASHType in a terminalلە تێرمیناڵدا بینووسە
python3.13 -m unittest
git commit -am "Add the delivery fee (R4)"
A commit graph. main has four commits: Start Bazaar Cart, models and SQLite repository, line prices R1–R3, console cart MVP; then 'Show the version number at start-up'. From the console-cart commit a branch feature/delivery-fee grows with 'Add the delivery fee (R4)'. Both changed the same lines of CHANGELOG.md.هێڵکارییەکی کۆمیتەکان. main چوار کۆمیتی هەیە: Start Bazaar Cart، مۆدێلەکان و کۆگای دراوەی SQLite، نرخی دێڕەکان R1–R3، سەبەتەی کۆنسۆڵ MVP؛ پاشان «Show the version number at start-up». لە کۆمیتی سەبەتەی کۆنسۆڵەوە لقی feature/delivery-fee بە «Add the delivery fee (R4)» گەشە دەکات. هەردووکیان هەمان دێڕەکانی CHANGELOG.mdـیان گۆڕیوە.
While R4 grew on its branch, main got a new commit too.لە کاتێکدا ⁦R4⁩ لەسەر گەشەی کرد، mainـیش نوێی وەرگرت.CêIT

Meanwhile Shilan adds the version number to the start screen on main and, following the Definition of Done, writes it into the CHANGELOG — in the same place where Karwan wrote his line.

لەو کاتەدا شیلان ژمارەی لە سەر main دەخاتە دێڕی یەکەمی و بەپێی لە CHANGELOGـدا دەینووسێت — لە هەمان شوێن کە کاروان دێڕەکەی خۆی نووسیبوو.

Meanwhile, on mainلەو کاتەدا، لەسەر mainBASHType in a terminalلە تێرمیناڵدا بینووسە
git switch main
git commit -am "Show the version number at start-up"

Step 9: the delivery fee on its own branchهەنگاوی 9: کرێی گەیاندن لەسەر لقی خۆی

Merge, resolve the conflict, delete the branchبەیەککردن، چارەسەرکردنی ناکۆکی، سڕینەوەی لقەکە

The merge stops on CHANGELOG.mdبەیەککردنەکە لەسەر ⁦CHANGELOG.md⁩ ڕادەوەستێتBASHType in a terminalلە تێرمیناڵدا بینووسە
git merge feature/delivery-fee
git status
CHANGELOG.md in the editor (the end of the file)⁦CHANGELOG.md⁩ لە دەستکاریکەرەکەدا (کۆتایی فایلەکە)CHANGELOG.mdTEXT
## [Unreleased]

### Added
- Five products with their stock in an SQLite database.
- Line prices rounded up to 250 IQD (R1), 100 g to 10,000 g per line (R2)
  and never more than the stock (R3).
- Console cart (MVP) with an SMS confirmation.
<<<<<<< HEAD
- Version number on the start screen.
=======
- Delivery fee: 3,000 IQD inside Sulaimani, free from 50,000 IQD (R4).
>>>>>>> feature/delivery-fee
The conflicted part of CHANGELOG.md with labels: <<<<<<< HEAD starts main's version (Version number on the start screen); ======= separates; >>>>>>> feature/delivery-fee ends the branch's version (Delivery fee … R4). Right: the resolved text keeps both lines and has no markers.بەشی ناکۆکی CHANGELOG.md بە ناونیشانەوە: <<<<<<< HEAD وەشانی main دەست پێدەکات (Version number on the start screen)؛ ======= جیایان دەکاتەوە؛ >>>>>>> feature/delivery-fee وەشانی لقەکە کۆتایی پێدێنێت (Delivery fee … R4). ڕاست: دەقە چارەسەرکراوەکە هەردوو دێڕەکە دەهێڵێتەوە و هیچ نیشانەیەکی تێدا نییە.
Both lines are right, so the resolution keeps both and removes every marker.هەردوو دێڕەکە ڕاستن، بۆیە چارەسەرکردنەکە هەردووکیان دەهێڵێتەوە و هەموو نیشانەکان لادەبات.CêIT

Both lines belong in the changelog, so keep both and delete the three marker lines. Run the tests before you finish the merge — leftover markers in a .py file would be a SyntaxError (leftover markers):

هەردوو دێڕەکە شوێنیان لە ، بۆیە هەردووکیان بهێڵەوە و سێ دێڕی نیشانەکان بسڕەوە. پێش تەواوکردنی تاقیکردنەوەکان جێبەجێ بکە — نیشانەی ماوە لە فایلێکی .pyـدا دەبێتە SyntaxError (نیشانە ماوەکان):

Finish the mergeبەیەککردنەکە تەواو بکەBASHType in a terminalلە تێرمیناڵدا بینووسە
python3.13 -m unittest
git add CHANGELOG.md
git commit --no-edit
git branch -d feature/delivery-fee
git log --oneline --graph

Step 10: release 1.0.0هەنگاوی ⁦10⁩: وەشانی بڵاوکراوە ⁦1.0.0⁩

Every Must is done, so Bazaar Cart becomes 1.0.0: the first stable version that people may depend on (what 1.0.0 means). Four small changes make a release: the version number in bazaar_cart/__init__.py (__version__ = "1.0.0"), the Unreleased lines moved under ## [1.0.0] - 2026-11-07 in the CHANGELOG, a build script, and an annotated tag:

هەموو Mustـەکان تەواو بوون، بۆیە Bazaar Cart دەبێتە ⁦1.0.0⁩: یەکەم جێگیر کە خەڵک دەتوانن پشتی پێ ببەستن (⁦1.0.0⁩ واتای چییە). چوار گۆڕانکاریی بچووک دروست دەکەن: ژمارەی لە bazaar_cart/__init__.py (__version__ = "1.0.0")، گواستنەوەی دێڕەکانی Unreleased بۆ ژێر ## [1.0.0] - 2026-11-07 لە CHANGELOG، سکریپتێکی بیلد و تاگێکی annotated:

The build script: zipapp, skipping tests and cachesسکریپتی بیلد: zipapp، بەبێ تاقیکردنەوە و کاشbuild.pyPYTHON
"""Build dist/bazaar_cart-<version>.pyz from the bazaar_cart package (zipapp, as in python -m zipapp)."""
import pathlib
import zipapp

from bazaar_cart import __version__

target = pathlib.Path("dist") / f"bazaar_cart-{__version__}.pyz"
target.parent.mkdir(exist_ok=True)
zipapp.create_archive(
    ".", target, main="bazaar_cart.app:main", interpreter="/usr/bin/env python3",
    filter=lambda path: path.parts[0] == "bazaar_cart" and "__pycache__" not in path.parts)
print(f"Built {target} ({target.stat().st_size:,} bytes)")
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 9-11

The same zipapp as python -m zipapp in versions and builds; the filter keeps only the package.

هەمان zipappـی python -m zipapp لە وەشانەکان و بیلدەکان؛ فلتەرەکە تەنیا پاکێجەکە دەهێڵێتەوە.

Commit and tag the releaseوەشانە بڵاوکراوەکە کۆمیت بکە و تاگی لێ بدەBASHType in a terminalلە تێرمیناڵدا بینووسە
git add .
git commit -m "Release 1.0.0"
git tag -a v1.0.0 -m "Bazaar Cart 1.0.0: first release"
git show v1.0.0 --no-patch
Six steps in a row: 1 version number 1.0.0 in __init__.py; 2 CHANGELOG section [1.0.0] - 2026-11-07; 3 commit 'Release 1.0.0'; 4 annotated tag v1.0.0; 5 build dist/bazaar_cart-1.0.0.pyz; 6 run the .pyz on a clean computer. A bracket over steps 1–4 says 'in Git'; over 5–6 'the artifact'.شەش هەنگاو بە ڕیز: 1 ژمارەی وەشان 1.0.0 لە __init__.py؛ 2 بەشی CHANGELOG [1.0.0] - 2026-11-07؛ 3 کۆمیتی «Release 1.0.0»؛ 4 تاگی annotated v1.0.0؛ 5 بیلدی dist/bazaar_cart-1.0.0.pyz؛ 6 جێبەجێکردنی .pyz لەسەر کۆمپیوتەرێکی پاک. کەوانەیەک لەسەر هەنگاوەکانی 1–4 دەڵێت «لە Git»؛ لەسەر 5–6 «بەرهەمی بیلد».
A release: number, changelog, commit and tag in Git, then the artifact.: ژمارە، ، و تاگ لە Git، پاشان .CêIT
Build the .pyz and run itبیلدی ⁦.pyz⁩ بکە و جێبەجێی بکە
$ python3.13 build.py
Built dist/bazaar_cart-1.0.0.pyz (8,984 bytes)

$ python3.13 dist/bazaar_cart-1.0.0.pyz
Bazaar Cart 1.0.0 - Soran's shop, Sulaimani bazaar
Add (code grams), empty to stop: walnuts 500
  Walnuts 500 g: 9,000 IQD
  Total 12,000 IQD (delivery 3,000)
Add (code grams), empty to stop: sumac 350
  Walnuts 500 g: 9,000 IQD
  Sumac 350 g: 3,000 IQD
  Total 15,000 IQD (delivery 3,000)
Add (code grams), empty to stop: 
Phone for the SMS: 0770 000 0001
[SMS to 0770 000 0001] Bazaar Cart: your order of 15,000 IQD is on its way.
Order placed: 15,000 IQD. Thank you!

Step 11: a bug report, a regression test and 1.0.1هەنگاوی ⁦11⁩: ڕاپۆرتی باگ، تاقیکردنەوەی پاشەکشە و ⁦1.0.1⁩

Two days after the release, Dilan opens issue #7 (a good bug report). Notice which bug did not come back: Bazaar Cart's real 1.0.0 shipped a rounding bug because that team had no tests yet; your test_r1_rounds_up_to_250 has guarded R1 since step 6. Tests only check what someone thought of, though, and nobody thought of the same product on two lines:

دوو ڕۆژ دوای ، دیلان ⁦#7⁩ دەکاتەوە (ڕاپۆرتێکی باشی باگ). سەرنج بدە کام نەگەڕایەوە: ⁦1.0.0⁩ـی ڕاستەقینەی Bazaar Cart خڕکردنەوەی تێدا بوو چونکە ئەو تیمە هێشتا تاقیکردنەوەی نەبوو؛ test_r1_rounds_up_to_250ـی تۆ لە هەنگاوی 6ـەوە پاسەوانی ⁦R1⁩ دەکات. بەڵام تاقیکردنەوەکان تەنیا ئەوە دەپشکنن کە کەسێک بیری لێ کردبێتەوە و کەس بیری لە هەمان کاڵا لەسەر دوو دێڕ نەکردبووەوە:

Issue #7, written by Dilanئیشووی ⁦#7⁩، نووسینی دیلانTEXT
Title: Two lines of honey take more than the stock  #7

Version: 1.0.0 (bazaar_cart-1.0.0.pyz)
Steps to reproduce:
1. Start the app; honey has 3,000 g in stock.
2. Add "honey 2000", then "honey 2000" again.
3. Confirm with any phone number.
Expected: "Honey: only 3,000 g in stock".
Actual: the order of 120,000 IQD is placed; the stock becomes -1,000 g.
Labels: bug   Assignee: Shilan   Milestone: 1.0.1
An issue card: an Open badge, title 'Two lines of honey take more than the stock #7', label bug, assignee Shilan, milestone 1.0.1. Four parts: Version 1.0.0; Steps (honey 2000 twice, confirm); Expected 'only 3,000 g in stock'; Actual: order placed, stock -1,000 g.کارتی ئیشوویەک: نیشانەی Open، ناونیشانی «Two lines of honey take more than the stock #7»، ناونیشانی bug، ڕاسپێردراو شیلان، milestone 1.0.1. چوار بەش: Version 1.0.0؛ هەنگاوەکان (honey 2000 دوو جار، پشتڕاستکردنەوە)؛ چاوەڕوانکراو «only 3,000 g in stock»؛ ڕاستەقینە: داواکاری تۆمار کرا، کۆگا -1,000 گرام.
A good bug report: version, steps, expected and actual.ڕاپۆرتێکی باشی : ، هەنگاوەکان، چاوەڕوانکراو و ڕاستەقینە.CêIT
Reproduce it with the 1.0.0 buildبە بیلدی ⁦1.0.0⁩ دووپاتەی بکەرەوە
$ python3.13 dist/bazaar_cart-1.0.0.pyz
Bazaar Cart 1.0.0 - Soran's shop, Sulaimani bazaar
Add (code grams), empty to stop: honey 2000
  Honey 2000 g: 60,000 IQD
  Total 60,000 IQD (delivery 0)
Add (code grams), empty to stop: honey 2000
  Honey 2000 g: 60,000 IQD
  Honey 2000 g: 60,000 IQD
  Total 120,000 IQD (delivery 0)
Add (code grams), empty to stop: 
Phone for the SMS: 0770 000 0002
[SMS to 0770 000 0002] Bazaar Cart: your order of 120,000 IQD is on its way.
Order placed: 120,000 IQD. Thank you!

A three-line script confirms the stock: Honey left in stock: -1000 g. The cause is in price_order(): R3 compares each line with the stock on its own, so two lines of 2,000 g both pass. BC-2's acceptance criterion had only one line per product — a gap in the requirements, not just in the code.

سکریپتێکی سێ دێڕی کۆگاکە دەسەلمێنێت: Honey left in stock: -1000 g. هۆکارەکە لە price_order()ـدایە: ⁦R3⁩ هەر دێڕێک بە تەنیا لەگەڵ کۆگا بەراورد دەکات، بۆیە دوو دێڕی ⁦2,000⁩ گرامی هەردووکیان تێدەپەڕن. ⁦BC-2⁩ تەنیا یەک دێڕی بۆ هەر کاڵایەک هەبوو — کەلێنێک لە ، نەک تەنیا لە کۆدەکەدا.

Step 11: a bug report, a regression test and 1.0.1هەنگاوی ⁦11⁩: ڕاپۆرتی باگ، تاقیکردنەوەی پاشەکشە و ⁦1.0.1⁩

Red, fix, green, release 1.0.1سوور، چاکسازی، سەوز، وەشانی بڵاوکراوە ⁦1.0.1⁩

Shilan works on a fix/ branch. First the regression test — it must fail on 1.0.0 (every bug gets a test) — then the fix: count the grams of each product over all its lines.

شیلان لەسەر fix/ کار دەکات. سەرەتا — دەبێت لەسەر ⁦1.0.0⁩ شکست بهێنێت (هەموو باگێک تاقیکردنەوەیەک وەردەگرێت) — پاشان چاکسازییەکە: گرامی هەر کاڵایەک لە هەموو دێڕەکانیدا بژمێرە.

BASHType in a terminalلە تێرمیناڵدا بینووسە
git switch -c fix/stock-two-lines
The regression test for #7تاقیکردنەوەی پاشەکشە بۆ ⁦#7⁩tests/test_services.pyPYTHON
    def test_r3_two_lines_of_one_product_count_together(self):   # issue #7
        with self.assertRaises(ValueError):
            self.service.price_order([("honey", 2000), ("honey", 2000)])
Red: python3.13 -m unittest tests.test_servicesسوور: ⁦python3.13 -m unittest tests.test_services⁩
......F..
======================================================================
FAIL: test_r3_two_lines_of_one_product_count_together (tests.test_services.OrderServiceTest.test_r3_two_lines_of_one_product_count_together)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "~/se/bazaar-cart/tests/test_services.py", line 62, in test_r3_two_lines_of_one_product_count_together
    with self.assertRaises(ValueError):
         ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AssertionError: ValueError not raised

----------------------------------------------------------------------
Ran 9 tests in 0.002s

FAILED (failures=1)
The fix (the start of price_order)چاکسازییەکە (سەرەتای ⁦price_order⁩)bazaar_cart/services.pyPYTHON
    def price_order(self, items):
        """Check every line (R2, R3) and price the order (R1, R4). Saves nothing."""
        lines, wanted = [], {}           # wanted: grams per product in this order
        for code, grams in items:
            product = self.repo.get(code)
            if product is None:
                raise ValueError(f"unknown product: {code}")
            if not MIN_GRAMS <= grams <= MAX_GRAMS:
                raise ValueError(f"{product.name}: {grams} g is outside 100-10,000 g")
            wanted[code] = wanted.get(code, 0) + grams
            if wanted[code] > product.stock_g:    # all lines of it together (#7)
                raise ValueError(f"{product.name}: only {product.stock_g:,} g in stock")
            lines.append(OrderLine(product.name, grams,
                                   line_price(product.price_per_kg, grams)))
Green, merge, release 1.0.1سەوز، بەیەککردن، وەشانی بڵاوکراوە ⁦1.0.1⁩BASHType in a terminalلە تێرمیناڵدا بینووسە
python3.13 -m unittest
git commit -am "Count all lines of a product in the stock check (fixes #7)"
git switch main
git merge fix/stock-two-lines
git branch -d fix/stock-two-lines
git commit -am "Release 1.0.1"
git tag -a v1.0.1 -m "Bazaar Cart 1.0.1: stock check fix"

The merge was a fast-forward: main had not moved, so Git only moved the name (fast-forward). The CHANGELOG got a ### Fixed line, and the commit message says fixes #7, which also closes the issue when the commit reaches GitHub's default branch.

بوو: main نەجووڵابوو، بۆیە Git تەنیا ناوەکەی جووڵاند (بەیەککردنی بەپێشخستن). CHANGELOG دێڕێکی ### Fixedـی وەرگرت و پەیامی دەڵێت ⁦fixes #7⁩، کە کاتێک دەگاتە بنەڕەتیی GitHub دادەخات.

Step 11: a bug report, a regression test and 1.0.1هەنگاوی ⁦11⁩: ڕاپۆرتی باگ، تاقیکردنەوەی پاشەکشە و ⁦1.0.1⁩

Step 12: the discount and 1.1.0هەنگاوی ⁦12⁩: داشکاندن و ⁦1.1.0⁩

BC-8 adds R5: 5 % off goods totals from 100,000 IQD, rounded up to 250 — the same discount() as in automated testing. Test first on feature/discount: the tests import discount, so the first run is red with an ImportError. Then the code, and Order gets a new field discount with the default 0, so code written for 1.0 keeps working: a MINOR version.

⁦BC-8 R5⁩ زیاد دەکات: 5 لە سەد داشکاندن بۆ کۆی کاڵای ⁦100,000⁩ دینار و زیاتر، بەرزکراوە بۆ ⁦250⁩ — هەمان discount()ـی تاقیکردنەوەی خۆکار. لەسەر feature/discount تاقیکردنەوە لە پێشەوە: تاقیکردنەوەکان discount هاوردە دەکەن، بۆیە یەکەم جێبەجێکردن بە ImportError سوورە. پاشان کۆدەکە و Order خانەیەکی نوێی discount بە بنەڕەتی 0 وەردەگرێت، بۆیە ئەو کۆدەی بۆ ⁦1.0⁩ نووسرابوو هەر کار دەکات: .

R5 in services.py⁦R5⁩ لە ⁦services.py⁩bazaar_cart/services.pyPYTHON
def discount(goods_total):
    """R5: 5 % off goods totals from 100,000 IQD, rounded up to 250 IQD."""
    if goods_total < DISCOUNT_FROM:
        return 0
    return round_up_250(goods_total * DISCOUNT_PERCENT / 100)
All 18 tests greenهەموو ⁦18⁩ تاقیکردنەوەکە سەوزنBASHType in a terminalلە تێرمیناڵدا بینووسە
python3.13 -m unittest -v
Merge, release 1.1.0 and buildبەیەککردن، وەشانی بڵاوکراوە ⁦1.1.0⁩ و بیلدBASHType in a terminalلە تێرمیناڵدا بینووسە
git commit -am "Add the 5 % discount from 100,000 IQD (R5)"
git switch main
git merge feature/discount
git branch -d feature/discount
git commit -am "Release 1.1.0"
git tag -a v1.1.0 -m "Bazaar Cart 1.1.0: discount from 100,000 IQD"
python3.13 build.py
Version 1.1.0 at workوەشان ⁦1.1.0⁩ لە کاردا
$ python3.13 dist/bazaar_cart-1.1.0.pyz
Bazaar Cart 1.1.0 - Soran's shop, Sulaimani bazaar
Add (code grams), empty to stop: honey 2000
  Honey 2000 g: 60,000 IQD
  Total 60,000 IQD (delivery 0)
Add (code grams), empty to stop: walnuts 2000
  Honey 2000 g: 60,000 IQD
  Walnuts 2000 g: 36,000 IQD
  Total 96,000 IQD (delivery 0)
Add (code grams), empty to stop: sumac 500
  Honey 2000 g: 60,000 IQD
  Walnuts 2000 g: 36,000 IQD
  Sumac 500 g: 4,000 IQD
  Total 95,000 IQD (delivery 0, discount 5,000)
Add (code grams), empty to stop: 
Phone for the SMS: 0770 000 0003
[SMS to 0770 000 0003] Bazaar Cart: your order of 95,000 IQD is on its way.
Order placed: 95,000 IQD. Thank you!
The final commit graph with tags: v1.0.0 on 'Release 1.0.0' after the merge of feature/delivery-fee; v1.0.1 on 'Release 1.0.1' after the fix for #7; v1.1.0 on 'Release 1.1.0' after the discount. Under each tag its CHANGELOG heading: Added (1.0.0), Fixed (1.0.1), Added (1.1.0).هێڵکاریی کۆتاییی کۆمیتەکان بە تاگەکانەوە: v1.0.0 لەسەر «Release 1.0.0» دوای بەیەککردنی feature/delivery-fee؛ v1.0.1 لەسەر «Release 1.0.1» دوای چاکسازیی #7؛ v1.1.0 لەسەر «Release 1.1.0» دوای داشکاندن. لە ژێر هەر تاگێک سەردێڕی CHANGELOGـەکەی: Added (1.0.0)، Fixed (1.0.1)، Added (1.1.0).
Three tags, three promises: first release, a fix, a new feature.سێ تاگ، سێ بەڵێن: یەکەم ، چاکسازییەک، تایبەتمەندییەکی نوێ.CêIT
The whole historyهەموو مێژووەکەBASHType in a terminalلە تێرمیناڵدا بینووسە
git log --oneline --graph --decorate
git tag -n

The marking checklist and the retrospectiveلیستی نمرەدان و ئاوڕدانەوە

A teacher (or the team itself) can mark the project with this checklist. Each line can be checked in the repository, not just in a report.

مامۆستا (یان خودی تیمەکە) دەتوانێت بەم لیستە نمرە بداتە پڕۆژەکە. هەر دێڕێک لە دەپشکنرێت، نەک تەنیا لە ڕاپۆرتێکدا.

Marking checklist (100 points)لیستی نمرەدان (⁦100⁩ خاڵ)
AreaبەشWhat to checkچی بپشکنرێتPointsخاڵ
Requirements and plan و پلانstories with acceptance examples, 3 non-functional requirements, MoSCoW, board, Definition of Doneچیرۆک بە نموونەی پەسەندکردن، 3 ، MoSCoW، تەختە، 15
Git historyمێژووی Gitsmall commits with clear messages; a feature branch; a resolved conflict; annotated tags بچووک بە پەیامی ڕوون؛ ؛ ناکۆکییەکی چارەسەرکراو؛ تاگی annotated15
Testsتاقیکردنەوەکانevery rule R1–R5 tested with boundaries; an integration test; a regression test; all greenهەموو یاسایەکی ⁦R1–R5⁩ بە سنوورەکانەوە تاقیکراوە؛ ؛ ؛ هەمووی سەوز25
Designدیزاینthree layers, no SQL or print in the business layer; MVP; constructor injection; one composition rootسێ ، هیچ یان print لە نییە؛ MVP؛ ؛ یەک 20
Releasecorrect version numbers, CHANGELOG, a .pyz that runsژمارە ڕاستەکانی ، CHANGELOG، ⁦.pyz⁩ـێک کە کار دەکات15
README and teamworkREADME و کاری تیمیhow to run and test; reviews; an honest retrospectiveچۆنیەتیی جێبەجێکردن و تاقیکردنەوە؛ پێداچوونەوەکان؛ ئاوڕدانەوەیەکی ڕاستگۆیانە10
A horizontal bar chart of the 100 points: Tests 25, Design 20, Requirements and plan 15, Git history 15, Release 15, README and teamwork 10.هێڵکارییەکی ستوونی ئاسۆیی بۆ 100 خاڵەکە: تاقیکردنەوەکان 25، دیزاین 20، پێداویستییەکان و پلان 15، مێژووی Git 15، وەشانی بڵاوکراوە 15، README و کاری تیمی 10.
Tests and design weigh most: they decide whether the next change is easy.تاقیکردنەوە و دیزاین زۆرترین قورسییان هەیە: ئەوان بڕیار دەدەن گۆڕانکاریی داهاتوو ئاسان دەبێت یان نا.CêIT

Retrospective questions

پرسیارەکانی ئاوڕدانەوە

  1. What went well and should stay?
  2. What slowed us down (a conflict, a late review, an unclear story)?
  3. Which bug got through, and which test or acceptance criterion would have caught it? (#7: one more example for BC-2.)
  4. What is our one change for the next sprint?
  1. چی باش بوو و دەبێت بمێنێتەوە؟
  2. چی خاوی کردینەوە (ناکۆکییەک، پێداچوونەوەیەکی دواکەوتوو، چیرۆکێکی ناڕوون)؟
  3. کام تێپەڕی و کام تاقیکردنەوە یان دەیگرت؟ (⁦#7⁩: نموونەیەکی تر بۆ ⁦BC-2⁩.)
  4. تاکە گۆڕانکاریمان بۆ داهاتوو چییە؟

Exercisesڕاهێنانەکان

ExerciseڕاهێنانWhich number next?ژمارەی داهاتوو کامە؟

Bazaar Cart is at 1.1.0. Give the next version for each change on its own: (a) a typo in the start message; (b) R6, delivery to Erbil for 6,000 IQD, while every old call still works; (c) place_order() returns an Order receipt instead of an int, so the shop's website must change.

Bazaar Cart لە ⁦1.1.0⁩ـدایە. بۆ هەر گۆڕانکارییەک بە تەنیا داهاتوو بڵێ: ⁦(a)⁩ هەڵەیەکی نووسین لە پەیامی دەستپێکدا؛ ⁦(b) R6⁩، گەیاندن بۆ هەولێر بە ⁦6,000⁩ دینار، لە کاتێکدا هەموو بانگکردنە کۆنەکان هەر کار دەکەن؛ ⁦(c)⁩ place_order() لەبری پسوولەیەکی Order دەگەڕێنێتەوە، بۆیە ماڵپەڕی دوکانەکە دەبێت بگۆڕێت.

Show answerوەڵامەکە پیشان بدە

(a) 1.1.1 — a fix (PATCH). (b) 1.2.0 — a new, backward-compatible feature (MINOR, PATCH back to 0). (c) 2.0.0 — a breaking change to the public API (MAJOR), exactly Bazaar Cart's 2.0.0 in versions and builds.

⁦(a) 1.1.1⁩ — چاکسازی ⁦(PATCH). (b) 1.2.0⁩ — تایبەتمەندییەکی نوێ کە لەگەڵ پێشوو دەگونجێت (MINOR، PATCH دەگەڕێتەوە بۆ ⁦0). (c) 2.0.0⁩ — لە API گشتییەکەدا ⁦(MAJOR)⁩، ڕێک ⁦2.0.0⁩ـی Bazaar Cart لە وەشانەکان و بیلدەکان.

ExerciseڕاهێنانThe missing acceptance exampleنموونەی پەسەندکردنی ون

Write the acceptance example for BC-2 that would have caught bug #7, in Given / When / Then form.

ئەو نموونەی پەسەندکردنەی ⁦BC-2⁩ بنووسە کە ⁦#7⁩ـی دەگرت، بە شێوەی ⁦Given / When / Then⁩.

Show answerوەڵامەکە پیشان بدە

Given 3,000 g of honey in stock, when a customer adds honey 2000 twice and confirms, then the order is refused with "Honey: only 3,000 g in stock" and the stock stays 3,000 g. It is exactly the regression test test_r3_two_lines_of_one_product_count_together.

Given ⁦3,000⁩ گرام هەنگوین لە کۆگادا، when کڕیارێک دوو جار ⁦honey 2000⁩ زیاد دەکات و پشتڕاستی دەکاتەوە، then داواکارییەکە بە «⁦Honey: only 3,000 g in stock⁩» ڕەت دەکرێتەوە و کۆگا ⁦3,000⁩ گرام دەمێنێتەوە. ئەمە ڕێک : test_r3_two_lines_of_one_product_count_together.

ExerciseڕاهێنانTest the discount on the screenداشکاندن لەسەر شاشە تاقی بکەرەوە

Write a presenter test: after honey 2000, walnuts 2000 and sumac 500, the view's total must read Total 95,000 IQD (delivery 0, discount 5,000).

تاقیکردنەوەیەکی بنووسە: دوای ⁦honey 2000⁩، ⁦walnuts 2000⁩ و ⁦sumac 500⁩، کۆی دەبێت Total 95,000 IQD (delivery 0, discount 5,000) بێت.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوtests/test_discount_text.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
import unittest

from bazaar_cart.presenter import CartPresenter
from bazaar_cart.repository import ProductRepository, open_demo_db
from bazaar_cart.services import OrderService
from tests.test_presenter import FakeCartView
from tests.test_services import FakeNotifier


class DiscountTextTest(unittest.TestCase):
    def test_the_total_shows_the_discount(self):
        view = FakeCartView()
        presenter = CartPresenter(OrderService(ProductRepository(open_demo_db()), FakeNotifier()), view)
        for code, grams in (("honey", "2000"), ("walnuts", "2000"), ("sumac", "500")):
            presenter.on_add(code, grams)
        self.assertEqual(view.total, "Total 95,000 IQD (delivery 0, discount 5,000)")
ExerciseڕاهێنانReal SMS in the shopنامەی کورتی ڕاستەقینە لە دوکانەکە

Add the SmsNotifier from the dependency injection lesson to notifiers.py, and change only app.py so that the shop (with SMS_API_KEY set) sends real messages while every other computer keeps ConsoleNotifier.

SmsNotifierـی وانەی تێخستنی وابەستەیی بۆ notifiers.py زیاد بکە و تەنیا app.py بگۆڕە بە جۆرێک کە دوکانەکە (بە SMS_API_KEYـەوە) نامەی ڕاستەقینە بنێرێت و هەموو تر ConsoleNotifier بهێڵێتەوە.

Show answerوەڵامەکە پیشان بدە
One possible answer: only app.py changesوەڵامێکی گونجاو: تەنیا ⁦app.py⁩ دەگۆڕێتbazaar_cart/app.pyPYTHON
"""Composition root: the one place where Bazaar Cart's objects are made and connected."""
import os

from . import __version__
from .cli import ConsoleCartView
from .notifiers import ConsoleNotifier, SmsNotifier
from .presenter import CartPresenter
from .repository import ProductRepository, open_demo_db
from .services import OrderService


def main():
    print(f"Bazaar Cart {__version__} - Soran's shop, Sulaimani bazaar")
    repo = ProductRepository(open_demo_db())          # data access layer
    key = os.environ.get("SMS_API_KEY")
    notifier = SmsNotifier(key) if key else ConsoleNotifier()   # real SMS only in the shop
    service = OrderService(repo, notifier)            # business layer + its notifier
    view = ConsoleCartView()                          # presentation: the view ...
    view.run(CartPresenter(service, view))            # ... and its presenter
On a laptop without SMS_API_KEYلەسەر لاپتۆپێک بەبێ ⁦SMS_API_KEY⁩
$ python3.13 -m bazaar_cart
Bazaar Cart 1.1.0 - Soran's shop, Sulaimani bazaar
Add (code grams), empty to stop: tea 250
  Black tea 250 g: 3,500 IQD
  Total 6,500 IQD (delivery 3,000)
Add (code grams), empty to stop: 
Phone for the SMS: 0770 000 0004
[SMS to 0770 000 0004] Bazaar Cart: your order of 6,500 IQD is on its way.
Order placed: 6,500 IQD. Thank you!

The business layer, the presenter and the tests do not change — that is what the composition root is for.

، و تاقیکردنەوەکان ناگۆڕێن — بۆ ئەمەیە.

Summary: the whole course in one projectپوختە: هەموو خولەکە لە یەک پڕۆژەدا

Animation in seven stations that light up one after another: story card BC-7; branch feature/delivery-fee; a test turns red; the test turns green; the branch merges into main; the tag v1.0.0 is placed; the .pyz runs and prints 'Bazaar Cart 1.0.0'.
Animation in seven stations that light up one after another: story card BC-7; branch feature/delivery-fee; a test turns red; the test turns green; the branch merges into main; the tag v1.0.0 is placed; the .pyz runs and prints 'Bazaar Cart 1.0.0'.
From a story to a running release: one rule, R4, through the whole pipeline.لە چیرۆکێکەوە بۆ کارا: یەک یاسا، ⁦R4⁩، بە هەموو زنجیرەکەدا.CêIT
  • Plan: stories with acceptance examples, MoSCoW, two sprints on a Kanban board, a Definition of Done.
  • History: small commits, a feature branch, a resolved conflict, annotated tags.
  • Quality: tests first, every layer tested, a regression test for every bug.
  • Design: three layers, MVP for the screen, constructor injection and one composition root.
  • Releases: 1.0.0, a PATCH 1.0.1 and a MINOR 1.1.0, each with a CHANGELOG section, a tag and a .pyz.
  • پلان: چیرۆک بە نموونەی پەسەندکردن، MoSCoW، دوو لەسەر تەختەی ، .
  • مێژوو: بچووک، ، ناکۆکییەکی چارەسەرکراو، تاگی annotated.
  • جۆرایەتی: تاقیکردنەوە لە پێشەوە، هەموو تاقیکراوە، بۆ هەموو .
  • دیزاین: سێ ، MVP بۆ شاشە، و یەک .
  • : ⁦1.0.0⁩، PATCHـی ⁦1.0.1⁩ و MINORـی ⁦1.1.0⁩، هەریەکە بە بەشێکی CHANGELOG، تاگێک و .pyzـێک.

What next?دواتر چی؟

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.

Appearanceڕووکار

Modeدۆخ
Styleشێواز
Coloursڕەنگەکان

Saved on this device only.تەنیا لەسەر ئەم ئامێرە پاشەکەوت دەکرێت.