├── 1.avif ├── LICENSE └── README.md /1.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SepehrRasouli/Unit-Testing-in-Python-Best-Practices/53924a31800404c45e4ac6e8eabc235682400f2b/1.avif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 SepehrRS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unit testing in python best practices 2 | | Note :این ترجمه ی [مقاله ای](https://imsadra.me/unit-testing-in-python-and-best-practices) هست که [صدرا](https://imsadra.me) نوشته 3 | 4 | در این مقاله من به شما کمک میکنم تا مفاهیم اولیه Unit Testing در پایتون را فرا بگیرید، و بعد درمورد بست پرکتیس ها صحبت میکنیم که تست هایتان را بسیار قابل فهم تر میکنند. 5 | اگر به دنبال جایی برای شروع تست نوشتن برای پروژه های پایتونتان میگردید، اینجا بهترین جاست. بیایید کمی جادو ببینیم. 6 | 7 | ## ۱. تست نرم افزار & Unit Test ها 8 | تست نرم افزار بخشی است که ما پروژه امان را تست میکنیم تا مطمئن شویم ارور های درستی را برمیگرداند، مقادیر مورد انتظار را میدهد و... . 9 | ما میتوانیم پروژه امان را با چندین روش تست کنیم. برای همین است که چندین راه حل برای تست پروژه ها داریم. یکی از این راه حل ها Unit Testing است. 10 | 11 | در یک خودرو ما مراکز کنترل متفاوت داریم. مرکز کنترل رادیو، چراغ ها، باطری، ماژول های سیستماتیک متفاوت و... . هرگاه برد الکتریکی خودروی شما دچار مشکل میشود، به راحتی میتوانید هر مرکز کنترل را بررسی کنید و منبع مشکل را پیدا کنید. این کار زمانی بسیار خوب است که مراکز کنترل به همدیگر ربط داشته باشند، به طور مثال، مرکز کنترل رادیو نیاز به باطری دارد. وقتی که رادیو به درستی کار نمیکند، شما هم باطری و هم رادیو را چک میکنید تا مشکل را حل کنید. 12 | 13 | تصور کنید نرم افزاری وجود داشت که تمامی مراکز نرم افزارتان را تست میکرد و گزارشی به شما تحویل میداد! اگر من بودم، همچین محصولی را حتما میخریدم. 14 | 15 | ما میدانیم که Unit test ها چه چیزی هستند. هر پروژه ای از مراکز مختلفی ساخته شده. تمامیت بین این مراکز یک سیستم بی عیبی درست میکند که نیازی به روش تست نویسی خودش به نام Integration Testing دارد. بیایید به Unit Test ها برگردیم. 16 | 17 | ## ۲. اصلا چرا تست بنویسیم؟ 18 | بسیاری از توسعه دهندگان هنگام نوشتن تست ها با مشکل مواجه هستند. مثلا شاید پروژه ی شما بدون تست ها به خوبی کار کند، اما آیا به راحتی قابل نگه داری هستند؟ آیا بقیه ی توسعه دهندگان از کد نویسی در پروژه ی شما خوشحال هستند؟ آیا مشارکت کنندگان جدید مرحله ی آشناییت با پروژه ی شمارا به سرعت رد میکنند ؟ 19 | 20 | * پوشش و نگه داری را بیشتر کنید. 21 | 22 | بیشتر پروژه ها با وضعیت پوشش دهی تست هایشیان قضاوت میشوند. پوشش دهی بیشتر، توسعه دهندگان خوشحال تر. 23 | 24 | * با تست ها، مشکلات را قبل از پیاده سازی حل کنید. 25 | 26 | در برنامه نویسی [TDD](https://en.wikipedia.org/wiki/Test-driven_development) توسعه دهندگان تلاش میکنند تا قبل از پیاده سازی اصلی، تست بنویسند. آنها قبل از اضافه کردن یک قابلیت به برنامه، آن را تست. و فقط هنگامی که از تست ها سربلند بیرون آمد، مرجش میکنند. 27 | 28 | * تست ها مثل مستندات هستند. 29 | 30 | با تست های تمیز، قابلیت نگه داری کدتان را بیشتر میکنید و توسعه دهندگان جدید، به راحتی میتوانند بخش های مختلف پروژه را با خواندن تست ها و پیدا کردن نیازمندی های هر بخش، به راحتی متوجه شوند. زمانی که کسی تست هارا میخواند، متوجه میشود که آن بخش باید چه کاری انجام دهد. 31 | 32 | > کد بیشتر از نوشته شدن خوانده می شود. 33 | 34 | ## ۳. تست های خود را ایزوله کنید 35 | زمانی که حرف ایزوله سازی در پایتون میشود، به یاد محیط های مجازی میوفتم. یک جورهایی یکی هستند، اما در اصل ما آنهارا برای تست نوشتن داریم. 36 | 37 | هر تستی که مینویسید باید نگه داری و اجرای ایزوله شده ای داشته باشد، هیچکدام از تست هایتان نباید هیچ بخش دیگری از پروژه ی شما یا تست های دیگر را در همان فایل یا پروژه مورد تاثیر قرار دهد. 38 | تست های شما نباید چیزهای واقعی در دیتاها یا دیتابیس ها را تغییر دهند. این معنی واقعی ایزوله سازی است که تست های شما کارشان را انجام میدهند و ردپایشان را پاک میکنند. تست های شما نباید وابسته به تست های دیگر باشند. 39 | 40 | ایزوله سازی تست های شمارا بسیار قابل خواندن میکنند. ما مدام از وابستگی میترسیم و پیدا کردن مشکل در همچین وضعیتی بسیار سخت میشود. یک تست ایزوله نشان میدهد که هر مشکلی که رخ داده در همان بلاک تست و فقط در همان بلاک تست است و نه در هیچ جای دیگر. زمانی که تمامی تست های یک پروژه را اجرا میکنید و تست شماره ی ۲ مشکل دارد، با تست های ایزوله شده، این بدین معناست که شما میتوانید مشکل را در همان تابع تست حل کنید و شانس اینکه آن مشکل در تست های دیگر هم باشد وجود ندارد. 41 | 42 | ## ۴. بیایید کمی کد بنویسیم 43 | صحبت کردن بس است، در پایتون، ما چندین ابزار قدرتمند تست نویسی داریم. ابزاری که امروز از آن استفاده میکنیم کتابخانه استاندارد [unittest](https://docs.python.org/3/library/unittest.html) که قابلیت های مختلفی بدون نصب کردن هیچ پکیج یا کتابخانه ی اضافه ای را میدهد. همچنین میتوانید از [PyTest](https://docs.pytest.org/) هم استفاده کنید. یک فایل `test.py` باز کنید و پیشنیاز ها را ایمپورت کنید. 44 | 45 | ```python 46 | import unittest 47 | from math import sqrt,power, pi 48 | ``` 49 | توجه داشته باشید که ما چند تابع ریاضی هم ایمپورت کردیم. در تست بعدی، چندین تابع از کتابخانه ی استاندارد ریاضی پایتون را امتحان میکنیم تا ببینیم آیا پاسخ درست را به ما میدهد یا نه. 50 | 51 | ```python 52 | class TestMathLib(unittest.TestCase): 53 | 54 | def test_if_sqrt_still_works(self): 55 | self.assertEqual(sqrt(25), 5) 56 | 57 | def to_test_if_pi_still_starts_with_three(self): 58 | self.assertEqual(pi // 1, 3) 59 | 60 | def test_if_power_still_works(self): 61 | self.assertEqual(pow(2, 3), 8) 62 | ``` 63 | تا الان سه تست نوشتیم. بیایید آنرا اجرا کنیم. در پایان فایل، برای قابل اجرا شدن آن از شل، این خط کد را اضافه میکنیم. 64 | ```python 65 | if __name__ == '__main__': unittest.main() 66 | ``` 67 | استفاده از دستور `python` برای اجرای تست ها به هیچ عنوان پیشنهاد نمیشود. چه میشود اگر میخواستید تست های اپلیکیشن خاصی را اجرا کنید؟! آن وقت پایتون دیگر نمیتوانست کمک کند. به راحتی از دستور `unittest` برای پایداری و قابلیت های بیشتر استفاده کنید. 68 | 69 | حالا، ترمینالی باز کنید، دایرکتوری خود را به دایرکتوری تست هایتان عوض کنید و این دستور را اجرا کنید. 70 | 71 | ```shell 72 | python -m unittest 73 | ``` 74 | و این نتیجه نهایی است. 75 | ```shell 76 | .. 77 | ---------------------------------------------------------------------- 78 | Ran 2 tests in 0.010s 79 | 80 | OK 81 | ``` 82 | به نظر میرسد که به نتیجه ای رسیده ایم! از آنجایی که اجرای `unitest` بدون هیچ فلگی تمامی تست ها را اجرا میکند، چرا فقط دوتای آن هارا اجرا کرد؟ آن نقطه ها چه میگویند؟! 83 | 84 | کتابخانه ی `unittest` فقط توابع تستی را اجرا میکند که با حرف `test` شروع شده باشند. این بدین معناست، که در تست ها، تست `pi` اجرا نشده. برای واضح تر شدن قضیه، حرف `test` را به ابتدای نام متود اضافه کنید. 85 | ```shell 86 | ... 87 | ---------------------------------------------------------------------- 88 | Ran 3 tests in 0.010s 89 | 90 | OK 91 | ``` 92 | آن حروف در ابتدای نتیجه نشان دهنده ی وضعیت اجرای هر تست است. هر تستی که دچار مشکل شود، حرف `F` و هر خطایی، برای مثال خطای سینتکس و یا غلط املایی، با حرف `F` به جای آن نقطه ها نمایش داده میشود. 93 | 94 | ## ۵. اصول DAMP و DRY در تست ها 95 | این دو اصل مشهور اجازه میدهند که تست های قابل فهم و تمیز بنویسید. در این بخش، درمورد اسامی زیبایی که میتوانید برای تست هایتان مشخص کنید صحبت میکنیم و میتوانیم از متود `setUp()` برای بهتر شدن کدهایمان استفاده کنیم. 96 | 97 | ### ۵.۱ عبارات توصیفی و معنی دار (DAMP) 98 | همانطور که قبل تر فهمیدیم، کتابخانه ی `unittest` تست هایی را که با کلمه ی `test` شروع نشده اند اجرا نمیکند. این همچنین دلیل خوبی است که نام تابعمان را به عبارت طولانی تری مثل `test_if_manipulation_works` یا `test_user_validation` تغییر دهیم. اسمی که نشان دهد تست چه کار میکند. همچنین، مطمئن شوید که نام کلاس تستتان با حرف `Test` شروع شود چون با بزرگ شدن پروژه، شاید نیاز به کلاس های مقلد داشته باشید که نباید جدا اجرا شوند و اینگونه میتوانید توابع تستتان را جدا کنید. 99 | 100 | ### ۵.۲ خودتان را تکرار نکنید (DRY) 101 | تصور کنید که به شیء ای از کلاسی به نام `Car` نیاز داریم تا متد ها و عملکردهایش را تست کنیم. 102 | ```python 103 | class TestCar(unittest.TestCase): 104 | 105 | def test_if_car_can_move_forward(self): 106 | my_car = Car() 107 | self.assertEqual(my_car.move('forward'), 'car is moving forward') 108 | 109 | def test_if_car_can_move_backward(self): 110 | my_car = Car() 111 | self.assertEqual(my_car.move('backward'), 'car is moving backward') 112 | ... 113 | ``` 114 | توجه کنید که ما داریم شیء ای از کلاس `Car` را مدام میسازیم. از متود `setUp()` استفاده کنید تا تعریف کنید چه چیزی برای چند تست نیاز دارید. این متود قبل از هر تستی که دارید اجرا میشود 115 | ```python 116 | class TestCar(unittest.TestCase): 117 | 118 | def setUp(self): 119 | self.my_car= Car() 120 | 121 | def test_if_car_can_move_forward(self): 122 | self.assertEqual(self.my_car.move('forward'), 'car is moving forward') 123 | 124 | def test_if_car_can_move_backward(self): 125 | self.assertEqual(self.my_car.move('backward'), 'car is moving backward') 126 | ... 127 | ``` 128 | این شکلی است که میتوانید اصل DRY را در کار با متود `setUp()` برای تست هایتان ببینید. برای اطلاعات بیشتر درمورد اینها، [این](https://imsadra.me/setup-and-teardown-in-python-unit-testing) لینک را ببینید. 129 | 130 | ## ۶. تقلید 131 | تقلید یک راه حل تست نیست. بلکه بخشی از سیستم های تست کردن است. برخی پروژه ها ممکن است مقلد داشته باشند، بعضی هم نه. با تقلید، میتوانید بعضی از نیازمندی هایی که تست هایتان نیاز دارند را تقلید کنید تا بتوانید یک قابلیت پروژه را تست کنید. 132 | 133 | برای مثال، تصور کنید تابعی در پروژه خود دارید که درخواست HTTP به یک API میزند و نتیجه را سریالایز میکند. حالا، میخواهید مرحله ی سریالایزیشن را با نوشتن تست، تست کنید. 134 | چه میشود اگر سرور در زمان تست کرش کند ؟ تست هایتان حتما دچار مشکل خواهند شد ولی ربطی به قابلیت های پروژتان ندارد، دارد ؟ تقصیر پروژه شما نبوده، تقصیر سرور بوده که باعث رد شدن تست ها شده. 135 | 136 | ![image1](1.avif) 137 | 138 | تقلید هنر شبیه سازی است. یک تابع میسازید که نقش سرور API را بازی میکند و آن را در بدنه ی تستتان استفاده میکنید. سپس، تست هایتان فقط هنگامی دچار مشکل میشوند که مشکلی با کد شما باشد، نه بقیه. 139 | 140 | در مثال بعدی، ما عملکرد `requests.get()` را تقلید میکنیم. ما ماژولی به نام `discovery.py` داریم که تابع رو به رو را داراست. 141 | ```python 142 | from requests import get 143 | 144 | def get_data(link, index): 145 | response = get(f'{link}/{index}') 146 | return response 147 | ``` 148 | در فایل `tests.py`، نیاز داریم تا `get_data()` را تست کنیم. معمولاً تست من هنگامی که `link` در دسترس نباشد و سرور باعث این اشکال باشد، رد میشود. اینگونه ما آن عملکرد را تقلید میکنیم. 149 | 150 | ```python 151 | from unittest import TestCase, main, mock 152 | 153 | from discovery import get_data 154 | from requests import Response 155 | 156 | succeed_response = Response() 157 | succeed_response.status_code = 200 158 | 159 | failed_response = Response() 160 | failed_response.status_code = 404 161 | 162 | class DiscoveryTest(TestCase): 163 | 164 | @mock.patch('discovery.get', return_value=succeed_response) 165 | def test_with_valid_index(self, mock_obj): 166 | response = get_data('https://google.com', 'search') 167 | self.assertEqual(response.status_code, 200) 168 | 169 | @mock.patch('discovery.get', return_value=failed_response) 170 | def test_with_invalid_index(self, moch_obj): 171 | response = get_data('https://google.com', 'somewhere') 172 | self.assertEqual(response.status_code, 404) 173 | 174 | if __name__ == '__main__': main() 175 | ``` 176 | در ابتدا، این پیاده سازی ممکن است کمی گیج کننده به نظر برسد. ما به سادگی داریم `get()` را از ماژول `discovery` تقلید میکنیم همانطور که در دکوریتور های پچ میبینید. سپس، مقداری که انتظارش را داریم را تعیین میکنیم. 177 | 178 | در پکیج `unittest`، شما میتوانید از مقلد ها به روش های مختلفی استفاده کنید. میتوانید آن هارا به عنوان دکوریتور ها، Context Manager ها و یا حتی خالصانه در متود های `setUp` و `tearDown` از آن ها استفاده کنید. من ترجیح میدهم از دکوریتور ها استفاده کنم چون خوانا تر هستند. 179 | 180 | ## ۷. بست پرکتیس ها 181 | ما تا الان درمورد مباحث اصلی صحبت کردیم. در این بخش میخواهیم نگاهی به بست پرکتیس هایی بیندازیم که میتوانند تست هایتان را بهتر و خوانا تر کنند. 182 | 183 | * تست هایتان را سریع کنید. 184 | 185 | توسعه دهندگان انتظار تست های سریع و یکراست دارند. تصور کنید توسعه دهنده ی متن بازی ای میخواهد ویژگی ای را در پروژه شما بهتر کند. او به روش توسعه تست های شما اهمیت نمیدهد، او فقط میخواهد تغییراتش را اجرا، آن هارا تست و مشکلات و باگ هارا حل کند. اگر تست ها زیادی طول بکشند، ممکن است دیگر تغییراتش را تست نکند. 186 | 187 | * تست هایتان را خوانا و ساده طراحی کنید. 188 | 189 | همیشه به کسی که میخواهد تست های شما و بهینه سازی هایی که او میخواهد بر اساس تست های شما انجام دهد فکر کنید. 190 | تست هایتان ممکن است بسیار ارزشمند تر از داکیومنت های شما برای توسعه دهندگانتان باشد، چون آنها آموزش دیده اند که فنی بیاموزند. 191 | 192 | * از اصول DRY و DAMP در تست هایتان استفاده کنید. 193 | 194 | داشتن این متود ها به شما کمک میکند تست های بهتری طراحی کنید. یک تست خوب اسامی خوبی دارد. ساده، ایزوله شده و به خوبی نگه داری شده است. 195 | 196 | * قواعدی برای ذخیره تست های پروژه خود داشته باشید. 197 | 198 | در پایتون بست پرکتیس نگه داشتن فایل های تستتان داشتن پکیجی به اسم `tests` و نگه داشتن تست ها در آنجاست. مطمئن شوید که اسامی فایل های تست هایتان با `test_` شروع بشود تا اجازه دهد اجرا کننده های تست سریعتر تست های شما را پیدا کنند. پکیج `unittest` قابلیت تست پیدا کردنی دارد که همیشه به دنبال کلمه ی کلیدی `test` میگردد. 199 | ساختمان فایل بسیار خوبی، بدین شکل خواهد بود: 200 | ```shell 201 | project 202 | ├── accounting 203 | ├── transaction 204 | ├── management 205 | ├── tests 206 | │ ├── __init__.py 207 | │ ├── test_accounting.py 208 | │ ├── test_management.py 209 | │ └── test_transaction.py 210 | ├── README.md 211 | └── app.py 212 | ``` 213 | * تست های خود را کراس پلتفرم (Cross-Platform) کنید. 214 | 215 | تست های کراس پلتفرم زمانی به کمک شما میایند که توسعه دادن CI/CD به میان میاید که سیستم های اتوماتیک با تست های شما کار میکنند. داشتن ساختمان ثابتی برای تست هایتان کلید اصلی است. به زبانی واضح تر، شما ساختمان زیبایی از تست ها دارید که میتوانید اجرای برخی تست ها را با چند گزینه یا دستور کنترل و مدیریت کنید. 216 | 217 | ## نتیجه‌ گیری 218 | ما به آخر رسیدیم اما تست کردن هیچوقت به پایان نمیرسد. داشتن پلتفرم خوبی برای تست کردن مطمئناً وقت بسیار زیادی برای تیم شما میخرد. ما در ابتدا یک معرفی ساده به یونیت تستینگ در پایتون دیدیم و سپس درمورد بست پرکتیس ها و اصولی که قابلیت های تست کردن شما را بهبود میبخشند صحبت کردیم. 219 | --------------------------------------------------------------------------------