1606821886-practice
Herokuapp : https://pmpl-farah.herokuapp.com/
Table of Contents
- Exercise 3 :
- Proses test isolation
- Perbedaan pada design
- Exercise 4 :
- Keterhubungan Chapter 8 dan 7
- Exercise 5 :
- Keterkaitan refactoring dan clean code
- Keuntungan test organization
- Exercise 6 :
- Pembuatan Mutant
- Mutation Testing Tool : Django-mutpy
- Exercise 7 :
- Spiking & De-Spiking
- Exercise 8 :
- Mocks Can Leave You Tightly Coupled
- Exercise 9 :
- Perbedaan implementasi functional test fitur login
Exercise 3
Proses test isolation
Setiap menjalankan functional test, proses pengujian sering kali tertinggal di database, dan itu akan menggangu jalannya tes berikutnya. Dengan mengaplikasikan test isolation, Django test runner secara otomatis membuat database pengujian baru (terpisah dari yang asli), yang dapat dengan aman diset ulang sebelum setiap tes individu dijalankan.
Tes yang berbeda tidak boleh saling memengaruhi. Django test runner membantu dengan membuat basis data test, yang dibersihkan di antara setiap dijalankannya test.
Perbedaan design baru dengan design sebelumnya
Potongan code di bawah menunjukkan perubahan yang terjadi pada functional_tests. Perubahan terjadi karena adanya implementasi test isolation menggunakan LiveServerTestCase. LiveServerTestCase akan secara otomatis membuat database uji (seperti dalam unit test run), dan memulai server pengembangan untuk menjalankan tes fungsional. Test runner akan menemukan file apa pun yang namanya dimulai dengan test.
+from selenium.common.exceptions import WebDriverException
+from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.firefox.options import Options
import time
import unittest
import environ
-env = environ.Env(DEBUG=(bool, False))
-environ.Env.read_env('.env')
+MAX_WAIT = 10
-class NewVisitorTest(unittest.TestCase):
+class NewVisitorTest(LiveServerTestCase):
[...]
Potongan code dibawah menunjukkan perubahan yang terjadi untuk mengubah implementasi url menjadi isolated url.
def test_can_start_a_list_and_retrieve_it_later(self):
# Edith has heard about a cool new online to-do app. She goes
# to check out its homepage
- url = env("HEROKU_APP_HOST", default='http://localhost:8000')
+ url = self.live_server_url
self.browser.get(url)
[...]
Exercise 4
Keterhubungan Chapter 8 dan 7
Pada bab 7, saya melakukan perubahan pada struktur kode yang memungkinkan ada lebih dari satu user yang bisa memulai sebuah To-Do List. Sehingga saya mengimplementasikan URL yang unik untuk setiap list, seperti petunjuk di buku.
Pada bab 8, saya memperbaiki tampilan halaman dengan menggunakan css dan Bootstrap. Dari html yang sudah dibuat pada bab 7, saya mengimplementasikan django template inheritance dengan membuat satu html sebagai "superclass" yaitu base.html.
Exercise 5
Keterkaitan refactoring dan konsep clean code
Tujuan utama dilakukannya refactoring adalah untuk mencapai atau memelihara clean code. Dengan Red Green Refactor, kita dapat :
- Menghindari duplication
- Meningkatkan code coverage
- Memudahkan maintenance
Dengan menerapkan Red Green Refactor, berarti kita juga menerapkan konsep clean code.
Keuntungan test organization
Memudahkan maintenance. Kita dapat dengan mudah menemukan test code yang kita butuhkan karena test sudah teratur dalam kategori-kategori. Selain itu kita dapat memilih test yang ingin dijalankan. Bisa menjalankan semua test, bisa juga menjalankan test spesifik saja.
Exercise 6
Pembuatan Mutant
Pada fungsi views.py berikut adalah code implementasi fitur komentar :
def view_list(request, list_id):
list_ = List.objects.get(id=list_id)
items = Item.objects.filter(list=list_)
if len(items) == 0:
comment = 'Yey, waktunya berlibur'
elif len(items) < 5:
comment = 'Sibuk tapi santai'
elif len(items) >= 5:
comment = 'Oh tidak'
return render(request, 'list.html', {'list': list_, 'comment':comment})
Setelah mengubah code tersebut beberapa kali, dapat dibuat satu mutant pada line 9 dengan mengubah menjadi :
elif len(items) > 5:
Dengan mengubah decision pada line tersebut, test tidak error karena pada test sebelumnya tidak menghandle kondisi dimana len(items) = 5
. Maka dari itu butuh dibuat test baru yang dapat menghandle kondisi tersebut. Jadi pada test_views.py ditambahkan function test untuk len(items) = 5
, setelah ditambahkan test tersebut, function tersebut menjadi error, maka mutant sudah berhasil di kill.
Mutation Testing Tool : Django-mutpy
Berikut adalah hasil dari mutation testing menggunakan django-mutpy. Tool ini dijalankan pada test_models dan test_views.
[0.13787 s] killed by test_passes_correct_list_to_template (lists.tests.test_views.ListViewTest)
[*] Mutation score [9.18592 s]: 100.0%
- all: 38
- killed: 38 (100.0%)
- survived: 0 (0.0%)
- incompetent: 0 (0.0%)
- timeout: 0 (0.0%)
Destroying test database for alias 'default'...
Dari hasil tersebut dapat kita lihat bahwa semua mutant sudah berhasil di kill oleh test yang ada.
Exercise 7
Spiking
Spiking adalah sebuah tahapan coding dimana kita mengeksplor suatu API baru atau solusi-solusi baru. Pada exercise, spiking dilakukan pada branch passwordless-spike dimana kita mengeksplor mengenai pengimplementasian paswordless auth.
De-Spiking
De-spiking adalah tahapan dimana kita mengimplementasikan hasil di spiking dan menggunakannya di production codebase (master). Karena di spiking dapat dilakukan tanpa test, pada tahap De-spiking kita akan menggunakan TDD dalam implementasinya.
Exercise 8
Kenapa mocking dapat membuat implementasi yang kita buat tightly coupled?
Penggunaan Mocking seringkali terikat pada bagaimana kita mengimplementasikan sesuatu. Ketika kita melakukan implementasi yang berbeda, walaupun hasilnya sama, test bisa saja tidak terpenuhi. Hal ini yang dimaksud dari "Mocks Can Leave You Tightly Coupled to the Implementation". Sedangkan biasanya, mengetest behavior lebih baik daripada mengetest detil implementasi.
Contohnya pada kasus untuk test (tanpa mock) berikut accounts/tests/test_views.py
def test_adds_success_message(self):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
}, follow=True)
message = list(response.context['messages'])[0]
self.assertEqual(
message.message,
"Check your email, we've sent you a link you can use to log in."
)
self.assertEqual(message.tags, "success")
Kita dapat mengubah test tersebut agar menggunakan mock, sehingga menjadi seperti ini
@patch('accounts.views.messages')
def test_adds_success_message_with_mocks(self, mock_messages):
response = self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
expected = "Check your email, we've sent you a link you can use to log in."
self.assertEqual(
mock_messages.success.call_args,
call(response.wsgi_request, expected),
)
Dengan implementasi di accounts/views.py seperti berikut ini
messages.add_message(
request,
messages.SUCCESS,
"Check your email, we've sent you a link you can use to log in."
)
Dengan implementasi tersebut, test tanpa mock terpenuhi sedangkan test dengan mock gagal terpenuhi. Dalam kata lain, dengan mock kita harus memiliki detil implementasi yang sesuai pada views, walaupun sebenarnya hasilnya sama sama benar.
Exercise 8
Perbedaan implementasi functional test fitur login
Pada bab 20.1 kita ingin menggunakan functional authentication system agar dapat mengidentifikasikan user dan memperlihatkan list yang sudah mereka buat. Maka dari itu kita membuat FT untuk logged-in user sehingga daripada melakukan seluruh prosedur dari tahap login yang memakan waktu, kita dapat melewati bagian login email (karena sudah logged in).
Pada implementasinya, kita menambahkan fungsi create_pre_authenticated_session
dibawah untuk melakukan login. Dari situ kita akan menggunakan sessionnya untuk FT lainnya sehingga tidak melakukan login berulang kali.
class MyListsTest(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()
## to set a cookie we need to first visit the domain.
## 404 pages load the quickest!
self.browser.get(self.live_server_url + "/404_no_such_url/")
self.browser.add_cookie(dict(
name=settings.SESSION_COOKIE_NAME,
value=session.session_key,
path='/',
))
def test_logged_in_users_lists_are_saved_as_my_lists(self):
email = 'edith@example.com'
self.browser.get(self.live_server_url)
self.wait_to_be_logged_out(email)
# Edith is a logged-in user
self.create_pre_authenticated_session(email)
self.browser.get(self.live_server_url)
self.wait_to_be_logged_in(email)
Pada hasil deployment untuk implementasi baru 21.1-21.2, apakah terjadi error?
Ya, ketikan dijalankan heroku run python3 manage.py test functional_tests -a pmpl-farah
terjadi error dikarenakan sistem authentication yang baru menyebabkan tidak bisa login pada session apapun.
Untuk bagian 21.2, pada logs di heroku bisa dilihat bahwa ada error SMTPSenderRefused dikarenakan dibutuhkannya password dari email ketika akan mengirimkan email login. Berikut adalah error yang muncul pada logs heroku :
2019-11-26T09:15:03.956705+00:00 app[web.1]: self.connection.sendmail(from_email, recipients, message.as_bytes(linesep='\r\n'))
2019-11-26T09:15:03.956707+00:00 app[web.1]: File "/app/.heroku/python/lib/python3.6/smtplib.py", line 867, in sendmail
2019-11-26T09:15:03.956709+00:00 app[web.1]: raise SMTPSenderRefused(code, resp, from_addr)
2019-11-26T09:15:03.956712+00:00 app[web.1]: smtplib.SMTPSenderRefused: (530, b'5.5.1 Authentication Required. Learn more at\n5.5.1 https://support.google.com/mail/?p=WantAuthError m6sm4766481qke.80 - gsmtp', 'noreply@superlists')
2019-11-26T09:15:03.957480+00:00 app[web.1]: 10.69.117.24 - - [26/Nov/2019:09:15:03 +0000] "POST /accounts/send_login_email HTTP/1.1" 500 86865 "http://pmpl-farah.herokuapp.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"