Fakultas Ilmu Komputer UI

Commit 52c2695f authored by Izzan Fakhril Islam's avatar Izzan Fakhril Islam
Browse files

Tutorial 3 PMPL

parent 5d45658e
...@@ -4,6 +4,122 @@ ...@@ -4,6 +4,122 @@
**Daftar Tutorial:** **Daftar Tutorial:**
1. Tutorial 1: Web Page Pribadi berbasis *Python* dengan *Unit test*. 1. Tutorial 1: Web Page Pribadi berbasis *Python* dengan *Unit test*.
2. Tutorial 2: Web Page *Todo List* dengan *Unit test* dan *Functional test*.
3. Tutorial 3: *Test Isolation* pada Django
**URL Heroku:** https://pmpl-izzan.herokuapp.com **URL Heroku:** https://pmpl-izzan.herokuapp.com/
## **Penjelasan Tutorial 3**
Berdasarkan buku **Test-Driven Development with Python 2nd Edition,** bab 6, pengerjaan tutorial 3 ini mencakup beberapa hal sebagai berikut:
- Pemisahan berkas-berkas **Unit Test** dan **Functional Test** (*test isolation*)
- Penerapan `LiveServerTestCase` milik Django pada *unit test* dan *functional test* yang berfungsi untuk melakukan *data fetching* pada sebuah *session* tersendiri yang dibentuk oleh Django
- Penerapan **explicit wait** pada penggunaan *functional test* Django
Untuk memisahkan berkas-berkas **Unit Test** dan **Functional Test,** saya telah melakukan pemisahan *file* sejak pengerjaan Tutorial 1, dengan membagi file `tests.py` menjadi `unit_tests.py` dan `functional_tests.py`. Selanjutnya, saya membuat dua buah **CI pipeline job** di *file* `.gitlab-ci.yml` saya, yaitu *job* `FunctionalTest` dan `UnitTest`. Pada *job* `FunctionalTest` berisikan *script* sebagai berikut:
```bash
$ python manage.py test -p "functional_test*.py"
```
Sedangkan pada *job* `UnitTest` berisikan *script* sebagai berikut:
```bash
$ python manage.py test -p "unit_test*.py"
```
Berikut adalah struktur *file* dari project tutorial PMPL (**1606875806-tutorial**) saya:
```
.
├── tutorial
| ├── __init__.py
| ├── settings.py
| ├── urls.py
| └── wsgi.py
├── tutorial_1
| ├── __init__.py
| ├── admin.py
| ├── apps.py
| ├── functional_tests.py
| ├── models.py
| ├── unit_tests.py
| ├── urls.py
| └── views.py
├── tutorial_2
| ├── __init__.py
| ├── admin.py
| ├── apps.py
| ├── functional_tests.py
| ├── models.py
| ├── unit_tests.py
| ├── urls.py
| └── views.py
├── tutorial_3
| ├── __init__.py
| ├── apps.py
| ├── functional_tests.py
| └── unit_tests.py
├── .gitignore
├── .gitlab-ci.yml
├── db.sqlite3
├── deployment.sh
├── manage.py
├── Procfile
└── requirements.txt
```
Selanjutnya, saya menggunakan `LiveServerTestCase`, sebuah *library* dari `django.test` untuk melakukan *functional test* dan *unit test* pada tutorial 3 ini. Salah satu perbedaannya adalah pada baris kode berikut:
```python
class Tutorial2UnitTest(LiveServerTestCase)
```
dimana pada *unit test* ataupun *functional test* kali ini, meng-*extend* class `LiveServerTestCase`, bukan `UnitTest`. Kemudian, saya juga melakukan *data fetching* ke URL yang telah disediakan oleh `LiveServerTestCase`, yaitu sebagai berikut:
```python
response = Client().get(self.live_server_url + "/tutorial-2/")
```
Pada potongan kode diatas, terlihat bahwa URL yang coba di-*fetch* bukan lagi `localhost:8000`, melainkan `self.live_server_url` yang sudah disediakan oleh Django.
Selanjutnya, saya menerapkan ***explicit wait*** pada kode *functional test* tutorial 2 yang saya modifikasi dengan menggunakan *library* `time` bawaan Python. Pada dasarnya, Selenium sebagai *library* untuk *functional test* memiliki fitur bawaan untuk menunggu apabila sebuah halaman telah selesai di-*load*. *Library* tersebut bernama `implicitly_wait`. Namun, secara performa dan akurasi, lebih baik menggunakan *explicit wait* dengan *library* `time` bawaan Python.
Berikut adalah contoh implementasi *explicit wait* pada *functional test* yang telah saya buat:
```Python
def setUp(self):
[...]
self.MAX_WAIT = 10
def test_input_todo(self):
start_time = time.time()
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')
selenium.find_element_by_id('todo_submit').click()
while True:
try:
table = selenium.find_element_by_id("todo_table")
rows = table.find_elements_by_tag_name("td")
self.assertIn('Dummy', [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)
```
- 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
...@@ -47,6 +47,7 @@ INSTALLED_APPS = [ ...@@ -47,6 +47,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'tutorial_1', 'tutorial_1',
'tutorial_2', 'tutorial_2',
'tutorial_3',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
......
...@@ -8,7 +8,7 @@ class Tutorial1FunctionalTest(TestCase): ...@@ -8,7 +8,7 @@ class Tutorial1FunctionalTest(TestCase):
def setUp(self): def setUp(self):
chrome_options = Options() chrome_options = Options()
chrome_options.add_argument('--dns-prefatch-disable') chrome_options.add_argument('--dns-prefetch-disable')
chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--headless') chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--disable-gpu')
......
...@@ -12,7 +12,7 @@ class Tutorial2FunctionalTest(TestCase): ...@@ -12,7 +12,7 @@ class Tutorial2FunctionalTest(TestCase):
def setUp(self): def setUp(self):
chrome_options = Options() chrome_options = Options()
chrome_options.add_argument('--dns-prefatch-disable') chrome_options.add_argument('--dns-prefetch-disable')
chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--headless') chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--disable-gpu')
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<div class="col-md-6 col-xs-6"> <div class="col-md-6 col-xs-6">
<h4>Todo List Table</h4> <h4>Todo List Table</h4>
<br> <br>
<table> <table id="todo_table">
<tr> <tr>
<th><h5><b>Date/Time</b></h5></th> <th><h5><b>Date/Time</b></h5></th>
<th><h5><b>Todo</b></h5></th> <th><h5><b>Todo</b></h5></th>
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
<br> <br>
<h4>Daily Todo List Comments</h4> <h4>Daily Todo List Comments</h4>
<br> <br>
<table> <table id="todo_comment_table">
<tr> <tr>
<th><h5><b>Date</b></h5></th> <th><h5><b>Date</b></h5></th>
<th><h5><b>Comment</b></h5></th> <th><h5><b>Comment</b></h5></th>
......
from django.apps import AppConfig
class Tutorial3Config(AppConfig):
name = 'tutorial_3'
# Tutorial 2 PMPL Functional Test, using Django's LiveServerTestCase
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.cases = [
(5, "oh, tidak"),
(2, "sibuk tapi santai"),
(0, "waktunya berlibur"),
]
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 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):
start_time = time.time()
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')
selenium.find_element_by_id('todo_submit').click()
while True:
try:
table = selenium.find_element_by_id("todo_table")
rows = table.find_elements_by_tag_name("td")
self.assertIn('Dummy', [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_input_todo_commentary_item(self):
start_time = time.time()
selenium = self.selenium
selenium.get(self.host)
todo_date = selenium.find_element_by_id('comment_date')
todo_date.send_keys('23122019')
todo_comment = selenium.find_element_by_id('comment')
todo_comment.send_keys('Dummy comment')
selenium.find_element_by_id('comment_submit').click()
while True:
try:
table = selenium.find_element_by_id("todo_comment_table")
rows = table.find_elements_by_tag_name("td")
self.assertIn("Dummy comment", [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_input_todo_on_the_same_date_and_generate_comment(self):
start_time = time.time()
selenium = self.selenium
selenium.get(self.host)
date = 24
month_and_year = '112019'
for n, comment in self.cases:
time_str = 1045
for i in range(n):
todo_date = selenium.find_element_by_id('todo_date')
todo_date.send_keys(str(date) + month_and_year)
todo_date.send_keys(Keys.TAB)
todo_date.send_keys(str(time_str))
todo_text = selenium.find_element_by_id('activity')
todo_text.send_keys('Dummy Activity ' + str(n))
selenium.find_element_by_id('todo_submit').click()
time_str += 1
todo_date = selenium.find_element_by_id('comment_date')
todo_date.send_keys(str(date) + month_and_year)
todo_comment = selenium.find_element_by_id('comment')
todo_comment.send_keys(comment)
selenium.find_element_by_id('comment_submit').click()
while True:
try:
table_comment = selenium.find_element_by_id("todo_comment_table")
rows_comment = table_comment.find_elements_by_tag_name("td")
table_todo = selenium.find_element_by_id("todo_table")
rows_todo = table_todo.find_elements_by_tag_name("td")
self.assertIn('Dummy Activity ' + str(n), [row.text for row in rows_todo])
self.assertIn(comment, [row.text for row in rows_comment])
date += 1
return
except (AssertionError, WebDriverException) as e:
if time.time() - start_time > self.MAX_WAIT:
raise e
time.sleep(0.5)
# Tutorial 2 PMPL Unit Test, using Django's LiveServerTestCase
from django.test import LiveServerTestCase
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(LiveServerTestCase):
def test_page_rendered_successful(self):
print(self.live_server_url + "/tutorial-2/")
response = Client().get(self.live_server_url + "/tutorial-2/")
self.assertEqual(response.status_code, 200)
def test_name_in_page(self):
request = HttpRequest()
response = index(request)
html_response = response.content.decode('utf8')
self.assertIn('Izzan Fakhril Islam', html_response)
def test_POST_request_create_todo(self):
self.client.post(
self.live_server_url + "/tutorial-2/add_todo/",
data={
'date': '2019-09-19T15:15',
'activity': DUMMY_TODO_ITEM,
}
)
todo_count = TodoList.objects.all().count()
page_response = self.client.get(self.live_server_url + '/tutorial-2/')
html_response = page_response.content.decode('utf8')
self.assertEqual(todo_count, 1)
self.assertIn(DUMMY_TODO_ITEM, html_response)
def test_POST_request_create_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_comment_count = TodoListCommentary.objects.all().count()
page_response = self.client.get(self.live_server_url + '/tutorial-2/')
html_response = page_response.content.decode('utf8')
self.assertEqual(todo_comment_count, 1)
self.assertIn(DUMMY_TODO_COMMENTARY_ITEM, html_response)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment