From 4989ac27079adf1cc2d18ac549f6964855e8c75c Mon Sep 17 00:00:00 2001 From: Izzan Fakhril Islam Date: Thu, 10 Oct 2019 20:08:26 +0700 Subject: [PATCH 1/4] init tutorial 4 pmpl --- tutorial/settings.py | 1 + tutorial_4/__init__.py | 0 tutorial_4/apps.py | 5 ++ tutorial_4/functional_tests.py | 42 ++++++++++++ tutorial_4/unit_tests.py | 122 +++++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 tutorial_4/__init__.py create mode 100644 tutorial_4/apps.py create mode 100644 tutorial_4/functional_tests.py create mode 100644 tutorial_4/unit_tests.py diff --git a/tutorial/settings.py b/tutorial/settings.py index e921d9f..c05ddb3 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_4/__init__.py b/tutorial_4/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tutorial_4/apps.py b/tutorial_4/apps.py new file mode 100644 index 0000000..e56432b --- /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 0000000..a6432d1 --- /dev/null +++ b/tutorial_4/functional_tests.py @@ -0,0 +1,42 @@ +import time + +from django.test import LiveServerTestCase +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 Tutorial2FunctionalTest(LiveServerTestCase): + + 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/" + super(Tutorial2FunctionalTest, self).setUp() + + def tearDown(self): + self.selenium.quit() + super(Tutorial2FunctionalTest, 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) + + diff --git a/tutorial_4/unit_tests.py b/tutorial_4/unit_tests.py new file mode 100644 index 0000000..ac721ea --- /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/') + -- GitLab From f454c17492326bdd38ffe604a4adb80f176893a6 Mon Sep 17 00:00:00 2001 From: Izzan Fakhril Islam Date: Thu, 10 Oct 2019 20:18:04 +0700 Subject: [PATCH 2/4] skipping tutorial 3 functional test --- tutorial_3/functional_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tutorial_3/functional_tests.py b/tutorial_3/functional_tests.py index be179dc..48a3d92 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): -- GitLab From 48bd03fd776991ce9249a2446fa1f82172866909 Mon Sep 17 00:00:00 2001 From: Izzan Fakhril Islam Date: Thu, 10 Oct 2019 23:26:02 +0700 Subject: [PATCH 3/4] add staticfile test case for tutorial 2 module --- README.md | 70 +++++++++++++++++++++++++++++++++- tutorial_4/functional_tests.py | 55 ++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 45e9d35..86a72c4 100644 --- a/README.md +++ b/README.md @@ -122,4 +122,72 @@ 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. \ No newline at end of file diff --git a/tutorial_4/functional_tests.py b/tutorial_4/functional_tests.py index a6432d1..51d7b01 100644 --- a/tutorial_4/functional_tests.py +++ b/tutorial_4/functional_tests.py @@ -1,13 +1,15 @@ +# Tutorial 4 Functional Test: Tutorial 2's module with StaticLiveServerTestCase module by Django + import time -from django.test import LiveServerTestCase +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 Tutorial2FunctionalTest(LiveServerTestCase): +class Tutorial2FunctionalStaticfilesTest(StaticLiveServerTestCase): def setUp(self): chrome_options = Options() @@ -19,11 +21,13 @@ class Tutorial2FunctionalTest(LiveServerTestCase): self.MAX_WAIT = 10 self.host = self.live_server_url + "/tutorial-2/" - super(Tutorial2FunctionalTest, self).setUp() + self.selenium.implicitly_wait(3) + + super(Tutorial2FunctionalStaticfilesTest, self).setUp() def tearDown(self): self.selenium.quit() - super(Tutorial2FunctionalTest, self).tearDown() + super(Tutorial2FunctionalStaticfilesTest, self).tearDown() def wait_for_row_list_in_table(self, row_text, table_id): start_time = time.time() @@ -39,4 +43,47 @@ class Tutorial2FunctionalTest(LiveServerTestCase): 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 + ) -- GitLab From 31b702a8a0c6e2fe50feb269158346ef048df9c6 Mon Sep 17 00:00:00 2001 From: Izzan Fakhril Islam Date: Thu, 10 Oct 2019 23:34:30 +0700 Subject: [PATCH 4/4] update README.md --- README.md | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 199 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 86a72c4..f7ee06b 100644 --- a/README.md +++ b/README.md @@ -190,4 +190,202 @@ Berdasarkan buku **Test-Driven Development with Python 2nd Edition,** tutorial i 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. \ No newline at end of file +- 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 %} +
+ +
+
+ + {% block footer %} + {% include "tutorial_2/../partials/footer.html" %} + {% 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 %} + +``` + -- GitLab