บทแนะนำการใช้งาน PyTest: PyTest คืออะไร? (พร้อมตัวอย่าง)

⚡ สรุปอย่างชาญฉลาด

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

  • 🔘 การติดตั้ง: คำสั่ง `pip install pytest` จะเพิ่มเฟรมเวิร์ก และคำสั่ง `py.test -h` จะยืนยันการติดตั้งโดยการแสดงผลลัพธ์ความช่วยเหลือ
  • ☑️ การค้นพบจากการทดสอบ: Pytest จะรวบรวมไฟล์ที่มีชื่อ test_*.py หรือ *_test.py และเมธอดที่มีชื่อขึ้นต้นด้วย test โดยอัตโนมัติ
  • การยืนยัน: คำสั่ง assert แบบธรรมดาจะรายงานข้อผิดพลาด AssertionError อย่างชัดเจนพร้อมกับค่าที่เปรียบเทียบกันทุกครั้งที่การตรวจสอบล้มเหลว
  • 🧪 การแข่งขัน: ตัวตกแต่ง @pytest.fixture จะให้ข้อมูลการตั้งค่าที่สามารถนำกลับมาใช้ซ้ำได้ ซึ่งจะถูกแชร์ระหว่างไฟล์ต่างๆ ผ่านไฟล์ conftest.py
  • 🛠️ การวิ่งแบบเลือกเฉพาะ: เครื่องหมาย ตัวกรองสตริงย่อย -k และการกำหนดพารามิเตอร์จะเรียกใช้ชุดย่อยเป้าหมายหรือการทดสอบเดียวกันกับอินพุตจำนวนมาก
  • 🤖 เวิร์กโฟลว์ AI: ทีมพัฒนาแมชชีนเลิร์นนิงใช้ pytest เพื่อตรวจสอบความถูกต้องของไปป์ไลน์ข้อมูลและผลลัพธ์ของโมเดล และ Copilot สามารถร่างกรณีทดสอบได้

บทช่วยสอน Pytest

ไพเทส เป็นหนึ่งในเฟรมเวิร์กการทดสอบที่ใช้กันอย่างแพร่หลายที่สุด Python ระบบนิเวศนี้ช่วยให้โค้ดทดสอบสั้นและอ่านง่าย ค้นหาการทดสอบโดยอัตโนมัติ และเติบโตจากการตรวจสอบเบื้องต้นไปสู่ชุดทดสอบที่สมบูรณ์สำหรับฐานข้อมูล API และส่วนติดต่อผู้ใช้ ส่วนด้านล่างจะอธิบายเกี่ยวกับการติดตั้ง การตรวจสอบ ฟิกซ์เจอร์ มาร์กเกอร์ การกำหนดพารามิเตอร์ และการรันแบบขนาน พร้อมตัวอย่างเชิงปฏิบัติ

Pytest คืออะไร?

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

เหตุใดจึงต้องใช้ Pytest?

ข้อดีบางประการของ pytest ได้แก่:

  • เริ่มต้นใช้งานได้ง่ายมาก เนื่องจากไวยากรณ์ที่เรียบง่ายและชัดเจน
  • สามารถรันการทดสอบแบบขนานได้
  • สามารถเรียกใช้การทดสอบเฉพาะรายการหรือชุดย่อยของการทดสอบได้
  • ตรวจจับการทดสอบโดยอัตโนมัติ
  • สามารถข้ามการทดสอบได้
  • โอเพ่นซอร์ส.

วิธีการติดตั้ง Pytest

ขั้นตอนการติดตั้ง pytest มีดังนี้:

ขั้นตอน 1) คุณสามารถติดตั้ง pytest ได้โดยใช้คำสั่งด้านล่างนี้

pip install pytest==2.9.1

เมื่อการติดตั้งเสร็จสมบูรณ์ คุณสามารถตรวจสอบได้ด้วยคำสั่งต่อไปนี้

py.test -h

นี่จะแสดงส่วนช่วยเหลือ

ติดตั้ง pytest และดูผลลัพธ์ในส่วนช่วยเหลือ

ตัวอย่าง Pytest พื้นฐานตัวแรก

ต่อไปนี้ คุณจะได้เรียนรู้วิธีการใช้งาน pytest โดยใช้ตัวอย่างพื้นฐานของ pytest

สร้างโฟลเดอร์ชื่อ study_pytest คุณจะสร้างไฟล์ทดสอบไว้ภายในโฟลเดอร์นี้ โปรดไปยังโฟลเดอร์นั้นในบรรทัดคำสั่งของคุณ จากนั้นสร้างไฟล์ชื่อ test_sample1.py ภายในโฟลเดอร์นั้น

สร้างไฟล์ทดสอบ pytest พื้นฐานไฟล์แรก

เพิ่มโค้ดด้านล่างลงไปแล้วบันทึก

import pytest
def test_file1_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed"
def test_file1_method2():
	x=5
	y=6
	assert x+1 == y,"test failed" 

เรียกใช้การทดสอบโดยใช้คำสั่งด้านล่าง

py.test

คุณจะได้รับผลลัพธ์ดังต่อไปนี้

test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed"
E       AssertionError: test failed
E       assert 5 == 6
test_sample1.py:6: AssertionError

ผลลัพธ์ความล้มเหลวในการทดสอบ Pytest

ในไฟล์ test_sample1.py F. นี้ ตัวอักษร F หมายถึงความล้มเหลว และจุด (.) หมายถึงความสำเร็จ ในส่วนของความล้มเหลว คุณจะเห็นเมธอดที่ล้มเหลวและบรรทัดที่เกิดความล้มเหลว ในที่นี้ x==y หมายถึง 5==6 ซึ่งเป็นเท็จ

ต่อไป คุณจะได้เรียนรู้เกี่ยวกับ assertions ใน pytest

การตรวจสอบใน Pytest

คำสั่ง assertion ใน Pytest จะส่งคืนค่า True หรือ False หากคำสั่ง assertion ล้มเหลวในเมธอดทดสอบ การทำงานของเมธอดนั้นจะหยุดลง โค้ดส่วนที่เหลือในเมธอดทดสอบนั้นจะไม่ถูกเรียกใช้งาน และ pytest จะดำเนินการต่อในเมธอดทดสอบถัดไป

ตัวอย่างการใช้คำสั่ง assert ใน Pytest:

assert "hello" == "Hai" is an assertion failure.
assert 4==4 is a successful assertion
assert True is a successful assertion
assert False is an assertion failure.

ลองพิจารณาสิ่งต่อไปนี้

assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

วางโค้ดนี้ไว้ใน test_file1_method1() แทนคำสั่ง assertion ด้านล่าง

assert x == y,"test failed"

การรันการทดสอบจะทำให้เกิดความล้มเหลวดังเช่น AssertionError: การทดสอบล้มเหลว x=5 y=6.

วิธีที่ Pytest ระบุไฟล์ทดสอบและเมธอดทดสอบ

โดยค่าเริ่มต้น pytest จะระบุเฉพาะชื่อไฟล์ที่ขึ้นต้นด้วย .py เท่านั้น ทดสอบ_ หรือลงท้ายด้วย _ทดสอบ เช่นเดียวกับไฟล์ทดสอบ เราสามารถระบุชื่อไฟล์อื่นๆ ได้อย่างชัดเจน (จะอธิบายในภายหลัง) Pytest กำหนดให้ชื่อเมธอดทดสอบต้องขึ้นต้นด้วย ทดสอบชื่อเมธอดอื่นๆ ทั้งหมดจะถูกละเลย แม้ว่าเราจะระบุให้เรียกใช้เมธอดเหล่านั้นอย่างชัดเจนก็ตาม

ดูตัวอย่างชื่อไฟล์ pytest ที่ถูกต้องและไม่ถูกต้องได้ที่นี่

test_login.py - valid
login_test.py - valid
testlogin.py -invalid
logintest.py -invalid

หมายเหตุ: ใช่ เราสามารถระบุอย่างชัดเจนให้ pytest เลือก testlogin.py และ logintest.py ได้

ดูตัวอย่างวิธีการทดสอบ pytest ที่ถูกต้องและไม่ถูกต้องได้ที่นี่

def test_file1_method1(): - valid
def testfile1_method1(): - valid
def file1_method1(): - invalid	

หมายเหตุ: แม้ว่าเราจะระบุ file1_method1() อย่างชัดเจนแล้วก็ตาม pytest ก็จะไม่รันเมธอดนี้

เรียกใช้การทดสอบหลายรายการจากไฟล์เฉพาะและหลายไฟล์

ปัจจุบัน ภายในโฟลเดอร์ study_pytest เรามีไฟล์ test_sample1.py อยู่ สมมติว่าเรามีไฟล์หลายไฟล์ เช่น test_sample2.py และ test_sample3.py หากต้องการรันการทดสอบทั้งหมดจากทุกไฟล์ในโฟลเดอร์และโฟลเดอร์ย่อย เราเพียงแค่ต้องรันคำสั่ง pytest เท่านั้น

