diff --git a/README.md b/README.md index 45e9d35498be2110a90b4b957154e7e2e299cead..f7ee06bf2c6819cbc8c97dc799ac9fc83787bb03 100644 --- a/README.md +++ b/README.md @@ -122,4 +122,270 @@ def test_input_todo(self): - Digunakan variabel `MAX_WAIT` sebagai penanda batas waktu untuk melakukan *looping* menunggu halaman selesai di-*load*. Variabel ini bernilai 10 yang berarti 10 detik. - Diberikan potongan kode `while True` dimana Selenium akan terus mengecek apakah terdapat kata `'Dummy'` dalam daftar tabel yang ditampilkan. -- Diberikan *error handling* pada potongan kode `except (AssertionError, WebDriverException) as e` dimana dilakukan pengecekan apakah waktu yang berjalan sudah melebihi nilai `MAX_WAIT`. Jika sudah, maka kedua *exeption* tersebut akan di-*raise* dan test gagal. Sedangkan jika belum, akan dilakukan *sleep* selama 0.5 detik sebelum kembali melakukan pengecekan. \ No newline at end of file +- Diberikan *error handling* pada potongan kode `except (AssertionError, WebDriverException) as e` dimana dilakukan pengecekan apakah waktu yang berjalan sudah melebihi nilai `MAX_WAIT`. Jika sudah, maka kedua *exeption* tersebut akan di-*raise* dan test gagal. Sedangkan jika belum, akan dilakukan *sleep* selama 0.5 detik sebelum kembali melakukan pengecekan. + +## Penjelasan Tutorial 4 + +Berdasarkan buku **Test-Driven Development with Python 2nd Edition,** tutorial ini memiliki beberapa keterkaitan dengan materi yang disajikan di bab 7 (**Working Incrementally**), diantaranya adalah sebagai berikut. + +- Implementasi *method*-*method* pada *unit test* untuk pengecekan *redirection* dan penggunaan *template file* oleh pada berkas `views.py`. Diantaranya sebagai berikut. + + ```python + class Tutorial2UnitTest(TestCase): + + def test_use_correct_template(self): + response = self.client.get('/tutorial-2/') + self.assertTemplateUsed(response, 'tutorial_2.html') + + def test_can_save_POST_request_todo(self): + self.client.post( + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + todo_count = TodoList.objects.count() + new_todo = TodoList.objects.first() + + self.assertEqual(todo_count, 1) + self.assertEqual(getattr(new_todo, 'todo_list'), DUMMY_TODO_ITEM) + + def test_can_save_POST_request_todo_commentary(self): + self.client.post( + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + todo_commentary_count = TodoListCommentary.objects.count() + new_todo_commentary = TodoListCommentary.objects.first() + + self.assertEqual(todo_commentary_count, 1) + self.assertEqual(getattr(new_todo_commentary, 'comment'), DUMMY_TODO_COMMENTARY_ITEM) + + def test_redirects_after_POST_todo(self): + response = self.client.post( + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') + + def test_redirects_after_POST_todo_commentary(self): + response = self.client.post( + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') + ``` + + Selain itu, saya juga menambahkan implementasi *unit tests* dengan menggunakan `LiveServerTestCase` yaitu sebuah *library* bawaan dari Django, sehingga saya tidak perlu menjalankan perintah `runserver` lagi. + +- Pengimplementasian *functional test* yang sudah disesuaikan dengan *unit test* yang diimplementasikan berdasarkan bab 7, sehingga ketika dijalankan *automated script* untuk *unit test* maupun *functional test*, berhasil. + +Selanjutnya, untuk pengimplementasian tutorial 4 ini, saya menambahkan **aesthetics test**, yaitu *functional testing* pada CSS dengan menggunakan modul `StaticLiveServerTestCase` yang merupakan bawaan dari Django. Berikut adalah salah satu contoh implementasinya. + +```python +def test_layout_and_styling_todo_input_textbox(self): + selenium = self.selenium + selenium.get(self.host) + selenium.set_window_size(1024, 768) + + todo_date = selenium.find_element_by_id('todo_date') + self.assertAlmostEqual( + todo_date.location['x'] + todo_date.size['width'] / 2, + 740, + delta=10 + ) + +def test_layout_and_styling_todo_commentary_input_textbox(self): + selenium = self.selenium + selenium.get(self.host) + selenium.set_window_size(1024, 768) + + todo_commentary = selenium.find_element_by_id('comment_date') + self.assertAlmostEqual( + todo_commentary.location['x'] + todo_commentary.size['width'] / 2, + 780, + delta=10 + ) +``` + +Untuk *templating languange* pada HTML, saya sudah mengimplementasikan sebelumya, dimulai dari *app* `tutorial-1` dan `tutorial-2` + +````html +{% load staticfiles %} + + + + + + + + + + + {% block title %} Tutorial 2 PMPL by {{ author }} - {{ npm }}{% endblock %} + + + + +
+ {% include "tutorial_2/../partials/header.html" %} +
+ +
+ {% block content %} + + {% endblock %} +
+ +
+ + + + + + + + +```` + +Dan berikut adalah konten dari `tutorial_2.html` saya. + +```html +{% extends "tutorial_2/../layout/base.html" %} + +{% block content %} + +

{{ author }}'s Todo List

+

+
+
+

Todo List Table

+
+ + + + + + {% for todos in todos_dict %} + + + + + {% endfor %} +
Date/Time
Todo
{{ todos.date }}{{ todos.todo_list }}
+
+

Daily Todo List Comments

+
+ + + + + + {% for commentary in todos_commentary_dict %} + + + + + {% endfor %} +
Date
Comment
{{ commentary.date }}{{ commentary.comment }}
+
+
+
+

Add Todo

+
+ {% csrf_token %} + + + + + + + + + +
+ Date/Time + + +
+ Todo + + +
+
+ +
+
+
+

{{ error_msg }}

+
+

Add Daily Todo List Comment

+
+ {% csrf_token %} + + + + + + + + + +
+ Date + + +
+ Todo List Comment + + +
+
+ +
+
+
+

{{ commentary_error_msg }}

+
+
+
+{% endblock %} + +``` + diff --git a/tutorial/settings.py b/tutorial/settings.py index e921d9fee7657fa3d95621656fd955a7697c7ebf..c05ddb3b52d87cd1ed77236c1bf7132e34682522 100644 --- a/tutorial/settings.py +++ b/tutorial/settings.py @@ -48,6 +48,7 @@ INSTALLED_APPS = [ 'tutorial_1', 'tutorial_2', 'tutorial_3', + 'tutorial_4', ] MIDDLEWARE = [ diff --git a/tutorial_3/functional_tests.py b/tutorial_3/functional_tests.py index be179dc9e0dcd875f9eea3b8a31a8417c3d49636..48a3d925d89c427ada0daa0ab5bed4e67fcf6173 100644 --- a/tutorial_3/functional_tests.py +++ b/tutorial_3/functional_tests.py @@ -1,6 +1,7 @@ # Tutorial 2 PMPL Functional Test, using Django's LiveServerTestCase import time +import unittest from django.test import LiveServerTestCase from selenium import webdriver @@ -9,6 +10,7 @@ from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import WebDriverException +@unittest.skip("excluded") class Tutorial2FunctionalTest(LiveServerTestCase): def setUp(self): diff --git a/tutorial_4/__init__.py b/tutorial_4/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tutorial_4/apps.py b/tutorial_4/apps.py new file mode 100644 index 0000000000000000000000000000000000000000..e56432b383a5cf887135b1898cb64e1380036ed0 --- /dev/null +++ b/tutorial_4/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class Tutorial4Config(AppConfig): + name = "tutorial_4" diff --git a/tutorial_4/functional_tests.py b/tutorial_4/functional_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..51d7b01aad9e547dd50805b3392b0e2d1e90c79c --- /dev/null +++ b/tutorial_4/functional_tests.py @@ -0,0 +1,89 @@ +# Tutorial 4 Functional Test: Tutorial 2's module with StaticLiveServerTestCase module by Django + +import time + +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import WebDriverException + + +class Tutorial2FunctionalStaticfilesTest(StaticLiveServerTestCase): + + def setUp(self): + chrome_options = Options() + chrome_options.add_argument('--dns-prefetch-disable') + chrome_options.add_argument('--no-sandbox') + chrome_options.add_argument('--headless') + chrome_options.add_argument('--disable-gpu') + self.selenium = webdriver.Chrome('./chromedriver', chrome_options=chrome_options) + + self.MAX_WAIT = 10 + self.host = self.live_server_url + "/tutorial-2/" + self.selenium.implicitly_wait(3) + + super(Tutorial2FunctionalStaticfilesTest, self).setUp() + + def tearDown(self): + self.selenium.quit() + super(Tutorial2FunctionalStaticfilesTest, self).tearDown() + + def wait_for_row_list_in_table(self, row_text, table_id): + start_time = time.time() + selenium = self.selenium + while True: + try: + table = selenium.find_element_by_id(table_id) + rows = table.find_elements_by_tag_name("td") + self.assertIn(row_text, [row.text for row in rows]) + return + except (AssertionError, WebDriverException) as e: + if time.time() - start_time > self.MAX_WAIT: + raise e + time.sleep(0.5) + + def test_find_name(self): + selenium = self.selenium + selenium.get(self.host) + self.assertIn('Izzan Fakhril Islam', selenium.page_source) + + def test_input_todo_item(self): + selenium = self.selenium + selenium.get(self.host) + + todo_date = selenium.find_element_by_id('todo_date') + todo_date.send_keys('23052019') + todo_date.send_keys(Keys.TAB) + todo_date.send_keys('1245') + + todo_text = selenium.find_element_by_id('activity') + todo_text.send_keys('Dummy') + todo_text.send_keys(Keys.ENTER) + + time.sleep(1) + self.wait_for_row_list_in_table("Dummy", "todo_table") + + def test_layout_and_styling_todo_input_textbox(self): + selenium = self.selenium + selenium.get(self.host) + selenium.set_window_size(1024, 768) + + todo_date = selenium.find_element_by_id('todo_date') + self.assertAlmostEqual( + todo_date.location['x'] + todo_date.size['width'] / 2, + 740, + delta=10 + ) + + def test_layout_and_styling_todo_commentary_input_textbox(self): + selenium = self.selenium + selenium.get(self.host) + selenium.set_window_size(1024, 768) + + todo_commentary = selenium.find_element_by_id('comment_date') + self.assertAlmostEqual( + todo_commentary.location['x'] + todo_commentary.size['width'] / 2, + 780, + delta=10 + ) diff --git a/tutorial_4/unit_tests.py b/tutorial_4/unit_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..ac721ea8935f265c6506a2685a3cbbc4d8415e0e --- /dev/null +++ b/tutorial_4/unit_tests.py @@ -0,0 +1,122 @@ +from django.test import LiveServerTestCase, TestCase +from django.test import Client +from tutorial_2.views import index +from django.http import HttpRequest +from tutorial_2.models import TodoListCommentary, TodoList + +DUMMY_TODO_ITEM = "Dummy todo item" +DUMMY_TODO_COMMENTARY_ITEM = "Dummy todo commentary item" + + +class Tutorial2UnitTest(TestCase): + + def test_use_correct_template(self): + response = self.client.get('/tutorial-2/') + self.assertTemplateUsed(response, 'tutorial_2.html') + + def test_can_save_POST_request_todo(self): + self.client.post( + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + todo_count = TodoList.objects.count() + new_todo = TodoList.objects.first() + + self.assertEqual(todo_count, 1) + self.assertEqual(getattr(new_todo, 'todo_list'), DUMMY_TODO_ITEM) + + def test_can_save_POST_request_todo_commentary(self): + self.client.post( + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + todo_commentary_count = TodoListCommentary.objects.count() + new_todo_commentary = TodoListCommentary.objects.first() + + self.assertEqual(todo_commentary_count, 1) + self.assertEqual(getattr(new_todo_commentary, 'comment'), DUMMY_TODO_COMMENTARY_ITEM) + + def test_redirects_after_POST_todo(self): + response = self.client.post( + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') + + def test_redirects_after_POST_todo_commentary(self): + response = self.client.post( + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') + + +class Tutorial2LiveServerUnitTest(LiveServerTestCase): + def test_use_correct_template(self): + response = self.client.get(self.live_server_url + '/tutorial-2/') + self.assertTemplateUsed(response, 'tutorial_2.html') + + def test_can_save_POST_request_todo(self): + self.client.post( + self.live_server_url + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + todo_count = TodoList.objects.count() + new_todo = TodoList.objects.first() + + self.assertEqual(todo_count, 1) + self.assertEqual(getattr(new_todo, 'todo_list'), DUMMY_TODO_ITEM) + + def test_can_save_POST_request_todo_commentary(self): + self.client.post( + self.live_server_url + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + todo_commentary_count = TodoListCommentary.objects.count() + new_todo_commentary = TodoListCommentary.objects.first() + + self.assertEqual(todo_commentary_count, 1) + self.assertEqual(getattr(new_todo_commentary, 'comment'), DUMMY_TODO_COMMENTARY_ITEM) + + def test_redirects_after_POST_todo(self): + response = self.client.post( + self.live_server_url + '/tutorial-2/add_todo/', + data={ + 'date': '2019-09-05T14:31', + 'activity': DUMMY_TODO_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') + + def test_redirects_after_POST_todo_commentary(self): + response = self.client.post( + self.live_server_url + '/tutorial-2/add_todo_commentary/', + data={ + 'date': '2019-09-12', + 'comment': DUMMY_TODO_COMMENTARY_ITEM + } + ) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/tutorial-2/') +