0

تست نویسی در فلسک

بازدید 31

آشنایی با موضوع

تست‌کردن یکی از قسمت‌های ضروری فرآیند توسعه‌ی نرم‌افزار است که اطمینان حاصل می‌کند کد همان‌طور که انتظار می‌رود رفتار کرده و عاری از نقص باشد. در زبان برنامه‌نویسی پایتون، pytest یک چارچوب تست محبوب است که برتری‌هایی نسبت به ماژول استاندارد unittest دارد که خود از ماژول‌های تست داخلی پایتون بوده و بخشی از کتابخانه‌ی استاندارد است. pytest شامل یک نحو ساده‌تر، خروجی بهتر، فیکسچرهای قدرتمند و یک اکوسیستم غنی از پلاگین‌ها است. این آموزش شما را در راه‌اندازی یک برنامه Flask، ادغام فیکسچرهای pytest و نوشتن تست‌های واحد با استفاده از pytest راهنمایی خواهد کرد.

پیش‌نیازها

قبل از شروع، نیازمند موارد زیر خواهید بود:

  • یک سرور که روی آن سیستم‌عامل اوبونتو نصب شده و کاربری غیر-ریشه با دسترسی‌های sudo و یک دیوار آتش فعال دارید. جهت دریافت راهنمایی چگونگی انجام این کار در توزیع مورد نظر خود به لیست توزیع‌ها مراجعه کنید و از راهنمای راه‌اندازی اولیه‌ی سرور ما پیروی کنید. لطفا از کار کردن با یک [نسخه‌ی پشتیبانی‌شده](https://releases.ubuntu.com/) اوبونتو اطمینان حاصل فرمایید.
  • آشنایی با خط فرمان لینوکس.
  • درک پایه‌ای از برنامه‌نویسی پایتون و چارچوب تست pytest در پایتون.

پایتون نسخه‌ی 3.7 یا بالاتر را بر روی سیستم اوبونتوی خود نصب داشته باشید. برای یادگیری چگونگی اجرای اسکریپت پایتون در اوبونتو، می‌توانید به آموزش ما در مورد مراجعه کنید.

چرا pytest به‌عنوان جایگزینی بهتر از unittest است

pytest برتری‌هایی نسبت به چارچوب تست داخلی unittest دارد:

  • Pytest به شما اجازه می‌دهد تا تست ها را با کمترین کدهای تکراری بنویسید و از گزاره‌های ادعای ساده به جای متدهای پرحجم‌تر مورد نیاز توسط unittest استفاده کنید.
  • خروجی‌های دقیق‌تر و قابل خواندن‌تری فراهم می‌کند، که شناسایی دلیل و محل شکست یک تست را ساده‌تر می‌سازد.
  • فیکسچرهای Pytest اجازه می‌دهند که تنظیمات تست قابل انعطاف‌تری و قابل استفاده‌ی مجدد داشته باشید، در مقایسه با متدهای setUp و tearDown در unittest.
  • اجرای یک تابع تست با مجموعه‌های ورودی گوناگون را آسان می‌سازد، که در unittest به این راحتی نیست.
  • Pytest مجموعه‌ای غنی از پلاگین‌ها را دارد که قابلیت‌های آن را توسعه می‌دهند، از ابزارهای پوشش کد گرفته تا اجرای موازی تست‌ها.
  • به‌صورت خودکار فایل‌ها و توابع تست را که با قراردادهای نام‌گذاری آن مطابقت دارند، کشف می‌کند، که وقت و تلاش در مدیریت مجموعه‌های تست را صرفه‌جویی می‌کند.

با در نظر گرفتن این مزایا، pytest غالبا گزینه‌ی مورد ترجیح برای تست‌کردن مدرن پایتون است. حالا بیایید یک برنامه Flask راه‌اندازی کنیم و تست‌های واحد را با استفاده از pytest بنویسیم.

گام نخست – راه‌اندازی محیط

اوبونتو 24.04 پایتون 3 را به‌طور پیش‌فرض به همراه دارد.

ترمینال را باز کرده و دستور زیر را برای بررسی نصب Python 3 اجرا کنید:



root@ubuntu:~# python3 --version

Python 3.8.5

Code language: Bash (bash)

اگر Python 3 در حال حاضر بر روی دستگاه شما نصب شده باشد، دستور فوق نسخه کنونی نصب Python 3 را باز می‌گرداند. در صورتی که نصب نشده باشد، می‌توانید دستور زیر را برای نصب Python 3 اجرا کنید:



root@ubuntu:~# sudo apt install python3

Code language: Bash (bash)

سپس، باید نصاب بسته pip را بر روی سیستم خود نصب کنید:

root@ubuntu:~# sudo apt install python3-pip
Code language: Bash (bash)

پس از نصب pip، حال بیایید Flask را نصب کنیم.

گام دوم – ایجاد یک برنامه Flask

برای شروع، یک برنامه Flask ساده ایجاد کنیم. یک دایرکتوری جدید برای پروژه خود ایجاد کرده و وارد آن شوید:



root@ubuntu:~# mkdir flask_testing_app

root@ubuntu:~# cd flask_testing_app

Code language: Bash (bash)

هم اکنون، بیایید یک محیط مجازی ایجاد کرده و فعال کنیم تا وابستگی‌ها را مدیریت کنیم:



root@ubuntu:~# python3 -m venv venv

root@ubuntu:~# source venv/bin/activate

Code language: Bash (bash)

Flask را با استفاده از pip نصب کنید:

<code data-shcb-language-name="bash">

root@ubuntu:~# pip install Flask

</code>Code language: Bash (bash)

اکنون، بیایید یک برنامه Flask ساده ایجاد کنیم. یک فایل جدید با نام app.py ایجاد کرده و کد زیر را به آن اضافه کنید:



# app.py

from flask import Flask, jsonify<code data-shcb-language-name="python">
app = Flask(__name__)</code>

@app.route('/')
 def home():
 return jsonify(message="Hello, Flask!")

@app.route('/about')
 def about():
 return jsonify(message="This is the About page")

@app.route('/multiply/<int:x>/<int:y>')

def multiply(x, y):

    result = x * y

    return jsonify(result=result)</int:y></int:x>

if __name__ == '__main__':

    app.run(debug=True)

Code language: Python (python)

این برنامه دارای سه مسیر است:

  • /: یک پیام ساده “Hello, Flask!” باز می‌گرداند.
  • /about: یک پیام ساده “This is the About page” باز می‌گرداند.
  • /multiply/<int:x>/<int:y>: دو عدد صحیح را در هم ضرب کرده و نتیجه را بازمی‌گرداند.

برای اجرای برنامه، دستور زیر را اجرا کنید:



root@ubuntu:~# flask run



 * Serving Flask app "app" (lazy loading)

 * Environment: production

   WARNING: This is a development server

 Do not use it in a production deployment

<code data-shcb-language-name="bash"> Use a production WSGI server instead</code>

 * Debug mode: on

 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Code language: Bash (bash)

از خروجی بالا می‌توان متوجه شد که سرور در حال اجرا بر روی http://127.0.0.1 بوده و بر روی پورت 5000 به دنبال درخواست‌ها است. یک کنسول اوبونتو دیگر را باز کرده و دستورات curl زیر را یک به یک اجرا کنید:

  • GET: curl http://127.0.0.1:5000/
  • GET: curl http://127.0.0.1:5000/about
  • GET: curl http://127.0.0.1:5000/multiply/10/20

بیایید بدانیم این درخواست‌های GET چه کاری انجام می‌دهند:

  1. curl http://127.0.0.1:5000/:

    این یک درخواست GET به مسیر ریشه (‘/’) برنامه Flask ما می‌فرستد. سرور با یک شیء JSON حاوی پیام “Hello, Flask!” پاسخ می‌دهد، که عملکرد اصلی مسیر اصلی ما را نشان می‌دهد.
  2. curl http://127.0.0.1:5000/about:

    این یک درخواست GET به مسیر /about ارسال می‌کند.

درخواست‌های GET

سرور با یک JSON که پیام “This is the About page” را شامل می‌شود، پاسخ می‌دهد. این نشان می‌دهد که مسیر ما به درستی عمل می‌کند.

درخواست دیگر:

curl http://127.0.0.1:5000/multiply/10/20

این دستور یک درخواست GET را به مسیر /multiply با دو پارامتر: 10 و 20 می‌فرستد. سرور این اعداد را ضرب کرده و با یک JSON که نتیجه (200) را دارد، پاسخ می‌دهد. این نشان می‌دهد که مسیر multiply می‌تواند به درستی پارامترهای URL را پردازش کند و محاسبات را انجام دهد.

این درخواست‌های GET به ما امکان می‌دهند تا با API اند‌پوینت‌های برنامه Flask خود تعامل داشته باشیم، اطلاعات را بازیابی کنیم یا عملکردهایی را بر روی سرور بدون تغییر هیچ داده‌ای فعال کنیم. این‌ها برای فراخوانی داده‌ها، تست کردن عملکرد اند‌پوینت‌ها و تأیید اینکه مسیرهای ما همانطور که انتظار داریم پاسخ می‌دهند، مفید هستند.

بیایید هر یک از این درخواست‌های GET را عملی ببینیم:

curl http://127.0.0.1:5000/

{"message":"Hello, Flask!"}
curl http://127.0.0.1:5000/about
{"message":"This is the About page"}
curl http://127.0.0.1:5000/multiply/10/20
<code data-shcb-language-name="bash">{"result":200}</code>Code language: JavaScript (javascript)

گام ۳ – نصب pytest و نوشتن اولین تست

حال که شما یک برنامه ابتدایی Flask دارید، بیایید pytest را نصب کنیم و چند تست واحد بنویسیم.

نصب pytest با استفاده از pip:

pip install pytest

ایجاد یک پوشه تست برای ذخیره فایل‌های تست شما:

mkdir tests

حالا، یک فایل جدید به نام test_app.py ایجاد کنید و کدهای زیر را اضافه نمایید:

# Import sys module for modifying Python's runtime environment

import sys

# Import os module for interacting with the operating system

import os
# Add the parent directory to sys.path

sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Import the Flask app instance from the main app file
 from app import app
 # Import pytest for writing and running tests
 import pytest

@pytest.fixture
 def client():
 """یک مشتری تست برای اپ."""
 with app.test_client() as client:
 yield client

def test_home(client):
 """تست کردن مسیر home."""
 response = client.get('/')
 assert response.status_code == 200
 assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
 """تست کردن مسیر about."""
 response = client.get('/about')
 assert response.status_code == 200
 assert response.json == {"message": "This is the About page"}

def test_multiply(client):
 """تست کردن مسیر multiply با ورودی معتبر."""
 response = client.get('/multiply/3/4')
 assert response.status_code == 200
 assert response.json == {"result": 12}

def test_multiply_invalid_input(client):

    """تست کردن مسیر multiply با ورودی نامعتبر."""

    response = client.get('/multiply/three/four')

    assert response.status_code == 200

 <title>تست اپلیکیشن Flask با استفاده از pytest - بخش 4</title>

status_code == 404

def test_non_existent_route(client):

    """تست برای مسیری غیرموجود"""

    response = client.get('/non-existent')

    assert response.status_code == 404

Code language: Python (python)

بیایید عملکرد توابع موجود در این فایل تست را تجزیه و تحلیل کنیم:




  1. @pytest.fixture

    def client():


    این یک fixture در pytest است که یک کلاینت تست برای اپلیکیشن Flask ما ایجاد می‌کند. این تابع با استفاده از متود app.test_client() یک کلاینت می‌سازد که می‌تواند درخواست‌هایی را به اپلیکیشن ما بفرستد بدون اجرای واقعی سرور. دستور yield اجازه می‌دهد تا کلاینت در تست‌ها مورد استفاده قرار گیرد و بعد از هر تست به درستی بسته شود.





  2. def test_home(client):


    این تابع مسیر اصلی (یعنی /) اپلیکیشن ما را تست می‌کند. این تست درخواست GET را به مسیر با استفاده از کلاینت تست ارسال می‌کند، سپس ادعا می‌کند که کد وضعیت پاسخ 200 (OK) است و که پاسخ JSON با پیام مورد انتظار مطابقت دارد.





  3. def test_about(client):


    مشابه test_home، این تابع مسیر درباره (یعنی /about) را تست می‌کند. این تابع برای کد وضعیت 200 بررسی می‌کند و محتوای پاسخ JSON را تایید می‌کند.





  4. def test_multiply(client):


    این تابع مسیر ضرب کردن با ورودی معتبر (یعنی /multiply/3/4) را تست می‌کند. این بررسی می‌کند که کد وضعیت 200 است و پاسخ JSON حاوی نتیجه صحیح ضرب می‌باشد.





  5. def test_multiply_invalid_input(client):


    این تابع مسیر ضرب کردن با ورودی نامعتبر (یعنی multiply/three/four) را تست می‌کند. این بررسی می‌کند که کد وضعیت 404 (یافت نشد) باشد، که رفتار مورد انتظار است وقتی که مسیر نمی‌تواند ورودی‌های متنی را با پارامترهای اعداد صحیح مورد نیاز متناظر کند.





  6. def test_non_existent_route(client):


    این تابع رفتار اپلیکیشن را زمانی که به یک مسیر غیرموجود دسترسی پیدا می‌شود تست می‌کند. این درخواست GET را به /non-existent ارسال می‌کند، که در اپلیکیشن Flask ما تعریف نشده است. تست ادعا می‌کند که کد وضعیت پاسخ 404 (یافت نشد) است، که این اطمینان می‌دهد که اپلیکیشن ما به درستی درخواست‌ها به مسیرهای تعریف نشده را مدیریت می‌کند.


این تست‌ها عملکرد پایه‌ای اپلیکیشن Flask ما را پوشش می‌دهند، اطمینان حاصل می‌کنند که هر مسیر به درستی به ورودی‌های معتبر پاسخ می‌دهد و اینکه مسیر ضرب کردن به درستی ورودی‌های نامعتبر را مدیریت می‌کند. با استفاده از pytest، به راحتی می‌توانیم این تست‌ها را اجرا کنیم تا تایید کنیم که اپلیکیشن ما به شکل مورد انتظار کار می‌کند.

گام چهارم – اجرای تست‌ها

برای اجرای تست‌ها، دستور زیر را اجرا کنید:


root@ubuntu:~# pytest

به صورت پیش‌فرض، فرآیند کشف pytest پوشه فعلی و زیرپوشه‌های آن را به صورت بازگشتی برای فایل‌هایی که با نام‌هایی شروع می‌شوند که یا با **“test_”** یا به **“_test”** ختم می‌شوند، اسکن می‌کند. تست‌هایی که در آن فایل‌ها یافت می‌شوند، سپس اجرا می‌گردند. باید خروجی مشابه به موارد زیر را ببینید:




platform linux -- Python 3.8.5, pytest-6.2.2, pluggy-0.13.1

rootdir: /home/user/flask_testing_app

collected 5 items

tests/test_app.py ...
<title>استفاده از Fixtures در pytest - قسمت 5</title>Code language: HTML, XML (xml)

مرحله 5: استفاده از Fixtures در pytest

Fixtures توابعی هستند که برای فراهم کردن داده‌ها یا منابع به تست‌ها استفاده می‌شوند. می‌توانند برای راه‌اندازی و جمع‌آوری محیط‌های تست، بارگذاری داده‌ها، یا انجام وظایف دیگر نصب، به کار برده شوند. در pytest، Fixtures با استفاده از دکوراتور @pytest.fixture تعریف می‌گردند.

در اینجا نحوه بهبود Fixture موجود آمده است. Fixture موجود برای کلاینت را برای استفاده از منطق راه‌اندازی و جمع‌آوری به روز کنید:



@pytest.fixture

def client():

    """Set up a test client for the app with setup and teardown logic"""

    print("\nSetting up the test client")

    with app.test_client() as client:

        yield client  # This is where the testing happens

    print("Tearing down the test client")


def test_home(client):

    """Test the home route"""

    response = client.get('/')

    assert response.status_code == 200

    assert response.json == {"message": "Hello, Flask!"}

def test_about(client):
 """Test the about route"""
 response = client.get('/about')
 assert response.status_code == 200
 assert response.json == {"message": "This is the About page"}

def test_multiply(client):
 """Test the multiply route with valid input"""
 response = client.get('/multiply/3/4')
 assert response.status_code == 200
 assert response.json == {"result": 12}

def test_multiply_invalid_input(client):
 """Test the multiply route with invalid input"""
 response = client.get('/multiply/three/four')
 assert response.status_code == 404

def test_non_existent_route(client):

    """Test for a non-existent route"""

    response = client.get('/non-existent')

    assert response.status_code == 404

Code language: Python (python)

این تنظیمات خروجی‌های print را اضافه می‌کند تا مراحل راه‌اندازی و جمع‌آوری در خروجی تست نمایش داده شوند. این‌ها ممکن است با کد مدیریت منابع واقعی در صورت لزوم جایگزین شوند.

بیایید دوباره تست‌ها را اجرا کنیم:


root@ubuntu:~# pytest -vs

فلگ -v سطح جزئیات را افزایش می‌دهد، و پرچم -s اجازه می‌دهد که خروجی‌های print در خروجی کنسول نمایش داده شوند.

شما باید خروجی زیر را مشاهده کنید:



platform linux -- Python 3.8.3, pytest-6.2.2, pluggy-0.13.1

rootdir: /home/user/flask_testing_app, cachedir: .pytest_cache
collected 5 items                                                                                          
tests/test_app.py::test_home
 Setting up the test client
 PASSED
 Tearing down the test client

tests/test_app.py::test_about
 Setting up the test client
 PASSED
 Tearing down the test client

tests/test_app.py::test_multiply
 Setting up the test client
 PASSED
 Tearing down the test client

tests/test_app.py::test_multiply_invalid_input
 Setting up the test client
 PASSED
 Tearing down the test client

tests/test_app.py::test_non_existent_route

Setting up the test client

PASSED

Tearing down the test client


 Code language: PHP (php)

قدم 6: افزودن یک مورد آزمایش ناموفق

بگذارید یک مورد آزمایش ناموفق را به فایل آزمایش موجود اضافه کنیم. فایل test_app.py را تغییر داده و تابع زیر را در انتهای آن برای ایجاد یک مورد آزمایش ناموفق برای نتیجه‌ی نادرست افزوده کنید:

def test_multiply_edge_cases(client):

    """Test the multiply route with edge cases to demonstrate failing tests"""

    # آزمایش با صفر

    response = client.get('/multiply/0/5')

    assert response.status_code == 200

    assert response.json == {"result": 0}
    # آزمایش با اعداد بزرگ (این ممکن است در صورت عدم مدیریت صحیح شکست بخورد)

    response = client.get('/multiply/1000000/1000000')

    assert response.status_code == 200

    assert response.json == {"result": 1000000000000}

    # آزمایش ناموفق عمدی: نتیجه نادرست

    response = client.get('/multiply/2/3')

    assert response.status_code == 200

    assert response.json == {"result": 7}, "این آزمایش باید به منظور نمایش یک مورد ناموفق شکست بخورد"

Code language: Python (python)

بیایید تابع test_multiply_edge_cases را تجزیه و تحلیل کنیم و توضیح دهیم که هر بخش چه کاری انجام می‌دهد:

  1. آزمایش با صفر:

    این آزمایش بررسی می‌کند که آیا تابع ضرب به درستی ضرب در صفر را مدیریت می‌کند. انتظار داریم نتیجه 0 باشد هنگام ضرب هر عددی در صفر. این یک مورد حاشیه‌ی مهم برای آزمایش است زیرا برخی از پیاده‌سازی‌ها ممکن است با ضرب صفر مشکل داشته باشند.
  2. آزمایش با اعداد بزرگ:

    این آزمایش بررسی می‌کند که آیا تابع ضرب قادر به مدیریت اعداد بسیار بزرگ بدون مشکل راندگی یا دقت است. ما دو میلیون را در یک میلیون ضرب می‌کنیم و انتظار داریم نتیجه یک تریلیون باشد. این تست بسیار مهم است زیرا حد بالای قابلیت تابع ضرب را بررسی می‌کند. توجه داشته باشید که این ممکن است شکست بخورد اگر پیاده‌سازی سرور اعداد بزرگ را به درستی مدیریت نکند، که می‌تواند به نیاز به کتابخانه‌های اعداد بزرگ یا یک نوع داده متفاوت اشاره داشته باشد.
  3. آزمایش ناموفق عمدی:

    این آزمایش عمداً برای شکست برنامه‌ریزی شده است. بررسی می‌کند که آیا ۲ ضربدر ۳ برابر با ۷ است، که نادرست است. هدف از این آزمایش نمایش نحوه‌ی ظاهر شدن یک آزمایش ناموفق در خروجی آزمایش است. این کمک می‌کند تا درک کنیم چگونه می‌توان آزمایش‌های ناموفق را شناسایی و عیب‌زدایی کرد، که یک مهارت ضروری در توسعه‌ی محوری آزمایش و فرآیندهای عیب‌زدایی است.

با ادراج این موارد حاشیه و یک شکست عمدی، شما نه تنها عملکرد پایه‌ای مسیر ضرب خود را آزمایش می‌کنید بلکه رفتار آن را تحت شرایط بی سابقه و نحوه گزارش خطاها نیز بررسی می‌کنید. این رویکرد برای آزمایش به منظور اطمینان از استحکام و قابلیت اطمینان برنامه‌ی ما کمک می‌کند.

بیایید برای دوباره آزمایش اجرا را امتحان کنیم:

شکست‌ها

# مشتری Flask

client = <FlaskClient <Flask 'app'>><code data-shcb-language-name="python">
</code>    def test_multiply_edge_cases(client):

        """آزمون مسیر ضرب با موارد حدی برای نمایش تست‌های ناموفق"""

        # تست با صفر

        response = client.get('/multiply/0/5')

        assert response.status_code == 200

        assert response.json == {"result": 0}Code language: Python (python)
        # تست با اعداد بزرگ (ممکن است در صورت عدم مدیریت مناسب شکست بخورد)

        response = client.get('/multiply/1000000/1000000')

        assert response.status_code == 200

        assert response.json == {"result": 1000000000000}Code language: PHP (php)
        # تست ناموفق عمدی: نتیجه نادرست

        response = client.get('/multiply/2/3')

        assert response.status_code == 200

>       assert response.json == {"result": 7}, "این تست باید شکست بخورد تا یک مورد شکست را نمایش دهد"

E       AssertionError: این تست باید شکست بخورد تا یک مورد شکست را نمایش دهد

E       assert {'result': 6} == {'result': 7}

E

E         آیتم‌های متفاوت:

E         {'result': 6} != {'result': 7}

E

E         تفاوت کامل:

E           {

E         -     'result': 7,Code language: PHP (php)

خروجی کامل کوتاه شد (4 خط پنهان)، برای نمایش ‘-vv’ استفاده کنید.

tests/test_app.py:61: AssertionErrorCode language: HTTP (http)

FAILED tests/test_app.py::test_multiply_edge_cases – AssertionError: این تست باید شکست بخورد تا یک مورد شکست را نمایش دهد

خروجی تست

1 ناموفق, 5 موفق در 0.32 ثانیه بخش ۸: راه‌اندازی تست‌های واحد برای برنامه‌های Flask

شکست عمدی تست

این شکست عمدی برای نمایش نحوه‌ی گزارش خطاهای تست و اطلاعات ارائه شده در پیام خطا مفید است. این پیام دقیقا نشان می‌دهد که خطای انتظاری در کدام خط رخ داده، مقادیر مورد انتظار و واقعی چه هستند و تفاوت بین آن دو چقدر است.

در یک سناریوی واقعی، شما کد را برای قبولی تست اصلاح می‌کنید یا در صورت نادرست بودن نتیجه مورد انتظار، تست را تنظیم مجدد می‌کنید. اما در این مورد، شکست تست برای اهداف آموزشی عمدی بوده است.

نتیجه‌گیری

در این آموزش، ما نحوه راه‌اندازی تست‌های واحد برای یک برنامه Flask با استفاده از `pytest`، ادغام `pytest` فیکسچرها و نمایش شکلی از شکست یک تست را پوشش دادیم. با پیروی از این مراحل، شما می‌توانید اطمینان حاصل کنید که برنامه‌های Flask شما قابل اعتماد و قابل نگهداری هستند، اشکالات را به حداقل می‌رسانند و کیفیت کد را افزایش می‌دهند.

شما می‌توانید برای یادگیری بیشتر به مستندات رسمی [Flask](https://flask.palletsprojects.com/en/3.0.x/) و [Pytest](https://docs.pytest.org/en/stable/) مراجعه کنید.

نظرات کاربران

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *