Pengujian Python dengan pytest. Konfigurasi, BAB 6

Kembali Selanjutnya


Dalam bab ini, kita akan melihat file-file konfigurasi yang memengaruhi pytest, membahas bagaimana pytest mengubah perilakunya berdasarkan pada mereka, dan membuat beberapa perubahan pada tugas-tugas konfigurasi proyek Tugas.



Contoh-contoh dalam buku ini ditulis menggunakan Python 3.6 dan pytest 3.2. pytest 3.2 mendukung Python 2.6, 2.7, dan Python 3.3+.


Kode sumber untuk proyek Tugas, serta untuk semua tes yang ditunjukkan dalam buku ini, tersedia di tautan di halaman web buku di pragprog.com . Anda tidak perlu mengunduh kode sumber untuk memahami kode uji; kode uji disajikan dalam bentuk yang mudah dalam contoh. Tetapi untuk mengikuti tugas-tugas proyek, atau mengadaptasi contoh uji untuk menguji proyek Anda sendiri (tangan Anda tidak terikat!), Anda harus pergi ke halaman web buku dan mengunduh karya. Di sana, di halaman web buku, ada tautan untuk pesan errata dan forum diskusi .

Di bawah spoiler adalah daftar artikel dalam seri ini.



Konfigurasi


Sejauh ini dalam buku ini saya telah berbicara tentang berbagai file non-tes yang mempengaruhi pytest terutama secara sepintas, dengan pengecualian conftest.py, yang saya bahas secara rinci dalam Bab 5, Plugin, di halaman 95. Dalam bab ini kita akan melihat file konfigurasi, yang mempengaruhi pytest, diskusikan bagaimana pytest mengubah perilakunya berdasarkan pada mereka, dan membuat beberapa perubahan pada file konfigurasi proyek Tugas.


Memahami file konfigurasi pytest


Sebelum saya memberi tahu Anda bagaimana Anda dapat mengubah perilaku default di pytest, mari kita membahas semua file non-tes di pytest dan, khususnya, siapa yang harus mengurusnya.


Anda harus tahu yang berikut:


  • pytest.ini : Ini adalah file konfigurasi Pytest utama yang memungkinkan Anda untuk mengubah perilaku default. Karena Anda dapat membuat beberapa perubahan konfigurasi, sebagian besar bab ini dikhususkan untuk pengaturan yang dapat Anda buat di pytest.ini .
  • conftest.py : Ini adalah plugin lokal yang memungkinkan fungsi dan perlengkapan hook terhubung ke direktori di mana file conftest.py dan semua subdirektori. File conftest.py dijelaskan dalam bab 5 “Plugin” di halaman 95.
  • __init__.py : Ketika ditempatkan di setiap subdirektori tes, file ini memungkinkan Anda untuk memiliki nama file tes yang identik di beberapa direktori tes. Kita akan melihat contoh apa yang akan salah tanpa file __init__.py di direktori pengujian dalam artikel “Menghindari Tabrakan Nama File” di halaman 120.

Jika Anda menggunakan racun, Anda akan tertarik pada:


  • tox.ini : File ini mirip dengan pytest.ini , tetapi untuk tox . Namun, Anda dapat meletakkan konfigurasi pytest Anda di pytest alih-alih memiliki file tox.ini dan file pytest.ini , menghemat satu file konfigurasi. Tox dibahas dalam bab 7, “Menggunakan pytest dengan alat lain,” di halaman 125.

Jika Anda ingin mendistribusikan paket Python (mis. Tugas), file ini akan menarik:


  • setup.cfg : Ini juga merupakan file INI yang memengaruhi perilaku setup.py . Anda dapat menambahkan beberapa baris ke setup.py untuk menjalankan python setup.py test dan menjalankan semua tes pytest Anda. Jika Anda mendistribusikan paket, Anda mungkin sudah memiliki file setup.cfg dan Anda dapat menggunakan file ini untuk menyimpan konfigurasi Pytest. Anda akan melihat bagaimana hal ini dilakukan dalam Lampiran 4, “Pengemasan dan Distribusi Proyek Python,” di halaman 175.

Tidak peduli file apa yang Anda masukkan konfigurasi pytest, formatnya pada dasarnya akan sama.


Untuk pytest.ini :


ch6 / format / pytest.ini

 [pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

Untuk tox.ini :


ch6 / format / tox.ini

 ... tox specific stuff ... [pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

Untuk setup.cfg :


ch6 / format / setup.cfg

 ... packaging specific stuff ... [tool:pytest] addopts = -rsxX -l --tb=short --strict xfail_strict = true ... more options ... 

Satu-satunya perbedaan adalah bahwa header bagian untuk setup.cfg adalah [tool:pytest] bukan [pytest] .


Daftar Opsi file inisial yang valid dengan pytest –help


Anda bisa mendapatkan daftar semua parameter yang valid untuk pytest.ini dari pytest --help :


 $ pytest --help ... [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: markers (linelist) markers for test functions empty_parameter_set_mark (string) default marker for empty parametersets norecursedirs (args) directory patterns to avoid for recursion testpaths (args) directories to search for tests when no files or directories are given in the command line. console_output_style (string) console output: classic or with additional progress information (classic|progress). usefixtures (args) list of default fixtures to be used with this project python_files (args) glob-style file patterns for Python test module discovery python_classes (args) prefixes or glob names for Python test class discovery python_functions (args) prefixes or glob names for Python test function and method discovery xfail_strict (bool) default for the strict parameter of xfail markers when not given explicitly (default: False) junit_suite_name (string) Test suite name for JUnit report junit_logging (string) Write captured log messages to JUnit report: one of no|system-out|system-err doctest_optionflags (args) option flags for doctests doctest_encoding (string) encoding used for doctest files cache_dir (string) cache directory path. filterwarnings (linelist) Each line specifies a pattern for warnings.filterwarnings. Processed after -W and --pythonwarnings. log_print (bool) default value for --no-print-logs log_level (string) default value for --log-level log_format (string) default value for --log-format log_date_format (string) default value for --log-date-format log_cli (bool) enable log display during test run (also known as "live logging"). log_cli_level (string) default value for --log-cli-level log_cli_format (string) default value for --log-cli-format log_cli_date_format (string) default value for --log-cli-date-format log_file (string) default value for --log-file log_file_level (string) default value for --log-file-level log_file_format (string) default value for --log-file-format log_file_date_format (string) default value for --log-file-date-format addopts (args) extra command line options minversion (string) minimally required pytest version xvfb_width (string) Width of the Xvfb display xvfb_height (string) Height of the Xvfb display xvfb_colordepth (string) Color depth of the Xvfb display xvfb_args (args) Additional arguments for Xvfb xvfb_xauth (bool) Generate an Xauthority token for Xvfb. Needs xauth. ... 

Anda akan melihat semua pengaturan ini dalam bab ini, dengan pengecualian doctest_optionflags , yang dibahas dalam Bab 7, “Menggunakan pytest dengan alat lain,” di halaman 125.


Plugin dapat menambahkan opsi file ini


Daftar pengaturan sebelumnya tidak konstan. Untuk plugin (dan file conftest.py) dimungkinkan untuk menambahkan opsi file ini. Opsi yang ditambahkan juga akan ditambahkan ke output dari perintah pytest --help.
Sekarang mari kita lihat beberapa perubahan konfigurasi yang bisa kita lakukan menggunakan pengaturan bawaan file .ini yang tersedia di core pytest.


Ubah opsi baris perintah default


Anda telah menggunakan beberapa opsi baris perintah untuk pytest , seperti -v/--verbose untuk verbose output -l/--showlocals untuk melihat variabel lokal dengan jejak stack untuk pengujian yang gagal. Anda mungkin menemukan bahwa Anda selalu menggunakan beberapa options—or ini options—or dan lebih suka menggunakannya them—for a project . Jika Anda menginstal addopts di pytest.ini untuk parameter yang Anda butuhkan, maka Anda tidak perlu lagi memasukkannya. Ini adalah set yang saya sukai:


 [pytest] addopts = -rsxX -l --tb=short --strict 

-rsxX memungkinkan pytest untuk melaporkan alasan semua tes yang skipped , xfailed atau xpassed . Switch -l memungkinkan pytest untuk menampilkan jejak stack untuk variabel lokal jika terjadi kegagalan. --tb=short akan menghapus sebagian besar jejak tumpukan. Namun, itu akan meninggalkan file dan nomor baris. --strict melarang penggunaan token jika tidak terdaftar dalam file konfigurasi. Anda akan melihat bagaimana melakukan ini di bagian selanjutnya.


Pendaftaran marker untuk menghindari kesalahan ketik marker


Marker khusus, seperti yang dijelaskan dalam “Fungsi Pelabelan Tes” pada halaman 31, sangat bagus untuk memungkinkan Anda menandai subset pengujian untuk dijalankan dengan spidol tertentu. Namun, terlalu mudah untuk membuat kesalahan pada marker dan akhirnya beberapa tes ditandai @pytest.mark.smoke dan beberapa lainnya ditandai @pytest.mark.somke . Secara default, ini bukan kesalahan. pytest hanya berpikir Anda membuat dua spidol. Namun, ini dapat diperbaiki dengan mendaftarkan token di pytest.ini, misalnya seperti ini:


 [pytest] ... markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get() ... 

Dengan mendaftarkan penanda ini, Anda sekarang juga dapat melihatnya dengan pytest --markers dengan deskripsi mereka:


 $ cd /path/to/code/ch6/b/tasks_proj/tests $ pytest --markers @pytest.mark.smoke: Run the smoke test test functions @pytest.mark.get: Run the test functions that test tasks.get() @pytest.mark.skip(reason=None): skip the ... ... 

Jika penanda tidak terdaftar, mereka tidak akan muncul di daftar - --markers . Ketika mereka terdaftar, mereka ditampilkan dalam daftar, dan jika Anda menggunakan --strict , token dengan kesalahan atau tidak terdaftar ditampilkan sebagai kesalahan. Satu-satunya perbedaan antara ch6/a/tasks_proj dan ch6/b/tasks_proj adalah isi dari file pytest.ini. Di ch6/a kosong. Mari kita coba menjalankan tes tanpa mendaftarkan penanda apa pun:


 $ cd /path/to/code/ch6/a/tasks_proj/tests $ pytest --strict --tb=line ============================= test session starts ============================= collected 45 items / 2 errors =================================== ERRORS ==================================== ______________________ ERROR collecting func/test_add.py ______________________ 'smoke' not a registered marker ________________ ERROR collecting func/test_api_exceptions.py _________________ 'smoke' not a registered marker !!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 2 error in 1.10 seconds =========================== 

Jika Anda menggunakan marker di pytest.ini untuk mendaftarkan marker Anda, Anda bisa menambahkan --strict ke addopts Anda saat Anda menggunakannya. Anda akan berterima kasih kepada saya nanti. Mari kita lanjutkan dan menambahkan file pytest.ini ke proyek tugas:


Jika Anda menggunakan marker di pytest.ini untuk mendaftarkan marker Anda, Anda juga dapat menambahkan --strict ke addopts Anda. Anda akan berterima kasih kepada saya nanti. Mari kita lanjutkan dan tambahkan file pytest.ini ke proyek tugas:


Jika Anda menggunakan token di pytest.ini untuk mendaftarkan token, Anda juga bisa menambahkan --strict ke yang sudah ada dengan addopts . Keren ?! pytest.ini terima kasih dan tambahkan file pytest.ini ke proyek tasks :


ch6 / b / task_proj / tes / pytest.ini

 [pytest] addopts = -rsxX -l --tb=short --strict markers = smoke: Run the smoke test test functions get: Run the test functions that test tasks.get() 

Berikut adalah kombinasi dari flag yang dipilih secara default:


  • -rsxX untuk -rsxX tes mana yang dilewati, xfailed, atau xpassed,
  • --tb = short untuk jejak yang lebih pendek pada kegagalan,
  • --strict untuk mengizinkan token yang dideklarasikan saja.
    Dan daftar spidol untuk proyek tersebut.

Ini harus memungkinkan kami melakukan tes, termasuk tes asap:


 $ cd /path/to/code/ch6/b/tasks_proj/tests $ pytest --strict -m smoke ===================== test session starts ====================== collected 57 items func/test_add.py . func/test_api_exceptions.py .. ===================== 54 tests deselected ====================== =========== 3 passed, 54 deselected in 0.06 seconds ============ 

Persyaratan Pytest Minimum


Parameter minversion memungkinkan minversion menentukan versi pytest minimum yang diharapkan untuk pengujian. Misalnya, saya bermaksud menggunakan approx() ketika menguji angka floating-point untuk menentukan kesetaraan "cukup dekat" dalam pengujian. Tetapi fungsi ini tidak diperkenalkan di pytest sampai versi 3.0. Untuk menghindari kebingungan, saya menambahkan yang berikut ke proyek yang menggunakan approx() :


 [pytest] minversion = 3.0 

Dengan demikian, jika seseorang mencoba menjalankan tes menggunakan versi pytest yang lebih lama, pesan kesalahan akan muncul.


Hentikan pytest dari pencarian di tempat yang salah


Tahukah Anda bahwa salah satu definisi "berulang" adalah bersumpah dua kali dalam kode Anda sendiri? Ya tidak. Bahkan, ini berarti akuntansi untuk subdirektori. pytest akan memungkinkan deteksi uji dengan secara rekursif memeriksa banyak direktori. Tetapi ada beberapa direktori yang ingin Anda kecualikan dari melihat pytest.


Nilai default untuk norecurse adalah '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' '. * Build dist CVS _darcs {arch} and *.egg. Having '.*' Adalah alasan yang bagus untuk memberi nama lingkungan virtual Anda '.venv', karena semua direktori yang dimulai dengan titik tidak akan terlihat.


Dalam kasus proyek Tugas, src tidak ada salahnya untuk menentukan, karena mencari dalam file uji menggunakan pytest akan membuang-buang waktu.


 [pytest] norecursedirs = .* venv src *.egg dist build 

Ketika mengganti parameter yang sudah memiliki nilai berguna, seperti parameter ini, akan berguna untuk mengetahui apa nilai defaultnya dan mengembalikan yang Anda butuhkan, seperti yang saya lakukan pada kode sebelumnya dengan *.egg dist build .
norecursedirs adalah semacam konsekuensi untuk jalur uji, jadi mari kita lihat ini nanti.


spesifikasi pohon direktori pengujian


Sementara norecursedirs memberitahu pytest ke mana harus mencari, testpaths memberitahu pytest ke mana harus mencari. testspaths adalah daftar direktori yang relatif terhadap direktori root untuk menemukan tes. Ini digunakan hanya jika direktori, file atau nodeid tidak ditentukan sebagai argumen.


Misalkan untuk proyek Tasks kita meletakkan pytest.ini di direktori tasks_proj alih-alih tes:


 \code\tasks_proj>tree/f . │ pytest.ini │ ├───src │ └───tasks │ api.py │ ... │ └───tests │ conftest.py │ pytest.ini │ ├───func │ test_add.py │ ... │ ├───unit │ test_task.py │ __init__.py │ ... 

Maka mungkin masuk akal untuk menempatkan tes di testpaths :


 [pytest] testpaths = tests 

Sekarang, jika Anda menjalankan pytest dari direktori task_proj, pytest hanya akan mencari di tasks_proj/tests . Masalahnya di sini adalah bahwa selama pengembangan dan debugging tes, saya sering melakukan iterate pada direktori tes, jadi saya dapat dengan mudah menguji subdirektori atau file tanpa menentukan keseluruhan path. Oleh karena itu, opsi ini sedikit membantu saya dalam pengujian interaktif.


Namun, ini bagus untuk pengujian yang dijalankan dari server integrasi berkelanjutan atau racun. Dalam kasus ini, Anda tahu bahwa direktori root akan diperbaiki, dan Anda bisa daftar direktori relatif terhadap direktori root tetap. Ini juga merupakan kasus ketika Anda benar-benar ingin mengurangi waktu pengujian, sehingga menyingkirkan pencarian untuk tes sangat bagus.


Pada pandangan pertama, mungkin terlihat konyol untuk menggunakan jalur uji dan norecursedirs bersamaan. Namun, seperti yang telah Anda lihat, jalur uji membantu sedikit dalam pengujian interaktif dari berbagai bagian sistem file. Dalam kasus ini, norecursedirs dapat membantu. Selain itu, jika Anda memiliki direktori pengujian yang tidak mengandung tes, Anda dapat menggunakan norecursedirs untuk menghindarinya. Tetapi sebenarnya, apa gunanya menempatkan direktori tambahan dalam tes yang tidak memiliki tes?


Mengubah Aturan Deteksi Tes


pytest menemukan tes untuk dijalankan berdasarkan aturan penemuan tes khusus. Aturan deteksi uji standar:


• Mulai dengan satu atau lebih direktori. Anda dapat menentukan nama file atau direktori pada baris perintah. Jika Anda tidak menentukan apa pun, direktori saat ini digunakan.
• Cari katalog dan semua subdirektori untuk modul uji.
• Modul uji adalah file dengan nama yang mirip dengan test_*.py atau *_test.py .
• Lihat di modul tes untuk fungsi-fungsi yang dimulai dengan tes .
• Cari kelas yang dimulai dengan Tes. Cari metode di kelas yang dimulai dengan `test , init` .


Ini adalah aturan deteksi standar; Namun, Anda dapat mengubahnya.


python_classes


Aturan umum untuk menemukan tes untuk pytest dan kelas adalah untuk mempertimbangkan kelas sebagai kelas tes potensial jika dimulai dengan Test* . Kelas juga tidak dapat memiliki metode __init__() . Tetapi bagaimana jika kita ingin menamai kelas tes kami sebagai <something>Test atau <something>Suite ? Di sinilah python_classes masuk:


 [pytest] python_classes = *Test Test* *Suite 

Ini memungkinkan kita untuk memberi nama kelas-kelas seperti ini:


 class DeleteSuite(): def test_delete_1(): ... def test_delete_2(): ... .... 

python_files


Seperti pytest_classes , python_files memodifikasi aturan deteksi uji default, yang terdiri dari menemukan file yang dimulai dengan test_* atau memiliki *_test di akhir.
Misalkan Anda memiliki kerangka kerja pengujian khusus di mana Anda memberi nama semua file pengujian Anda check_<something>.py . Tampaknya masuk akal. Alih-alih mengganti nama semua file Anda, tambahkan saja baris ke pytest.ini sebagai berikut:


 [pytest] python_files = test_* *_test check_* 

Sangat sederhana. Sekarang Anda dapat secara bertahap mentransfer konvensi penamaan jika Anda mau, atau biarkan saja sebagai check_* .


fungsi python_


python_functions bertindak seperti dua pengaturan sebelumnya, tetapi untuk fungsi uji dan nama metode. Nilai standarnya adalah test_* . Dan untuk menambahkan check_* menebaknya - lakukan ini:


 [pytest] python_functions = test_* check_* 

pytest penamaan terbaik tampaknya tidak begitu membatasi, bukan? Jadi, jika Anda tidak menyukai konvensi penamaan default, ubah saja. Meskipun demikian, saya mendorong Anda untuk memiliki alasan yang lebih kuat untuk keputusan semacam itu. Bermigrasi ratusan file uji jelas merupakan alasan yang bagus.


Larangan XPASS


Pengaturan xfail_strict = true berarti tes yang ditandai dengan @pytest.mark.xfail tidak dikenali sebagai penyebab kesalahan. Saya pikir pengaturan ini harus selalu demikian. Untuk informasi lebih lanjut tentang token xfail lihat “Menandai tes yang menunggu kegagalan” pada halaman 37.


Cegah Konflik Nama File


Kegunaan memiliki file __init__.py di setiap subdirektori uji proyek membingungkan saya untuk waktu yang lama. Namun, perbedaan antara memiliki atau tidak memilikinya adalah sederhana. Jika Anda memiliki file __init__.py di semua subdirektori pengujian, Anda dapat memiliki nama file pengujian yang sama di banyak direktori. Dan jika tidak, maka ini tidak akan berhasil.


Berikut ini sebuah contoh. Direktori a dan b keduanya memiliki file test_foo.py . Tidak peduli apa isi file-file ini, tetapi untuk contoh ini mereka terlihat seperti ini:


ch6 / dups / a / test_foo.py
 def test_a(): pass 


ch6 / dups / b / test_foo.py
 def test_b(): pass 

Dengan struktur direktori ini:


 dups ├── a │ └── test_foo.py └── b └── test_foo.py 

File-file ini bahkan tidak memiliki konten yang sama, tetapi tes rusak. Anda akan dapat menjalankannya secara terpisah, tetapi tidak ada cara untuk menjalankan pytest dari direktori dups :


 $ cd /path/to/code/ch6/dups $ pytest a ============================= test session starts ============================= collected 1 item a\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest b ============================= test session starts ============================= collected 1 item b\test_foo.py . ========================== 1 passed in 0.05 seconds =========================== $ pytest ============================= test session starts ============================= collected 1 item / 1 errors =================================== ERRORS ==================================== _______________________ ERROR collecting b/test_foo.py ________________________ import file mismatch: imported module 'test_foo' has this __file__ attribute: /path/to/code/ch6/dups/a/test_foo.py which is not the same as the test file we want to collect: /path/to/code/ch6/dups/b/test_foo.py HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules !!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!! =========================== 1 error in 0.34 seconds =========================== 

Tidak ada yang jelas!
Pesan kesalahan ini tidak menunjukkan apa yang salah.


Untuk memperbaiki tes ini, cukup tambahkan file __init__.py kosong ke subdirektori. Berikut adalah contoh direktori dups_fixed dengan nama file duplikat yang sama, tetapi dengan file __init__.py ditambahkan:


 dups_fixed/ ├── a │ ├── __init__.py │ └── test_foo.py └── b ├── __init__.py └── test_foo.py 

Sekarang mari kita coba lagi dari tingkat atas di dups_fixed :


 $ cd /path/to/code/ch6/ch6/dups_fixed/ $ pytest ============================= test session starts ============================= collected 2 items a\test_foo.py . b\test_foo.py . ========================== 2 passed in 0.15 seconds =========================== 

Jadi akan lebih baik.


Tentu saja, Anda dapat meyakinkan diri sendiri bahwa Anda tidak akan pernah memiliki nama file duplikat, jadi itu tidak masalah. Semuanya, seperti, normal. Tetapi proyek terus bertambah dan katalog pengujian terus bertambah, dan Anda pasti ingin menunggu sampai terjadi pada Anda sebelum mengurusnya? Saya katakan, letakkan saja file-file ini di sana. Jadikan kebiasaan dan jangan khawatir lagi.


Latihan


Di Bab 5, Plugin, di halaman 95, Anda membuat sebuah plugin bernama pytest-nice yang menyertakan opsi baris perintah --nice. Mari kita bahas untuk menyertakan opsi pytest.ini yang disebut nice.


Pada Bab 5, “Plugin” di halaman 95, Anda membuat sebuah plugin bernama pytest-nice yang menyertakan --nice baris perintah --nice . Mari kita kembangkan ini untuk menyertakan opsi pytest.ini disebut nice .


  1. Tambahkan baris berikut ke fungsi kait pytest_addoption pytest_nice.py : parser.addini('nice', type='bool', help='Turn failures into opportunities.')
  2. Tempat di plugin yang menggunakan getoption() juga harus memanggil getini('nice') . Buat perubahan ini.
  3. Verifikasi ini secara manual dengan menambahkan nice ke file pytest.ini .
  4. Jangan lupa tentang tes plugin. Tambahkan tes untuk memverifikasi bahwa parameter yang nice dari pytest.ini berfungsi dengan benar.
  5. Tambahkan tes ke direktori plugin. Anda perlu menemukan beberapa fitur Pytester tambahan .

Apa selanjutnya


Walaupun pytest sangat kuat dengan sendirinya - terutama dengan plugin - pytest juga terintegrasi dengan baik dengan pengembangan perangkat lunak lain dan alat pengujian perangkat lunak. Pada bab selanjutnya, kita akan memeriksa penggunaan pytest bersama dengan alat pengujian kuat lainnya.


Kembali Selanjutnya

Source: https://habr.com/ru/post/id448796/


All Articles