py.test

คำสั่งนี้จะรันไฟล์ทั้งหมดที่มีชื่อขึ้นต้นด้วย test_ และลงท้ายด้วย _test ในโฟลเดอร์นั้น รวมถึงโฟลเดอร์ย่อยที่อยู่ภายใต้โฟลเดอร์นั้นด้วย

หากต้องการเรียกใช้การทดสอบเฉพาะจากไฟล์ที่ระบุ เราสามารถใช้ py.test ได้ .

py.test test_sample1.py

เรียกใช้ชุดย่อยของการทดสอบทั้งหมดด้วย Pytest

บางครั้งเราไม่ต้องการรันชุดทดสอบทั้งหมด Pytest อนุญาตให้เรารันเฉพาะการทดสอบที่ต้องการได้ โดยสามารถทำได้สองวิธี:

  • กลุ่มping ของชื่อทดสอบโดยการจับคู่สตริงย่อย
  • กลุ่มping จากการทดสอบโดยผู้ประเมิน

เรามีไฟล์ test_sample1.py อยู่แล้ว สร้างไฟล์ test_sample2.py และเพิ่มโค้ดด้านล่างลงไป

def test_file2_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
def test_file2_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

ดังนั้นขณะนี้เรามีข้อมูลดังต่อไปนี้

• test_sample1.py
• test_file1_method1()
• test_file1_method2()
• test_sample2.py
• test_file2_method1()
• test_file2_method2()

ตัวเลือกที่ 1) เรียกใช้การทดสอบโดยการจับคู่สตริงย่อย

ในที่นี้ หากต้องการเรียกใช้การทดสอบทั้งหมดที่มี method1 อยู่ในชื่อ เราต้องเรียกใช้คำสั่งด้านล่างนี้

py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity

ดังนั้น การรันคำสั่ง py.test -k method1 -v จะให้ผลลัพธ์ดังต่อไปนี้

test_sample2.py::test_file2_method1 FAILED
test_sample1.py::test_file1_method1 FAILED

============================================== FAILURES ==============================================
_________________________________________ test_file2_method1 _________________________________________
    def test_file2_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
test_sample2.py:5: AssertionError

_________________________________________ test_file1_method1 _________________________________________
    @pytest.mark.only
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
test_sample1.py:8: AssertionError

================================= 2 tests deselected by '-kmethod1' ==================================
=============================== 2 failed, 2 deselected in 0.02 seconds ===============================

ตรงนี้คุณจะเห็นได้ว่า ในช่วงท้ายๆ นั้น... ยกเลิกการเลือกการทดสอบ 2 รายการโดย '-kmethod1'ซึ่งก็คือ test_file1_method2 และ test_file2_method2

ลองใช้การผสมผสานต่างๆ ดังต่อไปนี้ดู

py.test -k method -v - will run all the four methods
py.test -k methods -v – will not run any test as there is no test name matches the substring 'methods'

ตัวเลือกที่ 2) ทำการทดสอบโดยใช้เครื่องหมาย

Pytest อนุญาตให้เรากำหนดคุณลักษณะต่างๆ สำหรับเมธอดทดสอบโดยใช้เครื่องหมาย pytest (@pytest.mark) ในการใช้เครื่องหมายในไฟล์ทดสอบ เราต้องนำเข้า pytest เข้าไปในไฟล์ทดสอบก่อน ในที่นี้เราจะกำหนดชื่อเครื่องหมายที่แตกต่างกันให้กับเมธอดทดสอบและเรียกใช้การทดสอบเฉพาะตามชื่อเครื่องหมายเหล่านั้น เราสามารถกำหนดเครื่องหมายให้กับชื่อการทดสอบแต่ละชื่อได้โดยใช้ดังต่อไปนี้

@pytest.mark.<name>.			

เรากำลังกำหนดตัวบ่งชี้ set1 และ set2 ในเมธอดทดสอบ และเราจะทำการทดสอบโดยใช้ชื่อตัวบ่งชี้เหล่านั้น อัปเดตไฟล์ทดสอบด้วยโค้ดต่อไปนี้

test_sample1.py

import pytest
@pytest.mark.set1
def test_file1_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

@pytest.mark.set2
def test_file1_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

test_sample2.py

