diff --git a/README.md b/README.md index 276ac60b9725aa839e60f25a0d0e4550d72b6b4c..942139a6940b2b21bcb5fec628565254347b4a0d 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,16 @@ Pada tutorial **chapter 7**, saya membuat 2 file html yang berbeda, yaitu `index Pada tutorial **chapter 8**, saya belajar tentang Django Template Inheritance dengan menambahkan `base.html` yang nantinya akan di-inherits oleh file html lainnya. Selain itu saya juga belajar untuk menggunakan static files seperti bootstrap. Saya juga mencoba membuat file css tersendiri untuk sedikit memodifikasi tampilan. -Keterhubungan perubahan code pada chapter 8 ini dengan chapter sebelumnya (chapter 7) adalah adanya penggunaan `base.html` untuk memudahkan tampilan pada `index.html` dan `list.html` yang memiliki kemiripan. Kedua html file tersebut sama-sama memiliki input text, namun ada beberapa perbedaan seperti header text dan juga action yang ada pada form. Dengan menggunakan `base.html` seperti yang dijelaskan pada chapter 8, kita tidak perlu menuliskan code untuk tampilan yang sama sebanyak dua kali (**Don't repeat yourself!**), namun cukup menuliskannya pada `base.html` sedangkan tampilan yang berbeda akan dihandle oleh masing-masing html yang berkaitan. \ No newline at end of file +Keterhubungan perubahan code pada chapter 8 ini dengan chapter sebelumnya (chapter 7) adalah adanya penggunaan `base.html` untuk memudahkan tampilan pada `index.html` dan `list.html` yang memiliki kemiripan. Kedua html file tersebut sama-sama memiliki input text, namun ada beberapa perbedaan seperti header text dan juga action yang ada pada form. Dengan menggunakan `base.html` seperti yang dijelaskan pada chapter 8, kita tidak perlu menuliskan code untuk tampilan yang sama sebanyak dua kali (**Don't repeat yourself!**), namun cukup menuliskannya pada `base.html` sedangkan tampilan yang berbeda akan dihandle oleh masing-masing html yang berkaitan. + + +## Exercise 5 + +Pada penerapan refactoring (red, green, refactor), `red` berarti developer membuat failing test dari implementasi yang nantinya akan dibuat. Test ini dipastikan failed karena production code belum dituliskan. Test yang dibuat haruslah bersifat readability, artinya test tersebut mudah dipahami agar implementasinya pada production code bisa menjadi lebih mudah. + +Selanjutnya `green` berarti developer membuat production code yang akan membuat test yang telah dibuat sebelumnya menjadi passed. + +Sedangkan `refactor` berarti developer menambahkan atau memodifikasi production code yang sudah dibuat sebelumnya agar code tersebut lebih optimal serta dapat meningkatkan readability dan maintanability. Pada refactor ini biasanya developer akan membersihkan code smells yang ada pada production code, seperti code yang duplicate, method yang terlalu panjang, dan code smells lainnya. Refactor seharusnya tidak mengubah implementasi dari production code yang telah dibuat, namun hanya mengubah bagaimana implementasi tersebut dijalankan dengan lebih rapi. + + +Test Organizations yang saya lakukan pada exercise 5 ini memberikan kejelasan tentang test mana yang bertanggung jawab pada suatu aktivitas. Test yang dibuat akan menjadi lebih mudah dibaca karena adanya pemisahan pada setiap test yang melakukan aktivitas berbeda. \ No newline at end of file diff --git a/functional_tests/base.py b/functional_tests/base.py new file mode 100644 index 0000000000000000000000000000000000000000..0929e71ca0e66fc9c55e4bf812c7cb49e31066d1 --- /dev/null +++ b/functional_tests/base.py @@ -0,0 +1,44 @@ +from selenium import webdriver +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import WebDriverException +import time + +MAX_WAIT = 10 + +class FunctionalTest(StaticLiveServerTestCase): + + def setUp(self): + options = Options() + options.add_argument('--dns-prefetch-disable') + options.add_argument('--no-sandbox') + options.add_argument('--headless') + options.add_argument('disable-gpu') + self.browser = webdriver.Chrome(executable_path="./chromedriver", options=options) + + def tearDown(self): + self.browser.quit() + + def wait_for_row_in_list_table(self, row_text): + start_time = time.time() + while True: + try: + table = self.browser.find_element_by_id('id_list_table') + rows = table.find_elements_by_tag_name('tr') + self.assertIn(row_text, [row.text for row in rows]) + return + except (AssertionError, WebDriverException) as e: + if time.time() - start_time > MAX_WAIT: + raise e + time.sleep(0.5) + + def wait_for(self, fn): + start_time = time.time() + while True: + try: + return fn() + except (AssertionError, WebDriverException) as e: + if time.time() - start_time > MAX_WAIT: + raise e + time.sleep(0.5) diff --git a/functional_tests/test_layout_and_styling.py b/functional_tests/test_layout_and_styling.py new file mode 100644 index 0000000000000000000000000000000000000000..f64fd1b827b25f2b0737caf00fe12e45c8bd43c8 --- /dev/null +++ b/functional_tests/test_layout_and_styling.py @@ -0,0 +1,44 @@ +from selenium import webdriver +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium.webdriver.common.keys import Keys +from .base import FunctionalTest + +class LayoutAndStylingTest(FunctionalTest): + + def test_layout_and_styling(self): + # Edith goes to the home page + self.browser.get(self.live_server_url) + self.browser.set_window_size(1024, 768) + + # She notices the input box is nicely centered + inputbox = self.browser.find_element_by_id('id_new_item') + self.assertAlmostEqual( + inputbox.location['x'] + inputbox.size['width'] / 2, + 512, + delta=10 + ) + inputbox.send_keys('testing') + inputbox.send_keys(Keys.ENTER) + self.wait_for_row_in_list_table('1: testing') + inputbox = self.browser.find_element_by_id('id_new_item') + self.assertAlmostEqual( + inputbox.location['x'] + inputbox.size['width'] / 2, + 512, + delta=17 + ) + + def check_for_row_in_list_table_none(self): + h1_elem = self.browser.find_element_by_id('comment') + rows = h1_elem.find_elements_by_tag_name('h1') + self.assertIn("sibuk tapi santai", [row.text for row in rows]) + + def test_body_contains_name_in_h1(self): + self.browser.get(self.live_server_url) + h1_elem = self.browser.find_element_by_tag_name('h1') + self.assertIn('Aviliani Pramestya', h1_elem.text) + + def test_body_contains_npm_in_h1(self): + self.browser.get(self.live_server_url) + h1_elem = self.browser.find_element_by_tag_name('h2') + self.assertIn('1606829402', h1_elem.text) + diff --git a/functional_tests/test_list_item_validation.py b/functional_tests/test_list_item_validation.py new file mode 100644 index 0000000000000000000000000000000000000000..ccc0d60b4cb796642cec84408cab333fa552c3a1 --- /dev/null +++ b/functional_tests/test_list_item_validation.py @@ -0,0 +1,39 @@ +from selenium.webdriver.common.keys import Keys +from unittest import skip +from .base import FunctionalTest + +class ItemValidationTest(FunctionalTest): + + def test_cannot_add_empty_list_items(self): + # Edith goes to the home page and accidentally tries to submit + # an empty list item. She hits Enter on the empty input box + self.browser.get(self.live_server_url) + self.browser.find_element_by_id('id_new_item').send_keys(Keys.ENTER) + + + # The home page refreshes, and there is an error message saying + # that list items cannot be blank + self.wait_for(lambda: self.assertEqual( + self.browser.find_element_by_css_selector('.has-error').text, + "Please enter your to do list" + )) + + # She tries again with some text for the item, which now works + self.browser.find_element_by_id('id_new_item').send_keys('Buy milk') + self.browser.find_element_by_id('id_new_item').send_keys(Keys.ENTER) + self.wait_for_row_in_list_table('1: Buy milk') + + # Perversely, she now decides to submit a second blank list item + self.browser.find_element_by_id('id_new_item').send_keys(Keys.ENTER) + + # She receives a similar warning on the list page + self.wait_for(lambda: self.assertEqual( + self.browser.find_element_by_css_selector('.has-error').text, + "Please enter your to do list" + )) + + # And she can correct it by filling some text in + self.browser.find_element_by_id('id_new_item').send_keys('Make tea') + self.browser.find_element_by_id('id_new_item').send_keys(Keys.ENTER) + self.wait_for_row_in_list_table('1: Buy milk') + self.wait_for_row_in_list_table('2: Make tea') diff --git a/functional_tests/tests.py b/functional_tests/test_simple_list_creation.py similarity index 51% rename from functional_tests/tests.py rename to functional_tests/test_simple_list_creation.py index b7a73620f4aaf6b05689ed5c70887d3e18ac9d75..b7a16b27a3b7dca855b3b84a7f4a41e2bf1ca3ad 100644 --- a/functional_tests/tests.py +++ b/functional_tests/test_simple_list_creation.py @@ -1,39 +1,11 @@ from selenium import webdriver from django.contrib.staticfiles.testing import StaticLiveServerTestCase -from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.keys import Keys -from selenium.common.exceptions import WebDriverException -import time -import unittest +from .base import FunctionalTest -MAX_WAIT = 10 -class NewVisitorTest(StaticLiveServerTestCase): +class NewVisitorTest(FunctionalTest): - def setUp(self): - options = Options() - options.add_argument('--dns-prefetch-disable') - options.add_argument('--no-sandbox') - options.add_argument('--headless') - options.add_argument('disable-gpu') - self.browser = webdriver.Chrome(executable_path="./chromedriver", options=options) - - def tearDown(self): - self.browser.quit() - - def wait_for_row_in_list_table(self, row_text): - start_time = time.time() - while True: - try: - table = self.browser.find_element_by_id('id_list_table') - rows = table.find_elements_by_tag_name('tr') - self.assertIn(row_text, [row.text for row in rows]) - return - except (AssertionError, WebDriverException) as e: - if time.time() - start_time > MAX_WAIT: - raise e - time.sleep(0.5) - def test_can_start_a_list_for_one_user(self): self.browser.get(self.live_server_url) @@ -54,6 +26,7 @@ class NewVisitorTest(StaticLiveServerTestCase): self.wait_for_row_in_list_table('1: Buy peacock feathers') self.wait_for_row_in_list_table('2: Use peacock feathers to make a fly') + def test_multiple_users_can_start_lists_at_different_urls(self): # Edith starts a new to-do list self.browser.get(self.live_server_url) @@ -95,43 +68,3 @@ class NewVisitorTest(StaticLiveServerTestCase): page_text = self.browser.find_element_by_tag_name('body').text self.assertNotIn('Buy peacock feathers', page_text) self.assertIn('Buy milk', page_text) - - - def test_layout_and_styling(self): - # Edith goes to the home page - self.browser.get(self.live_server_url) - self.browser.set_window_size(1024, 768) - - # She notices the input box is nicely centered - inputbox = self.browser.find_element_by_id('id_new_item') - self.assertAlmostEqual( - inputbox.location['x'] + inputbox.size['width'] / 2, - 512, - delta=10 - ) - inputbox.send_keys('testing') - inputbox.send_keys(Keys.ENTER) - self.wait_for_row_in_list_table('1: testing') - inputbox = self.browser.find_element_by_id('id_new_item') - self.assertAlmostEqual( - inputbox.location['x'] + inputbox.size['width'] / 2, - 512, - delta=17 - ) - - - def check_for_row_in_list_table_none(self): - h1_elem = self.browser.find_element_by_id('comment') - rows = h1_elem.find_elements_by_tag_name('h1') - self.assertIn("sibuk tapi santai", [row.text for row in rows]) - - def test_body_contains_name_in_h1(self): - self.browser.get(self.live_server_url) - h1_elem = self.browser.find_element_by_tag_name('h1') - assert h1_elem.text == 'Aviliani Pramestya' - - def test_body_contains_npm_in_h1(self): - self.browser.get(self.live_server_url) - h1_elem = self.browser.find_element_by_tag_name('h2') - assert h1_elem.text == '1606829402' - diff --git a/lists/templates/base.html b/lists/templates/base.html index e59c24d596eaa0181135d253945c3ee939e410de..2bf1850a4edff889eaa1e73a113f027214d723bf 100644 --- a/lists/templates/base.html +++ b/lists/templates/base.html @@ -30,6 +30,11 @@
@@ -48,7 +53,7 @@ {% endblock %} - +