آموزش جنگو (Basic) : جلسه یازده | تست (test)

در این جلسه به بررسی تست ها در جنگو خواهیم پرداخت و ادامه پروژه قبلی را خواهیم نوشت . با ما همراه باشید . آموزش جنگو : جلسه یازده | تست (test)تست (test) چیست ؟تست ها فرایند هایی هستند که نحوه عمل کردن کد شما را بررسی میکنند .تست ها میتوانند برای بررسی های کوچک و بزرگ وبسایت استفاده شوند . برای مثال آیا یک صفحه مقادیر درستی را از دیتابیس به کاربر باز میگرداند یا سری مقادیری که کاربر وارد سایت میکند ,او را به صفحه درستی هدایت میکنند ؟ این ها هیچ تفاوتی با اینکه شما سرور را اجرا کنید و روی وبسایت تست های خودتان را انجام بدهید یا با استفاده از شل دیتابیس را بررسی کنید ,ندارند !آنچه در automated test تفاوت دارد , این است که آنها توسط سیستم برای شما اجرا می شوند پس وقت شما را برای تست کردن دستی اپ نمی گیرند . شما میتوانید تغییرات خود را در اپ خود ایجاد کنید و یکبار تست های خود را اجرا کنید تا برای شما سلامت کد اپ را بررسی کنند .چرا ما باید تست ها را بسازیم ؟شاید فکر کنید ما اپ نظرسنجی خود را ساخته ایم و به درستی کار میکند . چرا باید درباره تست های اتوماتیک بدانیم و از آنها استفاده کنیم . در واقع وبسایت رسمی جنگو این گونه جواب این سوال را داده است که : اگر ساخت یک برنامه نظرسنجی اخرین قدم از یادگیری تمام جنگو باشد , پس شما لازم ندارید تا تست های اتوماتیک را یاد بگیرید , اما اگر اینطور نیست ,پس بهتر است شروع به یادگیری آن تا اخرین قدم کنید و دلایلی که میتوانیم برای جواب این سوال به آنها اشاره کنیم :تست ها زمان شما را می خرند !تا یک نقطه خاص میتوانید به صورت دستی برنامه خود را تست کنید , اما پس از چندی ممکن است برنامه شما حاوی ده ها فرایند و ویوهای پیچیده باشد که تست کردن آنها زمان زیادی را بگیرد . شما در این مرحله (که اکثرا در شرکت ها و وبسایت های بزرگ این اتفاق میافتد ) میتوانید از تست های اتوماتیک استفاده کنید و پس از هربار ایجاد تغییر در اپ ,فقط تست خود را اجرا کنید تا مشکلات اپ شما شناسایی شود .تست ها مشکلات را شناسایی میکنند و از آنها جلوگیری میکنند !در جلوتر متوجه خواهید شد که گاهی وقت ها انقدر کد ها پیچیده می شوند که حتی خود برنامه نویس هم نمیفهمد این کد چه کاری انجام می دهد ! تست ها به شما کمک میکنند تا هر بار از نحوه عملکرد کد خود مطمین شوید و مشکلاتی احتمالی که کد شما ممکن است داشته باشد و شما متوجه آن نشده باشید را به شما می گویند . (البته اشاره دقیقی نمیکنند و فقط اشاره ای به محلی که باگ در آن به وجود امده است میکنند )تست ها به شما در کار گروهی نیز کمک میکنند !شما در پروژه های خود ممکن است مجبور شوید با تیمی از برنامه نویسان جنگو همراه شوید تا پروژه ای را به اتمام برسانید . تست های اتوماتیک به شما کمک می کنند تا برنامه نویسان دیگر با خواندن آنها و اجرای آنها نحوه عملکرد کد شما را به راحتی تمام درک کنند .استراتژی ایجاد تست ها (پایه)راه های زیادی برای ایجاد یک تست وجود دارد .بعضی از برنامه نویسان ,قبل از اینکه کدی را بنویسند ,تست های خود را ایجاد میکنند . این ممکن است کمی دیوانگی به نظر برسد ولی مثال واقعی ان مانند مردمی است که ابتدا مشکلی را تعریف میکنند و سپس برای آن یک راه حل می سازند .بعضی دیگر از برنامه نویسان تازه وارد دیگر ,ابتدا کد را مینویسند و سپس برای آن چندین تست تعریف میکنند . گاهی ممکن است بگویند که باید زودتر تست ها را مینوشتیم اما هیچ وقت برای شروع دیر نیست و آنها میتوانند هر زمان که می خواهند تست های خود را شروع به نوشتن و اجرا کنند .البته گاهی اوقات شما چندین هزار خط کد جنگو را در دسترس دارید و در آن هنگام است که نمیدانید دقیقا از کجا باید نوشتن تست ها را شروع کنید . بخاطر همین بهترین روش نوشتن یک تست در ابتدای نوشتن کد ها است تا با تغییرات و پیشرفت کد ها , تست ها به جای خود باقی بمانند . حال بیایید صحبت را کنار بگذاریم و از راه درست ,نوشتن تست ها را اغاز کنیم .شروع نوشتن تست ها : رفع یک باگ!در اپ polls , یک باگ وجود دارد که اکنون میتوانیم آن را اصلاح کنیم . متد ()question.was_published_recently در صورتی که فیلد pub_date شی شما مربوط به گذشته باشد ,مقدار True را باز میگرداند (که درست است) ولی همچنین هنگامی که تاریخ pub_date شما مربوط به آینده باشد ,باز مقدار True را به شما باز میگرداند که اشتباه است و نباید اینطور باشد !وارد شل شوید تا باگ را از نزدیک بررسی کنید :$ python manage.py shellکد های زیر را در شل بنویسید :>>> import datetime >>> from django.utils import timezone >>> from polls.models import Question >>> # create a Question instance with pub_date 30 days in the future >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) >>> # was it published recently? >>> future_question.was_published_recently() Trueدر خطوط ابتدایی موارد مورد نیاز را import کردیم و در خط بعدی یک شی از مدل Question ساخته ایم . مقدار pub_date این شی به وسیله توابع مربوط به زمان در جنگو ,روی سی روز آینده تنظیم شده است . (این خط و تنظیم زمان برای جنگو توضیحات بلندی دارد که در بعد ها به آن خواهیم پرداخت) سپس متد ()was_published_recently را روی شی فرا خوانده ایم و میبینیم که مقدار True را به ما بازگردانده است و این غلط است !بررسی باگ به وسیله تست هاکاری که در شل انجام دادیم , درست مانند کاری است که در یک تست اتوماتیک انجام می شود . حال بیایید کاری که در اینجا انجام دادیم را دوباره در تست ها بنویسیم .تست ها به صورت معمول در فایل tests.py که در هر اپ یکی از این فایل به صورت خودکار برای شما ایجاد میشود, نوشته می شوند . جنگو در هر اپ به دنبال این فایل میگردد و تست های درون آن را برای شما اجرا میکند . در فایل polls/tests.py کد های زیر را بنویسید .import datetime from django.test import TestCase from django.utils import timezone from .models import Question class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): &quot&quot&quot was_published_recently() returns False for questions whose pub_date is in the future. &quot&quot&quot time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False)بسیار خب ,هر تست کلاسی است که از TestCase ارث بری میکند و میتوانید آن را از django.test وارد کنید . همچنین تابعی درون کلاس وجود دارد که آرگومان self را دریافت میکند و به وسیله آن عملیات هایی را انجام می دهد . در خطوط ابتدایی تابع ما زمان (30 روز دیگر) را تعریف کرده ایم و سپس شی ای با pub_date مربوط به زمان 30 روز آینده ساخته ایم . در خط آخر نیز چک کردیم که آیا متد ()was_published_recently به ما مقدار False را بازمیگرداند یا خیر؟اجرای تست هادر ترمینال وارد روت پروژه خود شوید و دستور زیر را وارد کنید (روت = مسیری که فایل manage.py در آن قرار دارد).$ python manage.py test pollsخروجی دستور بالا ,چیزی شبیه به زیر است :Creating test database for alias 'default'... System check identified no issues (0 silenced). F ====================================================================== FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File &quot/path/to/mysite/polls/tests.py&quot, line 16, in test_was_published_recently_with_future_question self.assertIs(future_question.was_published_recently(), False) AssertionError: True is not False ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1) Destroying test database for alias 'default'...نکته : اگر خروجی شما شبیه به بالا نیست ,احتمالا در نوشتن بخشی از برنامه دچار اشتباه شده اید . بهتر است ارور مورد نظر را سرچ کنید ,گرچه بیشترین احتمال این است که به شما ارور NameEror بازگردانده شود . در آن صورت شما باید دوباره timezone را import کنید . (در مطالب قبلی به آن اشاره کرده بودیم )چیزی که در اینجا اتفاق افتاد , مانند فرایند زیر بود :1-با اجرای دستور python3 manage.py test polls جنگو به دنبال فایل tests.py در اپ polls رفت .2-در این فایل یک کلاس که از django.test.testcase ارث بری میکرد , پیدا شد .3-جنگو یک دیتابیس نابود شدنی برای اجرای تست ایجاد کرد .4-جنگو در کلاس شما ,به دنبال تابعی گشت که نام آن با test شروع شده باشد . (زیرا شما ممکن است توابع مختلفی درون کلاس خود داشته باشید )5-محتویات درون تابع اجرا شد و شی ای از مدل Question که فیلد pub_date آن مربوط به سی روز آینده است, ایجاد شد .6-در آخرین خط از تابع به وسیله متد ()assertIs ,بررسی شد که مقدار بازگشتی از متد was_published یک مقدار صحیح یا همان True است . در صورتی که ما حدس میزدیم مقدار False باید بازگشت داده شود .این به ما نشان می دهد که تست ها چگونه میتوانند به شما در پیدا کردن باگ ها کمک کنند . (گرچه اکثر برنامه نویسان تست ها را اجرا نمی کنند و فقط به صورت دستی به امتحان کردن برنامه خود می پردازند)اصلاح باگما همین الان مشکل را میدانیم : متد ()was_published_recently به شما در صورتی که تاریخ pub_date را برای آینده نیز تنظیم کنید , True را بازخواهد گرداند . پس فقط کافی است این متد را در فایل models.py اصلاح کنید. در فایل polls/models.py و کلاس Question متد را بازنویسی کنید :def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= nowحال دوباره تست را اجرا کنید . (کد های تابع بالا توضیحات خاصی ندارد و فقط نیازمند بلد بودن تابع time در جنگو است )Creating test database for alias 'default'... System check identified no issues (0 silenced). . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK Destroying test database for alias 'default'...ما پس از شناسایی باگ , تستی نوشتیم تا باگ ایجاد شده را نمایش دهیم و آن را برطرف کردیم تا تستی که ایجاد کردیم ,درست کار کند .ممکن است در آینده با باگ های بیشتری مواجه شوید ,اما می توانید مطمئن باشید که این باگ کاملا برطرف شده است زیرا ما با ایجاد تست و اجرای آن ,این باگ را کاملا در اپ برطرف کرده ایم .نوشتن تست های بیشترحال بیایید تست های بیشتری بر روی اپ انجام بدهیم . ممکن است در رفع باگ قبلی اشتباهاً , باگ دیگری را در اپ ایجاد کرده باشید ; به همین علت بهتر است تست های بیشتری بنویسیم تا کاملا از سلامت اپ مطمئن شویم.دو تابع دیگر را در کلاس تست ایجاد کنید تا رفتار تابع نوشته شده در مدل Question را بررسی کنید . فایل polls/tests.py را تغییر دهید :def test_was_published_recently_with_old_question(self): &quot&quot&quot was_published_recently() returns False for questions whose pub_date is older than 1 day. &quot&quot&quot time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) def test_was_published_recently_with_recent_question(self): &quot&quot&quot was_published_recently() returns True for questions whose pub_date is within the last day. &quot&quot&quot time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True)اکنون ما سه تست داریم که ثابت میکنند , ()Question.was_published_recently برای مقادیری از آینده, گذشته و اکنون مقدار های درستی را بازمیگرداند .حال دوباره می توان گفت که اپ polls یک اپ سالم است (حداقل در مورد این باگ) . علاوه بر این , هرگونه تغییر در اپ و یا اضافه کردن ویژگی های جدید باعث ایجاد این نوع از باگ در اپ شما نخواهد شد !در این جلسه نحوه ایجاد تست ها را با هم بررسی کردیم و یکی از باگ های پروژه قبلی خود را برطرف کردیم . در جلسه بعدی به ایجاد تست های پیشرفته تر برای ویو ها و تست هایی برای سمت کلاینت در جنگو , خواهیم پرداخت .