import pytest
@pytest.mark.set1
def test_file2_method1():
	x=5
	y=6
	assert x+1 == y,"test failed"
	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)

@pytest.mark.set1
def test_file2_method2():
	x=5
	y=6
	assert x+1 == y,"test failed"

เราสามารถเรียกใช้การทดสอบที่ทำเครื่องหมายไว้โดยใช้คำสั่งด้านล่าง

py.test -m <name>
-m <name> mentions the marker name

เรียกใช้คำสั่ง `py.test -m set1` เพื่อรันเมธอด `test_file1_method1`, `test_file2_method1` และ `test_file2_method2` ส่วนการเรียกใช้คำสั่ง `py.test -m set2` จะรันเมธอด `test_file1_method2`

เรียกใช้การทดสอบแบบขนานด้วย Pytest

โดยปกติแล้ว ชุดทดสอบจะมีไฟล์ทดสอบหลายไฟล์และเมธอดทดสอบหลายร้อยรายการ ซึ่งจะใช้เวลาในการประมวลผลค่อนข้างนาน Pytest ช่วยให้เราสามารถรันการทดสอบแบบขนานได้ สำหรับการนั้น เราจำเป็นต้องติดตั้ง pytest-xdist ก่อนโดยการรันคำสั่งด้านล่าง

pip install pytest-xdist

เรียกใช้การทดสอบแบบขนานด้วย pytest-xdist

คุณสามารถเริ่มการทดสอบได้โดยใช้คำสั่งต่อไปนี้

py.test -n 4

ที่นี่ -n เรียกใช้การทดสอบโดยใช้เวิร์กเกอร์หลายตัว ในคำสั่งข้างต้น จะมีเวิร์กเกอร์สี่ตัวเพื่อเรียกใช้การทดสอบ

โปรแกรมการแข่งขัน ไพเทสต์

Fixtures คือไฟล์ที่ใช้เมื่อเราต้องการรันโค้ดบางส่วนก่อนทำการทดสอบแต่ละครั้ง ดังนั้น แทนที่จะเขียนโค้ดเดิมซ้ำในทุกการทดสอบ เราจึงกำหนด fixtures ขึ้นมา โดยปกติแล้ว fixtures จะใช้ในการเริ่มต้นการเชื่อมต่อฐานข้อมูล และส่งค่าพื้นฐานไปยังฐานข้อมูล URLและอื่นๆ เมธอดจะถูกทำเครื่องหมายว่าเป็น pytest fixture โดยการทำเครื่องหมายด้วยข้อความต่อไปนี้

@pytest.fixture

วิธีการทดสอบสามารถใช้ฟิกซ์เจอร์ของ pytest ได้โดยการระบุฟิกซ์เจอร์เป็นพารามิเตอร์อินพุต สร้างไฟล์ใหม่ชื่อ test_basic_fixture.py โดยใส่โค้ดต่อไปนี้

import pytest
@pytest.fixture
def supply_AA_BB_CC():
	aa=25
	bb =35
	cc=45
	return [aa,bb,cc]

