Bagaimana cara menyatakan dengan benar bahwa pengecualian muncul di pytest?


293

Kode:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)

Keluaran:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================

Bagaimana cara membuat traceback cetak pytest, jadi saya akan melihat di mana dalam whateverfungsi pengecualian dimunculkan?


Saya mendapatkan seluruh traceback, Ubuntu 14.04, Python 2.7.6
thefourtheye

@thefourtheye Buat intisari dengan output silakan. Saya mencoba dengan Python 2.7.4 dan Ubunthu 14.04 - dengan hasil yang sama seperti yang saya jelaskan di posting utama.
Gill Bates

1
@GillBates, bisakah kamu menandai jawaban yang benar ??
Gonzalo Garcia

Jawaban:


331

pytest.raises(Exception) adalah apa yang kamu butuhkan.

Kode

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True

Keluaran

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================

Catatan yang e_infomenyimpan objek pengecualian sehingga Anda dapat mengekstrak detail darinya. Misalnya, jika Anda ingin memeriksa tumpukan panggilan pengecualian atau pengecualian bersarang lainnya di dalam.


34
Akan lebih baik jika Anda dapat menyertakan contoh yang sebenarnya menanyakan e_info. Untuk pengembang yang lebih terbiasa dengan bahasa lain tertentu, tidak jelas bahwa ruang lingkup e_infomeluas di luar withblok.
cjs

152

Apakah maksud Anda seperti ini:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'

16
excinfo.value.messagetidak bekerja untuk saya, harus menggunakan str(excinfo.value), menambahkan jawaban baru
d_j

5
@d_j, assert excinfo.value.args[0] == 'some info'adalah cara langsung untuk mengakses pesan
maxschlepzig

assert excinfo.match(r"^some info$")berfungsi juga
Robin Nemeth

14
Karena versi 3.1Anda dapat menggunakan argumen kata kunci matchuntuk menyatakan bahwa pengecualian cocok dengan teks atau regex: with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")atauwith raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
Ilya Rusin

58

Ada dua cara untuk menangani kasus seperti ini di pytest:

  • Menggunakan pytest.raisesfungsi

  • Menggunakan pytest.mark.xfaildekorator

Penggunaan pytest.raises:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

Penggunaan pytest.mark.xfail:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

Output dari pytest.raises:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================

Output dari pytest.xfailpenanda:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================

Seperti yang dikatakan dalam dokumentasi :

Menggunakan pytest.raisescenderung lebih baik untuk kasus-kasus di mana Anda menguji pengecualian, kode Anda sendiri sengaja dinaikkan, sedangkan menggunakan @pytest.mark.xfaildengan fungsi cek mungkin lebih baik untuk sesuatu seperti mendokumentasikan bug yang tidak diperbaiki (di mana tes ini menjelaskan apa yang "harus" terjadi) atau bug dalam dependensi .


xfailbukan solusi untuk masalah di sini, itu hanya memungkinkan tes gagal. Di sini kami ingin memeriksa apakah pengecualian tertentu dimunculkan.
Ctrl-C

44

Anda dapat mencoba

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 

Untuk mendapatkan pesan / nilai pengecualian sebagai string di pytest 5.0.0, str(excinfo.value)diperlukan penggunaan. Ia juga bekerja di pytest 4.x. Di pytest 4.x, str(excinfo)juga berfungsi, tetapi tidak berfungsi di pytest 5.0.0.
Makyen

Ini yang saya cari. Terima kasih
Eystein Bye

15

pytest terus berkembang dan dengan salah satu perubahan yang bagus di masa lalu baru-baru ini sekarang mungkin untuk menguji secara bersamaan

  • jenis pengecualian (tes ketat)
  • pesan kesalahan (cek ketat atau longgar menggunakan ekspresi reguler)

Dua contoh dari dokumentasi:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')

Saya telah menggunakan pendekatan itu di sejumlah proyek dan sangat menyukainya.


6

Cara yang benar digunakan pytest.raisestetapi saya menemukan cara alternatif yang menarik dalam komentar di sini dan ingin menyimpannya untuk pembaca masa depan dari pertanyaan ini:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True


2

Praktik yang lebih baik akan menggunakan kelas yang mewarisi unittest.TestCase dan menjalankan self.assertRaises.

Sebagai contoh:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

Maka Anda akan menjalankannya dengan menjalankan:

pytest -vs test_path

4
Latihan yang lebih baik dari apa? Saya tidak akan memanggil menggunakan sintaks unittest daripada sintaks pytest "praktik yang lebih baik" sama sekali.
Jean-François Corbett

2
Ini mungkin bukan 'lebih baik,' tetapi ini merupakan alternatif yang berguna. Karena kriteria untuk jawaban adalah kegunaan, saya tidak yakin.
Reb.Cabin

sepertinya pytestlebih populer daripada nosex, tetapi ini adalah bagaimana saya menggunakan pytest.
Gang

0

Sudahkah Anda mencoba menghapus "pytrace = True"?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

Sudahkah Anda mencoba menjalankan dengan '--fulltrace'?

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.