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

در این جلسه به بررسی بیشتر و کامل تر تست ها در جنگو خواهیم پرداخت . با ما همراه باشید .آموزش جنگو : جلسه دوازده | تست (test) | بخش دومنوشتن تست برای ویو ها (Views Test)در اپ polls شما همه ی سوالات را در صفحه index خواهید دید ,حتی سوالاتی که فیلد pub_date آنها تاریخی مربوط به آینده داشته باشد (فیلد pub_date تاریخ انتشار سوال را در خود جای می دهد). این اشتباه است و سوالاتی که تاریخ انتشار آنها مربوط به آینده است نباید در صفحه index نمایش داده شوند .وقتی باگ قبلی را رفع کردیم ,ابتدا تستی نوشتیم تا باگ را پیدا کنیم و سپس کد را تغییر دادیم تا باگ برطرف شود . اما واقعا مهم نیست که کار ها با چه ترتیبی انجام می شوند .این بار قرار است باگ بالا را طوری کشف کنیم که کاربر در مرورگر خود با آن رو به رو می شود . حال قبل از اینکه شروع به رفع باگ کنیم ,بیایید تا نگاهی به ابزار هایی که در اختیار داریم ,بیندازیم .نوشتن تست سمت کاربرجنگو یک کاربر آزمایشی یا client در اختیار شما قرار داده است که از ان در شل جنگو و یا فایل tests.py میتوانید استفاده کنید . این client میتواند برای شما تعامل کاربر با ویو های وبسایت شما را شبیه سازی کند !ابتدا شل را باز کنید و تنظیمات مربوط به تست ها را در آن با دستورات زیر ایجاد کنید . می خواهیم مانند تست قبلی چندین کار را قبل از نوشتن تست ها در tests.py انجام دهیم تا باگ ما شناسایی شود . ابتدا وارد شل شوید :$ python manage.py shellسپس می نویسیم :>>> from django.test.utils import setup_test_environment >>> setup_test_environment()در خط اول , Django.test.utils.setup_test_enviroment در شل جنگو import شده است . این به شما ابزار های موردنیاز برای نوشتن تست ویو ها را خواهد داد . همچنین در خط بعدی , از آن استفاده کردیم تا آن را فعال کنیم . این تابع ,برای شما تمپلیت رندر شده ای را ایجاد خواهد کرد که در آن به بعضی از ویژگی های پاسخ یا response دسترسی دارید . برای مثال شما میتوانید در آن مقدار response.context را بررسی کنید که در حالت عادی به آن دسترسی ندارید . نکته ویژه این است که این تابع برای شما دیتابیس تستی نمی سازد و از دیتابیسی که هم اکنون تنظیم شده است استفاده میکند .به همین علت ممکن است پاسخ هر کدام از ویو ها در آن به تعداد و یا سوالات شما در دیتابیس اصلی بستگی داشته باشد .نکته : ممکن است در حین تست کردن ویو ها با ارور های عجیبی مواجه شوید که به دلیل درست نبودن تنظیمات TIME_ZONE به وجود می آیند . به همین علت قبل از شروع ,در فایل settings.py تنظیمات TIME_ZONE را بررسی کنید .حال باید کلاس تست کلاینت (Client Test) را import کنیم . (در فایل tests.py ما از کلاس TestCase ارث بری میکنیم ,پس در آنجا نیازی به این کار نخواهد بود )>>> from django.test import Client >>> # create an instance of the client for our use >>> client = Client()حالا بیایید تا از client تستی خود ,درخواست کنیم تا برای ما کار هایی را انجام بدهد .>>> # get a response from '/' >>> response = client.get(&quot/&quot) Not Found: / >>> # we should expect a 404 from that address; if you instead see an >>> # &quotInvalid HTTP_HOST header&quot error and a 400 response, you probably >>> # omitted the setup_test_environment() call described earlier. >>> response.status_code 404 >>> # on the other hand we should expect to find something at '/polls/' >>> # we'll use 'reverse()' rather than a hardcoded URL >>> from django.urls import reverse >>> response = client.get(reverse(&quotpolls:index&quot)) >>> response.status_code 200 >>> response.content b'\n <ul>\n \n <li><a href=&quot/polls/1/&quot>What's up?</a></li>\n \n </ul>\n\n' >>> response.context[&quotlatest_question_list&quot] <QuerySet [<Question: What's up?>]>نکته : مقدار response.content برای شما کد html ای که نوشتید و در ویو آن را رندر میکنید را برای شما باز میگرداند !اصلاح ویو (index view)صفحه index به شما سوالاتی را نشان می دهد که هنوز منتشر نشده اند . پس بیایید این مشکل را درست کنیم . شما در صفحات قبلی ویو index را بر اساس based_views نوشته بودید :class IndexView(generic.ListView): template_name = 'polls/index.html' context_object_name = 'latest_question_list' def get_queryset(self): return Question.objects.order_by('-pub_date')[:5]ما باید تابع ()get_queryset را تغییر دهیم تا هنگام جستجو در دیتابیس ,تاریخ فیلد pub_date هر رکورد را با ()timezone.now مقایسه کند (منظور این است آن تاریخ را با زمان حال یا اکنون مقایسه کند) . ابتدا پس آن را import کنید .from django.utils import timezoneسپس تابع را به صورت زیر تغییر دهید :def get_queryset(self): &quot&quot&quot Return the last five published questions (not including those set to be published in the future). &quot&quot&quot return Question.objects.filter( pub_date__lte=timezone.now() ).order_by('-pub_date')[:5]کد Question,obejcts.filter(pub_date__lte=timezone.now()).order_by(‘-pub_date’)[:5] به شما 5 سوال آخری که منتشر شده اند را نشان خواهد داد . این خط بررسی میکند که فیلد pub_date هر کدام از آن رکورد ها کوچکتر یا مساوی زمان حال باشند . (lte__ به معنای less or equel است و استفاده از آن بعد از نام یک فیلد به شما امکان مقایسه آن را میدهد . گرچه انواع بیشتری از این مقایسه ها در جنگو وجود دارد که بعدا به آنها خواهیم پرداخت)تست کردن ویو اصلاح شدهاکنون میتوانید مرورگر خود را باز کنید . سوالاتی در ادمین پنل با تاریخ های انتشار متفاوت ایجاد کنید . صفحات را بررسی کنید و ببینید آیا ویو شما درست کار میکند یا خیر ؟ اما ما نمی خواهیم هر بار که تغییری در اپ ایجاد میکنیم مجبور به انجام این کارها شویم ,پس بیایید تستی بر اساس کاری که در قبل در شل جنگو انجام دادیم ,بنویسیم .در فایل polls/tests.py خط زیر را اضافه کنید .from django.urls import reverseحال باید فایل را به صورت زیر تغییر دهید تا تستی ایجاد کنیم که سوالات را برای ما بسازد و آنها را بررسی کند .def create_question(question_text, days): time = timezone.now() + datetime.timedelta(days=days) return Question.objects.create(question_text=question_text, pub_date=time) class QuestionIndexViewTests(TestCase): def test_no_questions(self): response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, &quotNo polls are available.&quot) self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_past_question(self): question = create_question(question_text=&quotPast question.&quot, days=-30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question], ) def test_future_question(self): create_question(question_text=&quotFuture question.&quot, days=30) response = self.client.get(reverse('polls:index')) self.assertContains(response, &quotNo polls are available.&quot) self.assertQuerysetEqual(response.context['latest_question_list'], []) def test_future_question_and_past_question(self): question = create_question(question_text=&quotPast question.&quot, days=-30) create_question(question_text=&quotFuture question.&quot, days=30) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question], ) def test_two_past_questions(self): question1 = create_question(question_text=&quotPast question 1.&quot, days=-30) question2 = create_question(question_text=&quotPast question 2.&quot, days=-5) response = self.client.get(reverse('polls:index')) self.assertQuerysetEqual( response.context['latest_question_list'], [question2, question1], )بیایید برخی از خط کد ها را بررسی کنیم :ابتدا یک تابع , ()create_question ایجاد کردیم که برخی از عملیات های تکراری که برای ساخت یک question یا سوال است را انجام می دهد .دومین تابع ()test_no_question , سوالی را ایجاد نمیکند ولی بررسی میکند که اگر latest_question_list خالی باشد و به زبانی ساده تر سوالی وجود نداشته باشد ,مقدار نظرسنجی ای وجود ندارد را به شما بازگرداند . نکته ویژه این است که کلاس TestCase به شما توابع و متود هایی از assertion ارائه میکند که ما در اینجا از آنها استفاده میکنیم ولی توضیحات آنها برای صفحات بعدی کتاب می ماند .در سومین تابع که نام آن ()test_past_question است , ما سوالی که تاریخ انتشار آن برای گذشته باشد را میسازیم و اطمینان حاصل میکنیم که در صفحه index نمایش داده شود (در واقع در لیست latest_question_list وجود داشته باشد ) .در چهارمین تابع ()test_future_question ,سوالی را با تاریخ انتشار مربوط به آینده ایجاد میکنیم . به یاد داشته باشید که سوالی که در تست قبلی ایجاد شد دیگر در دیتابیس وجود نخواهد داشت زیرا در تست ها هر بار پس از اجرای یک تابع ,دیتابیس ریست میشود .و در ادامه آنها ما یکبار سوالی در اینده و در گذشته ایجاد کردیم و یکبار دو سوال با تاریخی مربوط به آینده ایجاد کردیم . در واقع فهمیدید که تست ها میتوانند به شما کمک کنند تا در زمان صرفه جویی کنید و بدون نیاز به هربار تست دستی و رفتن به ادمین پنل و ایجاد سوال ,تمام این کار ها را به جنگو بسپارید .ایجاد تست برای DetailViewبا اینکه اپ شما به درستی کار می کند و سوالاتی که باید در آینده نمایش داده شوند , در لیست صفحه index نمایش داده نمی شوند ,اما اگر کاربر بتواند آدرس مربوط به صفحه جزییات آنها را حدس بزند ,میتواند به آن صفحه برود و به گزینه ها رای بدهد . این یک باگ جدید در اپ است و ما باید آن را برطرف کنیم . در polls/views.py ویو مربوط به صفحه Detail را به صورت زیر تغییر بدهید :class DetailView(generic.DetailView): ... def get_queryset(self): &quot&quot&quot Excludes any questions that aren't published yet. &quot&quot&quot return Question.objects.filter(pub_date__lte=timezone.now())سپس چند تست اضافه میکنیم تا بررسی کنیم که سوالاتی که تاریخ انتشار گذشته دارند نمایش داده شوند و بقیه آنها نه (منظور این است که در صفحه جزییات آنها نمایش داده شوند یا خیر) :class QuestionDetailViewTests(TestCase): def test_future_question(self): &quot&quot&quot The detail view of a question with a pub_date in the future returns a 404 not found. &quot&quot&quot future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) def test_past_question(self): &quot&quot&quot The detail view of a question with a pub_date in the past displays the question's text. &quot&quot&quot past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) response = self.client.get(url) self.assertContains(response, past_question.question_textنکته : اگر بخشی از کد ها یا متود هایی از آنها برایتان معنایی ندارند و یا با آنها اشنا نیستید نگران نباشید ! بخش تست کردن در جنگو از اهمیت ویژه ای برخوردار نیست و اطمینان میتوان حاصل کرد که شما اکنون هر آنچه مقدمات شروع کار با جنگو است را بلدید (البته کار با استاتیک فایل ها و ادمین پنل دو بخش بعدی مقالات هستند که به آنها خواهیم پرداخت ) .در این جلسه به بررسی کامل تست ها در جنگو پرداختیم و تست های بیشتری را برای پروژه قبلی خود نوشتیم . در جلسه بعدی به بررسی فایل های استاتیک در جنگو خواهیم پرداخت .