diff --git a/README.md b/README.md index edbc52bd056ca232a38051a80307c42bdec7db21..ff76065dd5639ebd02fb889a8f8475683510881e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Repository Tutorial PMPL +# Repository Tutorial PMPL #### **Izzan Fakhril Islam (1606875806)** @@ -12,6 +12,7 @@ 6. Tutorial 6: Mutation Testing 7. Tutorial 7: Spiking & De-Spiking 8. Tutorial 8: Using Mocks +9. Tutorial 9: Test Fixtures and a Decorator **URL Heroku:** https://pmpl-izzan.herokuapp.com/ @@ -723,3 +724,33 @@ def test_send_mail_to_address_from_post(self, mock_send_mail): **Penjelasan:** pada potongan kode diatas, terdapat *decorator* yang diletakkan diatas *test case method*, yaitu `@patch('tutorial_7.views.send_mail')`, dimana *decorator* tersebut menandakan bahwa *test case method* dibawahnya akan melakukan *mocking* terhadap *method* `send_mail` yang terdapat di file `views.py` milik modul `tutorial_7`. Selanjutnya, *method* yang sudah dilakukan *mocking* tersebut disimpan dalam sebuah parameter yang bernama `mock_send_mail`. Dan dilakukan pengecekan apakah *method* tersebut terpanggil melalui POST request, dan pencocokan argumen-argumen yang diterima oleh *method* yang sudah dilakukan *mocking* tersebut. +## Penjelasan Tutorial 9 + +Berdasarkan buku **Test-Driven Development with Python 2nd Edition, bab 20.1,** terdapat beberapa penjelasan mengenai *user authentication*, dimana dalam hal ini membuat *functional test* yang berfungsi untuk melakukan *testing* terhadap alur *login* terhadap *user* yang sudah terdaftar. + +Sebenarnya, hal ini sudah pernah dilakukan dalam pengerjaan **Tutorial 7**, atau lebih tepatnya dalam **buku acuan bab 18.3**. Namun, terdapat beberapa perbedaan dalam implementasi tutorial ini dengan tutorial sebelumnya, yaitu: + +- Pada Tutorial 7, dilakukan *functional testing* untuk alur *login* dengan memasukkan email secara manual kedalam *form* yang sudah dibuat di file HTML-nya, dan mengecek inbox untuk memastikan apakah telah dikirim email yang berisikan `uid` yang sudah dibuat. Alur berjalannya *functional testing* pada Tutorial 7 secara garis besar terbagi menjadi 3 bagian, yaitu **Pengisian form email, Pengecekan mailbox,** dan **Pengecekan link `uid` yang diberikan**. + +- Pada Tutorial 9, dilakukan *functional testing* untuk alur *login* dengan membuat sebuah objek ***session*** baru yang berisikan informasi-informasi yang digunakan untuk *login*, yaitu email pengguna. Pembuatan objek *session* ini berlangsung secara *internal* di dalam modul `tutorial_7`, tanpa melibatkan interaksi dengan modul luar seperti *mailbox*. Berikut adalah potongan kode yang menjalankan fungsi tersebut. + + ```python + def create_pre_authenticated_session(self, email): + user = User.objects.create(email=email) + session = SessionStore() + session[SESSION_KEY] = user.pk + session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0] + session.save() + + self.selenium.get(self.live_server_url + '/tutorial-7/') + self.selenium.add_cookie(dict( + name=settings.SESSION_COOKIE_NAME, + value=session.session_key, + path='/tutorial-7/', + )) + ``` + + **Penjelasan:** pada potongan kode diatas, dibentuk sebuah *instance* dari model `User` dengan *primary key* email yang menjadi parameter dari *method* tersebut. Selanjutnya, digunakan sebuah *library* bawaan Django yaitu `django.contrib.auth` yang menyediakan penyimpanan informasi *session*, dan dilakukan *assignment* untuk variabel `SESSION_KEY` dengan menggunakan *primary key* dari *instance* model `User` yang telah dibuat (email), dan selanjutnya *session* yang telah dibuat di-*save*, dan menjadi penanda adanya sebuah user yang sedang aktif (*logged in*). + +Penggunaan *method* `create_pre_authenticated_session` ini memiliki beberapa kelebihan dibandingkan dengan implementasi pada Tutorial 7. Diantaranya, setiap *test case* tidak perlu melewati tahapan-tahapan mengirimkan email yang berisikan UID, yang mana memakan waktu dan tidak perlu melakukan interaksi dengan modul luar, cukup hanya di dalam modul `tutorial_7`. + diff --git a/tutorial_7/functional_tests/base.py b/tutorial_7/functional_tests/base.py index f507b3c93eef540fc3387113e52bdbea9fe75c40..52b7edbaa12030e95c4bb25d89206dd62b0d72bd 100644 --- a/tutorial_7/functional_tests/base.py +++ b/tutorial_7/functional_tests/base.py @@ -27,6 +27,20 @@ class FunctionalTest(StaticLiveServerTestCase): self.selenium.quit() super(FunctionalTest, self).tearDown() + def wait(fn): + def modified_fn(*args, **kwargs): + start_time = time.time() + MAX_WAIT = 10 + while True: + try: + return fn(*args, **kwargs) + except (AssertionError, WebDriverException) as e: + if time.time() - start_time > MAX_WAIT: + raise e + time.sleep(0.5) + return modified_fn + + @wait def wait_for_row_list_in_table(self, row_text, table_id): start_time = time.time() selenium = self.selenium @@ -42,17 +56,28 @@ class FunctionalTest(StaticLiveServerTestCase): time.sleep(0.5) # Same as wait_for_row_list_in_table but using lambda expressions + @wait def wait_for(self, fn): - start_time = time.time() - while True: - try: - return fn() - except (AssertionError, WebDriverException) as e: - if time.time() - start_time > self.MAX_WAIT: - raise e - time.sleep(0.5) + return fn() def get_host_from_selenium(self, url): selenium = self.selenium selenium.get(url) return selenium + + @wait + def wait_to_be_logged_in(self, email): + self.wait_for( + lambda: self.selenium.find_element_by_link_text('Log out') + ) + body = self.selenium.find_element_by_tag_name('body') + self.assertIn(email, body.text) + + @wait + def wait_to_be_logged_out(self, email): + self.wait_for( + lambda: self.selenium.find_element_by_name('email') + ) + body = self.selenium.find_element_by_tag_name('body') + self.assertNotIn(email, body.text) + diff --git a/tutorial_7/functional_tests/test_login.py b/tutorial_7/functional_tests/test_login.py index 8628682d07bc994195fd42ffdcb65f428fe17a68..f5c7b83bc49eb776c28c9ea789c3448a3503c66f 100644 --- a/tutorial_7/functional_tests/test_login.py +++ b/tutorial_7/functional_tests/test_login.py @@ -43,3 +43,7 @@ class LoginTest(FunctionalTest): lambda: logged_in_account.find_element_by_link_text('Log out') ) self.assertIn(TEST_EMAIL, selenium_host.find_element_by_tag_name('body').text) + + self.wait_to_be_logged_in(email=TEST_EMAIL) + self.selenium.find_element_by_link_text('Log out').click() + self.wait_to_be_logged_out(email=TEST_EMAIL) diff --git a/tutorial_7/functional_tests/test_todo.py b/tutorial_7/functional_tests/test_todo.py new file mode 100644 index 0000000000000000000000000000000000000000..7c02d4f98d15938749d525588a5d560f8d5ba976 --- /dev/null +++ b/tutorial_7/functional_tests/test_todo.py @@ -0,0 +1,31 @@ +from django.conf import settings +from django.contrib.auth import BACKEND_SESSION_KEY, SESSION_KEY, get_user_model +from django.contrib.sessions.backends.db import SessionStore +from .base import FunctionalTest +User = get_user_model() + + +class TodoListsTest(FunctionalTest): + + def create_pre_authenticated_session(self, email): + user = User.objects.create(email=email) + session = SessionStore() + session[SESSION_KEY] = user.pk + session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0] + session.save() + + self.selenium.get(self.live_server_url + '/tutorial-7/') + self.selenium.add_cookie(dict( + name=settings.SESSION_COOKIE_NAME, + value=session.session_key, + path='/tutorial-7/', + )) + + def test_logged_in_users_lists_are_saved_as_todo_lists(self): + email = 'rvd.cena@gmail.com' + self.selenium.get(self.live_server_url + '/tutorial-7/') + self.wait_to_be_logged_out(email) + + self.create_pre_authenticated_session(email) + self.selenium.get(self.live_server_url + '/tutorial-7/') + self.wait_to_be_logged_in(email) diff --git a/tutorial_7/urls.py b/tutorial_7/urls.py index 18fb1f142a9f3456b6bc603e6faec06cd0a07826..9b561b45687b5b24793120c7e74447ab5482ad82 100644 --- a/tutorial_7/urls.py +++ b/tutorial_7/urls.py @@ -8,4 +8,5 @@ urlpatterns = [ url(r'^send_email$', send_login_email, name='send_login_email'), url(r'^login$', login, name='login'), url(r'^logout$', logout, name='logout') + ]