ข้อมูลพื้นฐานเกี่ยวกับ Pytest

เสร็จสมบูรณ์เมื่อ

มาเริ่มทดสอบกับ Pytest กันเลย ดังที่เราได้กล่าวไว้ในหน่วยก่อนหน้า Pytest สามารถกําหนดค่าได้สูงและสามารถจัดการชุดการทดสอบที่ซับซ้อน แต่ไม่จําเป็นต้องใช้มากในการเริ่มเขียนการทดสอบ ในความเป็นจริงเฟรมเวิร์กที่ง่ายขึ้นช่วยให้คุณสามารถเขียนการทดสอบได้ดีขึ้น

ในตอนท้ายของส่วนนี้คุณควรมีทุกสิ่งที่คุณต้องเริ่มเขียนการทดสอบแรกของคุณและเรียกใช้ด้วย Pytest

ข้อ ตกลง

ก่อนที่จะดําดิ่งลงไปในการทดสอบการเขียนเราต้องครอบคลุมบางมาตรฐานการทดสอบที่ Pytest อาศัย

ไม่มีกฎที่ยากลําบากเกี่ยวกับไฟล์ทดสอบ ไดเรกทอรีทดสอบ หรือเค้าโครงการทดสอบทั่วไปใน Python เมื่อรู้กฎเหล่านี้คุณสามารถใช้ประโยชน์จากการค้นหาการทดสอบโดยอัตโนมัติและการดําเนินการโดยไม่จําเป็นต้องมีการกําหนดค่าเพิ่มเติม

ทดสอบไดเรกทอรีและไฟล์ทดสอบ

ไดเรกทอรีหลักสําหรับการทดสอบคือการทดสอบ ไดเรกทอรี คุณสามารถวางไดเรกทอรีนี้ที่ระดับรากของโครงการ แต่ก็ไม่ใช่เรื่องผิดปกติที่จะเห็นควบคู่ไปกับมอดูลโค้ด

โน้ต

ในมอดูลนี้ เราจะเริ่มต้นใช้ การทดสอบ ที่รากของโครงการ

มาดูกันว่ารากของโครงการ Python ขนาดเล็กชื่อ jformat มีลักษณะอย่างไร:

.
├── README.md
├── jformat
│   ├── __init__.py
│   └── main.py
├── setup.py
└── tests
    └── test_main.py

การทดสอบ ไดเรกทอรีอยู่ที่รากของโครงการที่มีไฟล์ทดสอบเดียว ในกรณีนี้ จะเรียกว่าไฟล์ทดสอบ test_main.py ตัวอย่างนี้แสดงให้เห็นถึงแบบแผนสําคัญสองแบบ:

  • ใช้การทดสอบ ไดเรกทอรีเพื่อวางไฟล์ทดสอบและไดเรกทอรีการทดสอบที่ซ้อนกัน
  • ไฟล์ทดสอบคํานําหน้าด้วย ทดสอบ คํานําหน้าบ่งชี้ว่าไฟล์ประกอบด้วยโค้ดทดสอบ

ความระมัดระวัง

หลีกเลี่ยงการใช้ test (ฟอร์มเอกพจน์) เป็นชื่อไดเรกทอรี ชื่อ test เป็นโมดูล Python ดังนั้นการสร้างไดเรกทอรีที่ชื่อเดียวกันจะแทนที่มัน ใช้ tests พหูพจน์แทนเสมอ

ฟังก์ชัน Test

อาร์กิวเมนต์ที่แข็งแกร่งสําหรับการใช้ Pytest คือช่วยให้คุณสามารถเขียนฟังก์ชันการทดสอบได้ คล้ายกับไฟล์ทดสอบ ฟังก์ชันทดสอบต้องขึ้นต้นด้วย test_ คํานําหน้า test_ ช่วยให้แน่ใจว่า Pytest จะรวบรวมการทดสอบและดําเนินการ

นี่คือลักษณะของฟังก์ชันการทดสอบอย่างง่าย:

def test_main():
    assert "a string value" == "a string value"

โน้ต

หากคุณคุ้นเคยกับ unittestอาจเป็นเรื่องน่าประหลาดใจที่เห็นการใช้ assert ในฟังก์ชันการทดสอบ เราครอบคลุม asserts ธรรมดาในรายละเอียดเพิ่มเติมในภายหลัง แต่ด้วย Pytest คุณจะได้รับการรายงานความล้มเหลวมากมายพร้อมข้อความแฝงธรรมดา

ทดสอบคลาสและวิธีการทดสอบ

คล้ายกับหลักทั่วไปสําหรับไฟล์และฟังก์ชันคลาสการทดสอบและวิธีการทดสอบใช้หลักทั่วไปต่อไปนี้:

  • คลาสการทดสอบจะถูกขึ้นต้นด้วย Test
  • วิธีการทดสอบมีคํานําหน้า test_

ความแตกต่างที่สําคัญของไลบรารี unittest ของ Python คือไม่จําเป็นต้องรับช่วงมรดก

ตัวอย่างต่อไปนี้ใช้คํานําหน้าเหล่านี้และมาตรฐานการตั้งชื่อ Python อื่น ๆ สําหรับคลาสและวิธีการ ซึ่งแสดงให้เห็นถึงระดับการทดสอบขนาดเล็กที่กําลังตรวจสอบชื่อผู้ใช้ในแอปพลิเคชัน

class TestUser:

    def test_username(self):
        assert default() == "default username"

เรียกใช้การทดสอบ

Pytest เป็นทั้งเฟรมเวิร์กการทดสอบและโปรแกรมรันการทดสอบ โปรแกรมรันการทดสอบเป็นคําสั่งที่สามารถดําเนินการได้ในบรรทัดคําสั่ง (ในระดับสูง) สามารถ:

  • ดําเนินการรวบรวมการทดสอบโดยการค้นหาไฟล์ทดสอบคลาสทดสอบและฟังก์ชันทดสอบทั้งหมดสําหรับการทดสอบการเรียกใช้
  • เริ่มต้นการทดสอบการทํางานโดยการดําเนินการทดสอบทั้งหมด
  • ติดตามความล้มเหลว ข้อผิดพลาด และการทดสอบการส่งผ่าน
  • ให้การรายงานที่หลากหลายในตอนท้ายของการทดสอบการใช้งาน

โน้ต

เนื่องจาก Pytest เป็นไลบรารีภายนอก จึงต้อง ติดตั้งเพื่อใช้งาน

กําหนดเนื้อหาเหล่านี้ในไฟล์ test_main.py เราจะเห็นว่า Pytest ทํางานอย่างไรต่อการทดสอบ:

# contents of test_main.py file

def test_main():
    assert True

ในบรรทัดคําสั่งในเส้นทางเดียวกันกับที่มีไฟล์ test_main.py เราสามารถเรียกใช้ pytest ปฏิบัติการได้:

 $ pytest
=========================== test session starts ============================
platform -- Python 3.10.1, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /private/tmp/project
collected 1 item

test_main.py .                                                       [100%]

============================ 1 passed in 0.00s =============================

ในเบื้องหลัง Pytest จะรวบรวมการทดสอบตัวอย่างในไฟล์ทดสอบโดยไม่จําเป็นต้องมีการกําหนดค่าใด ๆ

คําสั่ง assert ที่มีประสิทธิภาพ

จนถึงตอนนี้ ตัวอย่างการทดสอบของเราทั้งหมดใช้การโทร assert ธรรมดา โดยปกติแล้วใน Python assert คําสั่ง ไม่ได้ถูกใช้สําหรับการทดสอบเนื่องจากไม่มีการรายงานที่เหมาะสมเมื่อการยืนยันล้มเหลว อย่างไรก็ตาม Pytest ไม่มีข้อจํากัดนี้ ในเบื้องหลัง Pytest เปิดใช้งานคําสั่งเพื่อทําการเปรียบเทียบที่หลากหลายโดยไม่ต้องบังคับให้ผู้ใช้เขียนโค้ดเพิ่มเติมหรือกําหนดค่าใด ๆ

ด้วยการใช้คําสั่งธรรมดา assert คุณสามารถใช้ตัวดําเนินการของ Python ได้ ตัวอย่างเช่น ><, , !=, >=หรือ<= ตัวดําเนินการของ Python ทั้งหมดถูกต้อง ความสามารถนี้อาจเป็นคุณลักษณะที่สําคัญที่สุดอย่างเดียวของ Pytest: คุณไม่จําเป็นต้องเรียนรู้ไวยากรณ์ใหม่เพื่อเขียนการยืนยัน

มาดูวิธีที่แปลเมื่อจัดการกับการเปรียบเทียบทั่วไปกับวัตถุ Python ในกรณีนี้ เรามาดูรายงานความล้มเหลวเมื่อเปรียบเทียบสตริงที่ยาว:

================================= FAILURES =================================
____________________________ test_long_strings _____________________________

    def test_long_strings():
        left = "this is a very long strings to be compared with another long string"
        right = "This is a very long string to be compared with another long string"
>       assert left == right
E       AssertionError: assert 'this is a ve...r long string' == 'This is a ve...r long string'
E         - This is a very long string to be compared with another long string
E         ? ^
E         + this is a very long strings to be compared with another long string
E         ? ^                         +

test_main.py:4: AssertionError

Pytest แสดงบริบทที่มีประโยชน์เกี่ยวกับความล้มเหลว: การกําหนดปลอกที่ไม่ถูกต้องที่จุดเริ่มต้นของสตริงและอักขระพิเศษในคํา แต่นอกเหนือจากสตริงแล้ว Pytest สามารถช่วยในการวัตถุและโครงสร้างข้อมูลอื่น ๆ ได้ ตัวอย่างเช่น วิธีการทํางานกับรายการมีดังนี้:

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "coffee", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'co...ater', 'milk']
E         At index 1 diff: 'wheat' != 'coffee'
E         Full diff:
E         - ['sugar', 'coffee', 'wheat', 'salt', 'water', 'milk']
E         ?                     ---------
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?           +++++++++

