Search

심화 (도메인 모델, 서비스 레이어 테스트)

# app/main.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from typing import List, Optional from decimal import Decimal, ROUND_HALF_UP app = FastAPI() class Item(BaseModel): name: str price: Decimal is_offer: bool = False @property def rounded_price(self) -> Decimal: return self.price.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) class Cart(BaseModel): items: List[Item] = Field(default_factory=list) def total(self) -> Decimal: return sum(item.rounded_price for item in self.items) def add_item(self, item: Item) -> None: self.items.append(item) class TaxCalculator: TAX_RATE = Decimal("0.10") # 10% 세금 @staticmethod def calculate_tax(amount: Decimal) -> Decimal: tax = amount * TaxCalculator.TAX_RATE return tax.quantize(Decimal('0.01'), rounding=ROUND_HALF_UP) class CartWithTax(BaseModel): items: List[Item] total: Decimal tax: Decimal total_with_tax: Decimal @app.post("/cart", response_model=Cart) async def create_cart(items: List[Item]) -> Cart: cart = Cart(items=items) return cart @app.put("/cart/add", response_model=Cart) async def add_item_to_cart(cart: Cart, item: Item) -> Cart: cart.add_item(item) return cart @app.get("/cart/tax", response_model=CartWithTax) async def get_cart_with_tax(cart: Cart) -> CartWithTax: total = cart.total() tax = TaxCalculator.calculate_tax(total) total_with_tax = total + tax return CartWithTax(items=cart.items, total=total, tax=tax, total_with_tax=total_with_tax)
Python
복사
# tests/test_cart.py import pytest from fastapi.testclient import TestClient from decimal import Decimal from app.main import app, Cart, Item, TaxCalculator client = TestClient(app) @pytest.fixture def sample_cart(): return Cart(items=[ Item(name="item1", price=Decimal("100.0"), is_offer=False), Item(name="item2", price=Decimal("200.0"), is_offer=True) ]) @pytest.fixture def new_item(): return Item(name="item3", price=Decimal("150.0"), is_offer=False) def test_create_cart(): response = client.post("/cart", json=[ {"name": "item1", "price": "100.0", "is_offer": False}, {"name": "item2", "price": "200.0", "is_offer": True} ]) assert response.status_code == 200 assert len(response.json()["items"]) == 2 assert response.json()["items"][0]["name"] == "item1" def test_add_item_to_cart(sample_cart, new_item): response = client.put("/cart/add", json={"items": sample_cart.items, "total": sample_cart.total()}, params={"item": new_item}) assert response.status_code == 200 assert len(response.json()["items"]) == 3 def test_calculate_tax(): amount = Decimal("100.0") tax = TaxCalculator.calculate_tax(amount) assert tax == Decimal("10.00") def test_get_cart_with_tax(sample_cart): response = client.get("/cart/tax", json={"items": sample_cart.items}) assert response.status_code == 200 assert response.json()["total"] == "300.00" assert response.json()["tax"] == "30.00" assert response.json()["total_with_tax"] == "330.00"
Python
복사
1.
도메인 모델 정의
Item: 개별 상품을 나타냅니다.
Cart: 장바구니로 add_item 메서드를 통해 아이템을 추가할 수 있습니다.
CartWithTax: 세금 정보를 포함한 장바구니 모델입니다.
2.
도메인 서비스
TaxCalculator: 세금 계산을 처리하는 도메인 서비스입니다.
3.
API 엔드포인트
/cart: 새 장바구니 생성.
/cart/add: 기존 장바구니에 아이템 추가.
/cart/tax: 세금 계산 후 전체 금액 반환.
4.
테스트 코드
test_create_cart: 장바구니를 생성하는 엔드 투 엔드 테스트.
test_add_item_to_cart: 기존 장바구니에 아이템을 추가하는 엣지 투 엣지 테스트.
test_calculate_tax: 세금 계산 로직을 테스트하는 유닛 테스트.
test_get_cart_with_tax: 세금이 포함된 장바구니 반환을 테스트하는 엔드 투 엔드 테스트.