Всё, что было раньше, — инструменты. Теперь три задачи, ради которых их и брали.
Разница двух наборов ищется вычитанием множеств: слева — чего не хватает, справа — что появилось лишнего.
Посчитать то, чего нет на экране
Интерфейс показывает список заказов, а вопрос звучит иначе: «на какую сумму заказов в статусе PAID за день?» Такой цифры на экране нет, а в данных — есть.
живой пример
заказы = [
{"id": "ord-042", "status": "PAID", "total": 4990},
{"id": "ord-043", "status": "CANCELLED", "total": 890},
{"id": "ord-044", "status": "PAID", "total": 1780},
]
оплаченные = [з for з in заказы if з["status"] == "PAID"]
сумма = sum(з["total"] for з in оплаченные)
print(f"Оплаченных: {len(оплаченные)}, на сумму {сумма}")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
sum() складывает числа из перечня. Рядом живут min(), max() и len() —
этой четвёрки хватает для большинства сводок.
Считать по группам удобно словарём-накопителем:
живой пример
заказы = [{"status": "PAID"}, {"status": "CANCELLED"}, {"status": "PAID"}]
по_статусам = {}
for заказ in заказы:
статус = заказ["status"]
по_статусам[статус] = по_статусам.get(статус, 0) + 1
print(по_статусам) # {'PAID': 2, 'CANCELLED': 1}
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
get(статус, 0) здесь и держит всю конструкцию: для нового статуса счётчика ещё
нет, и вместо ошибки берётся ноль.
Отработать здесь: Проверить поле ответа · Сумма в отчёт · Проверить формат почты
Сверить итог с составом
Сумма позиций должна совпадать с итогом заказа. На экране такое расхождение не видно — его находят только счётом.
живой пример
# ошибка: пример падает намеренно — у второго заказа итог не сходится
заказы = [
{"id": "ord-042", "items": [{"qty": 1, "price": 4990}], "total": 4990},
{"id": "ord-043", "items": [{"qty": 2, "price": 890}], "total": 1000},
]
def сумма_сходится(заказ):
посчитано = sum(п["qty"] * п["price"] for п in заказ["items"])
return посчитано == заказ["total"]
расхождения = [з["id"] for з in заказы if not сумма_сходится(з)]
assert not расхождения, f"итог не сходится у заказов: {расхождения}"
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Главное в последней строке — что попадёт в сообщение об ошибке:
живой пример
расхождения = ["ord-042", "ord-107"]
# так не надо: видно, сколько заказов сломано, но не видно какие
print(f"итог не сходится у {len(расхождения)} заказов")
# так надо: по номерам заказ сразу открывают и смотрят
print(f"итог не сходится у заказов: {расхождения}")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
С дробями осторожно: 0.1 + 0.2 в любом языке не равно ровно 0.3. Деньги
поэтому считают либо в копейках целыми числами, либо сравнивают с допуском:
живой пример
# так не надо: дробные числа не сравнивают «в лоб»
print(0.1 + 0.2 == 0.3)
# так надо: разница меньше копейки — значит, сошлось
print(abs((0.1 + 0.2) - 0.3) < 0.01)
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Отработать здесь: Сходится ли итог заказа
Найти, что пропало и что лишнее
Две выгрузки: из интерфейса и из базы, из старой версии и из новой. Чего не хватает и что лишнее? Списками это неудобно, множествами — одна строка.
Множество — набор без порядка и без повторов:
живой пример
было = {"ord-01", "ord-02", "ord-03"}
стало = {"ord-02", "ord-03", "ord-04"}
print(было - стало) # {'ord-01'} — пропало
print(стало - было) # {'ord-04'} — появилось
print(было & стало) # есть в обоих
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Отсюда и главная причина не сверять списками:
живой пример
# так не надо: порядок другой — значит, не равны, хотя состав тот же
print(["ord-01", "ord-02"] == ["ord-02", "ord-01"])
# так надо: множеству порядок безразличен
print({"ord-01", "ord-02"} == {"ord-02", "ord-01"})
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Из списка словарей множество собирают по ключевому полю:
живой пример
база = [{"id": "ord-042"}, {"id": "ord-043"}]
выгрузка = [{"id": "ord-042"}, {"id": "ord-043"}]
в_базе = {з["id"] for з in база}
в_выгрузке = {з["id"] for з in выгрузка}
пропали = в_базе - в_выгрузке
лишние = в_выгрузке - в_базе
assert not пропали, f"нет в выгрузке: {sorted(пропали)}"
assert not лишние, f"лишние в выгрузке: {sorted(лишние)}"
print("состав совпал")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
sorted() в сообщении не украшение: у множества нет порядка, и без сортировки
одно и то же расхождение печатается каждый раз по-разному.
Отработать здесь: Что пропало и что лишнее · Свести две выгрузки
Сравнить не только состав, но и содержимое
Совпадения номеров мало: заказ может быть на месте, но с другой суммой. Тогда сверяют поля:
живой пример
база = [{"id": "ord-042", "total": 4990}]
выгрузка = [{"id": "ord-042", "total": 4990}]
эталон = {з["id"]: з for з in база} # словарь по номеру
for заказ in выгрузка:
ожидаем = эталон[заказ["id"]]
assert заказ["total"] == ожидаем["total"], (
f"{заказ['id']}: в выгрузке {заказ['total']}, в базе {ожидаем['total']}")
print("суммы совпали")
Запустить
Запуск примеров доступен в платном доступе. Там этот же код выполняется прямо в статье: редактор, запуск и проверка рядом с абзацем. Три дня бесплатно →
Строка со словарём эталон — приём, который стоит запомнить: список
превращается в словарь по ключу, и дальше элемент достаётся сразу, без перебора.
Отработать здесь: Сверить ответ с ожидаемым
Коротко
sum(),len(),min(),max()закрывают большинство сводок; счёт по группам — словарь-накопитель сget(ключ, 0).- Сверка итога с составом ловит расхождения, невидимые на экране; в сообщение кладите номера проблемных записей, а не их количество.
- Деньги в дробных числах сравнивают с допуском либо считают в копейках.
- Разницу двух наборов ищут множествами:
было - сталоистало - было; списки на такое сравнение не годятся — им важен порядок. - Список превращают в словарь по ключу, когда нужно быстро находить пару.
Что почитать дальше
- pytest: первый настоящий тест — как это оформляют в проекте.
- Автотесты на API — откуда берётся вторая выгрузка, когда данные отдаёт сервис.
- SQL для тестировщиков — вторая половина той же работы: данные берут из базы.