test_main.py:9: AssertionError

รายงานนี้ระบุว่าดัชนี 1 (หน่วยข้อมูลที่สองในรายการ) แตกต่างกัน ไม่เพียงระบุหมายเลขดัชนีเท่านั้น แต่ยังแสดงถึงความล้มเหลวด้วย นอกเหนือจากการเปรียบเทียบหน่วยข้อมูล แล้วยังสามารถรายงานได้หากรายการหายไป และให้ข้อมูลที่สามารถบอกให้คุณทราบว่ารายการใดอาจเป็นได้ ในกรณีต่อไปนี้ที่ "milk":

________________________________ test_lists ________________________________

    def test_lists():
        left = ["sugar", "wheat", "coffee", "salt", "water", "milk"]
        right = ["sugar", "wheat", "salt", "water", "milk"]
>       assert left == right
E       AssertionError: assert ['sugar', 'wh...ater', 'milk'] == ['sugar', 'wh...ater', 'milk']
E         At index 2 diff: 'coffee' != 'salt'
E         Left contains one more item: 'milk'
E         Full diff:
E         - ['sugar', 'wheat', 'salt', 'water', 'milk']
E         + ['sugar', 'wheat', 'coffee', 'salt', 'water', 'milk']
E         ?                    ++++++++++

test_main.py:9: AssertionError

สุดท้าย มาดูกันว่ามันทํางานอย่างไรกับพจนานุกรม การเปรียบเทียบพจนานุกรมขนาดใหญ่สองรายการอาจครอบงําหากมีความล้มเหลว แต่ Pytest ทํางานที่โดดเด่นในการให้บริบทและระบุความล้มเหลว:

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'F...rry Ln.', ...} == {'county': 'F...ry Lane', ...}
E         Omitting 3 identical items, use -vv to show
E         Differing items:
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         {'number': 39} != {'number': 38}
E         Full diff:
E           {
E            'county': 'Frett',...
E
E         ...Full output truncated (12 lines hidden), use '-vv' to show

ในการทดสอบนี้ มีสองความล้มเหลวในพจนานุกรม หนึ่งคือค่า "street" แตกต่างกัน และอีกค่าหนึ่งคือ "number" ไม่ตรงกัน

Pytest ตรวจหาความแตกต่างเหล่านี้ได้อย่างถูกต้อง (แม้ว่าจะเป็นความล้มเหลวหนึ่งในการทดสอบเดียว) เนื่องจากพจนานุกรมประกอบด้วยรายการจํานวนมาก Pytest จึงละเว้นส่วนที่เหมือนกันและแสดงเนื้อหาที่เกี่ยวข้องเท่านั้น มาดูกันว่าจะเกิดอะไรขึ้นถ้าเราใช้ธง -vv ที่แนะนําเพื่อเพิ่มความฟุ่มเฟี้ยวในผลลัพธ์:

____________________________ test_dictionaries _____________________________

    def test_dictionaries():
        left = {"street": "Ferry Ln.", "number": 39, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
        right = {"street": "Ferry Lane", "number": 38, "state": "Nevada", "zipcode": 30877, "county": "Frett"}
>       assert left == right
E       AssertionError: assert {'county': 'Frett',\n 'number': 39,\n 'state': 'Nevada',\n 'street': 'Ferry Ln.',\n 'zipcode': 30877} == {'county': 'Frett',\n 'number': 38,\n 'state': 'Nevada',\n 'street': 'Ferry Lane',\n 'zipcode': 30877}
E         Common items:
E         {'county': 'Frett', 'state': 'Nevada', 'zipcode': 30877}
E         Differing items:
E         {'number': 39} != {'number': 38}
E         {'street': 'Ferry Ln.'} != {'street': 'Ferry Lane'}
E         Full diff:
E           {
E            'county': 'Frett',
E         -  'number': 38,
E         ?             ^
E         +  'number': 39,
E         ?             ^
E            'state': 'Nevada',
E         -  'street': 'Ferry Lane',
E         ?                    - ^
E         +  'street': 'Ferry Ln.',
E         ?                     ^
E            'zipcode': 30877,
E           }

โดยการเรียกใช้ pytest -vvการรายงานจะเพิ่มปริมาณรายละเอียดและให้การเปรียบเทียบที่ละเอียด รายงานนี้ไม่เพียงตรวจหาและแสดงความล้มเหลวเท่านั้น แต่จะช่วยให้คุณสามารถทําการเปลี่ยนแปลงเพื่อแก้ไขปัญหาได้อย่างรวดเร็ว