def test_comparewithAA(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

ที่นี่:

  • เรามีฟิกซ์เจอร์ชื่อ supply_AA_BB_CC เมธอดนี้จะส่งคืนลิสต์ของค่าสามค่า
  • เรามีวิธีการทดสอบสามวิธีที่ใช้เปรียบเทียบกับค่าแต่ละค่า

ฟังก์ชันทดสอบแต่ละฟังก์ชันมีอาร์กิวเมนต์อินพุตที่มีชื่อตรงกับฟิกซ์เจอร์ที่มีอยู่ จากนั้น Pytest จะเรียกใช้เมธอดฟิกซ์เจอร์ที่เกี่ยวข้อง และค่าที่ส่งคืนจะถูกเก็บไว้ในอาร์กิวเมนต์อินพุต ในที่นี้คือลิสต์ [25,35,45] จากนั้นรายการในลิสต์จะถูกนำไปใช้ในเมธอดทดสอบเพื่อเปรียบเทียบ ตอนนี้ลองรันการทดสอบและดูผลลัพธ์

 py.test test_basic_fixture
test_basic_fixture.py::test_comparewithAA FAILED                                                                                                                                                                                       
test_basic_fixture.py::test_comparewithBB PASSED                                                                                                                                                                                       
test_basic_fixture.py::test_comparewithCC FAILED
                                                                                                                                                                                       
============================================== FAILURES ==============================================
_________________________________________ test_comparewithAA _________________________________________
supply_AA_BB_CC = [25, 35, 45]
    def test_comparewithAA(supply_AA_BB_CC):
    	zz=35
>   	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"
E    AssertionError: aa and zz comparison failed
E    assert 25 == 35
test_basic_fixture.py:10: AssertionError

_________________________________________ test_comparewithCC _________________________________________
supply_AA_BB_CC = [25, 35, 45]
    def test_comparewithCC(supply_AA_BB_CC):
    	zz=35
>   	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
E    AssertionError: cc and zz comparison failed
E    assert 45 == 35
test_basic_fixture.py:16: AssertionError
================================= 2 failed, 1 passed in 0.05 seconds =================================

การทดสอบ test_comparewithBB ผ่าน เนื่องจาก zz=BB=35 ส่วนการทดสอบอีกสองรายการที่เหลือล้มเหลว

เมธอด fixture มีขอบเขตการใช้งานเฉพาะภายในไฟล์ทดสอบที่กำหนดไว้เท่านั้น หากเราพยายามเข้าถึง fixture ในไฟล์ทดสอบอื่น เราจะได้รับข้อผิดพลาดเกี่ยวกับ fixture ไม่พบ 'supply_AA_BB_CC' สำหรับวิธีการทดสอบในไฟล์อื่นๆ

เพื่อใช้ fixture เดียวกันกับไฟล์ทดสอบหลายไฟล์ เราจึงสร้างเมธอด fixture ในไฟล์ชื่อ conftest.py มาดูตัวอย่าง pytest ด้านล่างกัน สร้างไฟล์สามไฟล์ ได้แก่ conftest.py, test_basic_fixture.py และ test_basic_fixture2.py โดยใช้โค้ดต่อไปนี้

conftest.py

import pytest
@pytest.fixture
def supply_AA_BB_CC():
	aa=25
	bb =35
	cc=45
	return [aa,bb,cc]

test_basic_fixture.py

import pytest
def test_comparewithAA(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC(supply_AA_BB_CC):
	zz=35
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

test_basic_fixture2.py

import pytest
def test_comparewithAA_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"

def test_comparewithBB_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed"

def test_comparewithCC_file2(supply_AA_BB_CC):
	zz=25
	assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"

Pytest จะค้นหา fixture ในไฟล์ทดสอบก่อน และหากไม่พบ ก็จะค้นหาในไฟล์ conftest.py เรียกใช้การทดสอบด้วยคำสั่ง py.test -k test_comparewith -v เพื่อดูผลลัพธ์ด้านล่าง

test_basic_fixture.py::test_comparewithAA FAILED  
test_basic_fixture.py::test_comparewithBB PASSED 
test_basic_fixture.py::test_comparewithCC FAILED 
test_basic_fixture2.py::test_comparewithAA_file2 PASSED 
test_basic_fixture2.py::test_comparewithBB_file2 FAILED 
test_basic_fixture2.py::test_comparewithCC_file2 FAILED

การทดสอบแบบกำหนดพารามิเตอร์ของไพเทสต์

จุดประสงค์ของการกำหนดพารามิเตอร์ให้กับการทดสอบคือการรันการทดสอบกับชุดอาร์กิวเมนต์หลายชุด เราสามารถทำได้โดยใช้ `@pytest.mark.parametrize` เราจะเห็นได้จากตัวอย่าง pytest ด้านล่างนี้ ในที่นี้เราจะส่งอาร์กิวเมนต์สามตัวไปยังเมธอดทดสอบ เมธอดทดสอบนี้จะบวกอาร์กิวเมนต์สองตัวแรกและเปรียบเทียบผลลัพธ์กับอาร์กิวเมนต์ตัวที่สาม สร้างไฟล์ทดสอบ `test_addition.py` ด้วยโค้ดด้านล่างนี้

import pytest
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add(input1, input2, output):
	assert input1+input2 == output,"failed"

เมธอดทดสอบนี้รับอาร์กิวเมนต์สามตัว ได้แก่ input1, input2 และ output โดยจะนำค่า input1 และ input2 มาบวกกัน แล้วเปรียบเทียบผลรวมกับค่า output ลองรันการทดสอบด้วยคำสั่ง py.test -k test_add -v แล้วดูผลลัพธ์กัน

test_addition.py::test_add[5-5-10] PASSED                                                                                                                                                                                              
test_addition.py::test_add[3-5-12] FAILED                                                                                                                                                                                              
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
    @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
    def test_add(input1, input2, output):
>   	assert input1+input2 == output,"failed"
E    AssertionError: failed
E    assert (3 + 5) == 12
test_addition.py:5: AssertionError

คุณจะเห็นว่ามีการทดสอบสองครั้ง ครั้งแรกตรวจสอบว่า 5+5 ==10 และครั้งที่สองตรวจสอบว่า 3+5 ==12

test_addition.py::test_add[5-5-10] ผ่านแล้ว

test_addition.py::test_add[3-5-12] ล้มเหลว

การทดสอบ Xfail และ Skip ของ Pytest

จะมีบางสถานการณ์ที่เราไม่ต้องการทำการทดสอบ หรือ กรณีทดสอบ ไม่เกี่ยวข้องในเวลาใดเวลาหนึ่ง ในสถานการณ์เหล่านั้น เรามีตัวเลือกที่จะ xfail การทดสอบหรือข้ามการทดสอบ การทดสอบที่ xfailed จะถูกดำเนินการ แต่จะไม่ถูกนับรวมเป็นส่วนหนึ่งของการทดสอบที่ล้มเหลวหรือผ่าน จะไม่มี traceback จะแสดงขึ้นหากการทดสอบนั้นล้มเหลว เราสามารถ xfail การทดสอบได้โดยใช้ @pytest.mark.xfail ข้ามไปping การทดสอบนี้จะไม่ดำเนินการ เราสามารถข้ามการทดสอบได้โดยใช้ `@pytest.mark.skip` แก้ไขไฟล์ test_addition.py ด้วยโค้ดด้านล่างนี้

import pytest
@pytest.mark.skip
def test_add_1():
	assert 100+200 == 400,"failed"

@pytest.mark.skip
def test_add_2():
	assert 100+200 == 300,"failed"

@pytest.mark.xfail
def test_add_3():
	assert 15+13 == 28,"failed"

@pytest.mark.xfail
def test_add_4():
	assert 15+13 == 100,"failed"

def test_add_5():
	assert 3+2 == 5,"failed"

def test_add_6():
	assert 3+2 == 6,"failed"

ที่นี่:

  • test_add_1 และ test_add_2 ถูกข้ามไปและจะไม่ถูกดำเนินการ
  • test_add_3 และ test_add_4 ถูกจัดอยู่ในสถานะ xfailed การทดสอบเหล่านี้จะถูกดำเนินการและจะเป็นส่วนหนึ่งของการทดสอบ xfailed (เมื่อการทดสอบล้มเหลว) หรือ xpassed (เมื่อการทดสอบผ่าน) จะไม่มีการทดสอบใดๆ เพิ่มเติม traceback สำหรับความล้มเหลว
  • คำสั่ง test_add_5 และ test_add_6 จะถูกเรียกใช้งาน และ test_add_6 จะรายงานความล้มเหลวพร้อมข้อความแสดงข้อผิดพลาด traceback ในขณะที่ test_add_5 ผ่าน

เรียกใช้การทดสอบด้วยคำสั่ง `py.test test_addition.py -v` แล้วดูผลลัพธ์

test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED

============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
    def test_add_6():
>   	assert 3+2 == 6,"failed"
E    AssertionError: failed
E    assert (3 + 2) == 6
test_addition.py:24: AssertionError

================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================

ผลลัพธ์ในรูปแบบ XML

เราสามารถสร้างผลลัพธ์การทดสอบในรูปแบบ XML ซึ่งเราสามารถป้อนให้กับเซิร์ฟเวอร์ Continuous Integration เพื่อประมวลผลต่อไปได้ สามารถทำได้โดยใช้คำสั่ง `py.test test_sample1.py -v –junitxml=”result.xml”` ไฟล์ `result.xml` จะบันทึกผลลัพธ์การทดสอบ ตัวอย่างไฟล์ `result.xml` อยู่ด้านล่าง

<?xml version="1.0" encoding="UTF-8"?>
<testsuite errors="0" failures="1" name="pytest" skips="0" tests="2" time="0.046">
   <testcase classname="test_sample1" file="test_sample1.py" line="3" name="test_file1_method1" time="0.001384973526">
     <failure message="AssertionError:test failed because x=5 y=6 assert 5 ==6">
    @pytest.mark.set1
    def test_file1_method1():
    	x=5
    	y=6
       	assert x+1 == y,"test failed"
>      	assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E       AssertionError: test failed because x=5 y=6
E       assert 5 == 6
         test_sample1.py:9: AssertionError
    </failure>
   </testcase>
   <testcase classname="test_sample1" file="test_sample1.py" line="10" name="test_file1_method2" time="0.000830173492432" />
</testsuite>

จาก เราจะเห็นการทดสอบทั้งหมดสองรายการ ซึ่งหนึ่งในนั้นล้มเหลว ด้านล่างนั้น คุณจะเห็นรายละเอียดเกี่ยวกับการทดสอบแต่ละรายการที่ดำเนินการไปแล้ว แท็ก

เฟรมเวิร์ก Pytest สำหรับทดสอบ API

ต่อไปนี้เราจะสร้างเฟรมเวิร์ก pytest ขนาดเล็กเพื่อทดสอบ API โดย API ที่ใช้ในที่นี้เป็น API ฟรีจาก [ชื่อแพลตฟอร์ม API] ความต้องการ.ในเว็บไซต์นี้เป็นเพียง API ที่สามารถทดสอบได้ และไม่ได้จัดเก็บข้อมูลของเรา ต่อไปนี้เราจะเขียนการทดสอบสำหรับหัวข้อต่อไปนี้:

  • แสดงรายชื่อผู้ใช้บางส่วน
  • เข้าสู่ระบบด้วยชื่อผู้ใช้

สร้างไฟล์ต่อไปนี้โดยใช้โค้ดที่ให้มา ก่อนอื่น ไฟล์ conftest.py จะมีฟิกซ์เจอร์ที่จะให้ข้อมูลพื้นฐาน URL สำหรับวิธีการทดสอบทั้งหมด

import pytest
@pytest.fixture
def supply_url():
	return "https://reqres.in/api"

ถัดไป ไฟล์ test_list_user.py จะมีเมธอดทดสอบสำหรับแสดงรายชื่อผู้ใช้ที่ถูกต้องและไม่ถูกต้อง

  • test_list_valid_user ทดสอบการดึงข้อมูลผู้ใช้ที่ถูกต้องและตรวจสอบการตอบกลับ
  • test_list_invaliduser ทดสอบการดึงข้อมูลผู้ใช้ที่ไม่ถูกต้องและตรวจสอบความถูกต้องของผลลัพธ์
import pytest
import requests
import json
@pytest.mark.parametrize("userid, firstname",[(1,"George"),(2,"Janet")])
def test_list_valid_user(supply_url,userid,firstname):
	url = supply_url + "/users/" + str(userid)
	resp = requests.get(url)
	j = json.loads(resp.text)
	assert resp.status_code == 200, resp.text
	assert j['data']['id'] == userid, resp.text
	assert j['data']['first_name'] == firstname, resp.text

def test_list_invaliduser(supply_url):
	url = supply_url + "/users/50"
	resp = requests.get(url)
	assert resp.status_code == 404, resp.text

จากนั้น ไฟล์ test_login_user.py จะมีเมธอดทดสอบสำหรับทดสอบฟังก์ชันการเข้าสู่ระบบ

  • test_login_valid จะทดสอบการเข้าสู่ระบบที่ถูกต้องโดยใช้ที่อยู่อีเมลและรหัสผ่าน
  • test_login_no_password ทดสอบการพยายามเข้าสู่ระบบที่ไม่ถูกต้องโดยไม่ใส่รหัสผ่าน
  • test_login_no_email จะทดสอบการพยายามเข้าสู่ระบบที่ไม่ถูกต้องโดยไม่ระบุอีเมล
import pytest
import requests
import json
def test_login_valid(supply_url):
	url = supply_url + "/login/" 
	data = {'email':'test@test.com','password':'something'}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 200, resp.text
	assert j['token'] == "QpwL5tke4Pnpja7X", resp.text

def test_login_no_password(supply_url):
	url = supply_url + "/login/" 
	data = {'email':'test@test.com'}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 400, resp.text
	assert j['error'] == "Missing password", resp.text

def test_login_no_email(supply_url):
	url = supply_url + "/login/" 
	data = {}
	resp = requests.post(url, data=data)
	j = json.loads(resp.text)
	assert resp.status_code == 400, resp.text
	assert j['error'] == "Missing email or username", resp.text

เรียกใช้การทดสอบโดยใช้คำสั่ง py.test -v และดูผลลัพธ์ดังต่อไปนี้

test_list_user.py::test_list_valid_user[1-George] PASSED                                                                                                                                                                               
test_list_user.py::test_list_valid_user[2-Janet] PASSED                                                                                                                                                                                
test_list_user.py::test_list_invaliduser PASSED                                                                                                                                                                                        
test_login_user.py::test_login_valid PASSED                                                                                                                                                                                            
test_login_user.py::test_login_no_password PASSED                                                                                                                                                                                      
test_login_user.py::test_login_no_email PASSED

อัปเดตการทดสอบและลองใช้ผลลัพธ์ต่างๆ

คำถามที่พบบ่อย

ทั้งคู่วิ่ง Python pytest ใช้คำสั่ง assert แบบธรรมดา ลดความซ้ำซ้อน และมี fixtures, markers และ parametrize ให้ใช้งาน Unittest มาพร้อมกับไลบรารีมาตรฐานและใช้รูปแบบ xUnit ที่อิงตามคลาส Pytest ยังสามารถรันเคสทดสอบ unittest ที่มีอยู่แล้วได้

ใช้ไวยากรณ์ระบุรหัสโหนดโดยใช้เครื่องหมายโคลอนสองตัว: pytest test_sample1.py::test_file1_method1 คำสั่งนี้จะรันเฉพาะเมธอดนั้นเมธอดเดียว คุณยังสามารถกำหนดเป้าหมายทั้งไฟล์ (pytest test_sample1.py) หรือกรองตามชื่อโดยใช้ตัวเลือก -k ได้อีกด้วย

ติดตั้งปลั๊กอิน pytest-cov จากนั้นเรียกใช้คำสั่ง pytest –cov=your_package คำสั่งนี้จะแสดงเปอร์เซ็นต์ของบรรทัดที่ถูกทดสอบ และ –cov-report=html จะสร้างรายงานที่สามารถเรียกดูได้ ซึ่งจะเน้นบรรทัดที่ยังไม่ได้ทดสอบ

ขอบเขตของฟิกซ์เจอร์จะควบคุมความถี่ในการรัน: ฟังก์ชัน (ค่าเริ่มต้น), คลาส, โมดูล, แพ็กเกจ หรือเซสชัน ตั้งค่าได้ด้วย @pytest.fixture(scope=”module”) ขอบเขตที่กว้างขึ้นจะนำการตั้งค่าที่มีค่าใช้จ่ายสูง เช่น การเชื่อมต่อฐานข้อมูล มาใช้ซ้ำในการทดสอบหลายครั้ง

เรียกใช้คำสั่ง `pytest -x` เพื่อหยุดการทดสอบทันทีหลังจากพบการทดสอบล้มเหลวครั้งแรก หรือ `pytest –maxfail=2` เพื่อหยุดการทดสอบหลังจากพบความล้มเหลวตามจำนวนที่เลือกไว้ วิธีนี้จะช่วยลดระยะเวลาในการแก้ไขปัญหาชุดทดสอบที่ทำงานผิดพลาด

โดยค่าเริ่มต้น Pytest จะบันทึกเอาต์พุตมาตรฐาน (stdout) หากต้องการปิดการบันทึกและแสดงข้อความที่พิมพ์แบบเรียลไทม์ ให้ใช้แฟล็ก -s (pytest -s) แฟล็ก -v จะเพิ่มเอาต์พุตแบบละเอียดสำหรับแต่ละการทดสอบ โดยจะแสดงชื่อการทดสอบและผลลัพธ์

ทีมพัฒนาแมชชีนเลิร์นนิงใช้ pytest เพื่อตรวจสอบความถูกต้องของกระบวนการโหลดข้อมูล การแปลงคุณลักษณะ และผลลัพธ์ของโมเดล Fixtures จะจัดเตรียมชุดข้อมูลตัวอย่าง และ Parametrize จะตรวจสอบอินพุตจำนวนมาก เพื่อให้ส่วนที่ทำงานอัจฉริยะของระบบยังคงเชื่อถือได้แม้ว่าโค้ดจะเปลี่ยนแปลงไป

ใช่แล้ว GitHub Copilot และผู้ช่วย AI ของ Agentic สามารถสร้างฟังก์ชัน pytest, ฟิกซ์เจอร์ และเคสที่มีพารามิเตอร์ได้จากข้อความแจ้งสั้นๆ หรือจากฟังก์ชันที่มีอยู่แล้ว Revโปรดตรวจสอบข้อสรุปที่สร้างขึ้น เนื่องจาก AI อาจมองข้ามกรณีพิเศษและประเมินค่าที่คาดหวังผิดพลาดได้

สรุปโพสต์นี้ด้วย: