Fakultas Ilmu Komputer UI

Skip to content
Snippets Groups Projects
user avatar
Luthfi Dzaky Saifuddin authored
08242998
History

1606889830-practice

Luthfi Dzaky Saifuddin - 1606889830 - Computer Science 2016 - PMPL B - Practice Repo

URL

http://pmpl-luthfi.herokuapp.com

Tutorial

Tutorial 1

Tutorial 2

forgot squash commit

Tutorial 3

Isolation Test

Pada tutorial 3 kali ini, dijelaskan mengenai isolation test, dimana seharusnya sebuah tes berjalan secara independen. Pada tutorial sebelumnya ketika kita menjalankan functional test. Kita harus menghapus db.sqlite3 terlebih dahulu karena masih tersimpan data pada functional test sebelumnya.

Isolation test salah satunya dapat menggunakan LiveServerTestCase. Salah satu tools dari Django yang memungkinkan pembuatan Django Server ketika setUp() dan mematikannya ketika tearDown(). Dengan tools tersebut, memungkinkan kita melakukan isolation test karena setiap test yang kita lakukan akan selalu membuat Django Server yang baru. Kita sendiri dapat mengatur port yang akan digunakan. Contohnya ketika kita ingin menggunakan port 8333.

./manage.py test --liveserver=localhost:8333

Additional Information

Selain mengenai isolation test diatas, ada dua perubahan mengenai desain kode lama dan desain kode pada latihan kali ini.

Pertama, functional test pada latihan kali ini sudah diubah menjadi sebuah app. Jadi untuk menjalankan seluruh test cukup menggunakan kode berikut:

python manage.py test

Foto 1

Kedua, pada latihan sebelumnya setiap kali kita menjalankan sebuah functional test, setiap input yang diberikan kita akan memberi waktu eksplisit dengan time.sleep(1). Namun, hal tersebut dapat berdampak pada dua hal, yaitu:

  • Waktu yang digunakan terlalu cepat, sehingga menimbulkan NoSuchElementException atau StateElementException
  • Waktu yang digunakan terlalu lama, hal tersebut membuat functional test berjalan lama.

Pada latihan kali ini menggunakan konsep try-catch dimana ketika tiap time.sleep(0.5) akan mengecek apakah elemen yang dicari ada atau tidak. Jika sudah mencapai threshold yang ditentukan (pada latihan ini 10 detik), maka akan langsung menimbulkan error. Foto 2

Tutorial 4

[Bab 7] Pada tutorial 4 kali ini, mempelajari bagaimana seharusnya sebuah program dibuat secara bertahap. TDD membantu kita untuk bekerja secara bertahap dengan cara memastikan setiap fitur yang kita buat sebelumnya tidak menjadi rusak dengan penambahan fitur yang kita tambahkan.

[Bab 8] Selain itu, tutorial ini saya juga mengimplementasikan styling dengan SCSS dan juga mengimplementasikannya pada tahap Deployment. Saya juga menggunakan base.html sebagai cara untuk tidak mengulangi suatu kode yang sama.

Kemudian, saya juga belajar untuk melakukan testing pada bentuk layout. Sehingga bisa memastikan apakah style yang saya gunakan (dengan SASS) berhasil masuk. Untuk file yang saya gunakan adalah home1.scss sedangkan jika saya menggunakan home2.scss akan menimbulkan functional test error karena pada test tersebut saya mengecek layout apakah card berada di tengah atau tidak. Ada perubahan pada functional_test dimana saya harus memakai StaticLiveServerTestCase untuk mengetes layout karena ketika saya menggunakan LiveServerTestCase style yang saya gunakan tidak tampil. Namun, penggunaannya tetap sama.

Tutorial 5

Red-Green-Refactor merupakan bentuk implementasi dari TDD. RED dikatakan sebagai fase dimana kita membuat test-test yang memungkinkan dari semua use case, dikatakan RED karena pada fase ini akan membuat semua test fail/pipeline fail karena kita belum membuat implementasi dari program tersebut. Kemudian GREEN merupakan fase dimana kita mengimplementasi program dengan memenuhi keseluruhan test yang dibuat. Hal tersebut akan membuat test kita berhasil atau pipeline kita sudah menunjukkan success. Kemudian REFACTOR merupakan fase dimana kita melakukan refaktorisasi terhadap implementasi yang ada, seperti contohnya menghapus duplikasi kode, penamaan variabel, simplifikasi fungsi dan lain-lain. Hal tersebut membuat program yang kita buat akan lebih mudah terhindar dari bug atau code smell dimana hal tersebut merupakan salah satu penanda bahwa kita menerapkan clean code atau tidak.

Kemudian, mengenai Test Organization, Test Organization merupakan bentuk pengorganisasian test berdasarkan tanggung jawab aktivitas apa yang dilakukan. Seperti pada functional test membagi pada 3 aktivitas berbeda dan test lists membagi pada 2 aktivitas berbeda, yaitu aktivitas yang berkaitan dengan views dan berkaitan dengan models. Hal tersebut memberikan keuntungan ketika program yang kita buat memuat banyak aktivitas sehingga untuk mencari test atau membuat test terkait aktivitas tersebut tidak hanya berada di 1 file yang bisa mengakibatkan file tersebut menjadi panjang.

Tutorial 6

Pada tutorial kali ini saya membuat 2 mutant pada method berikut:

def message_todo(list_):
   if (list_.item_set.count() == 0):
       return "yey, waktunya berlibur"
   elif (list_.item_set.count() >= 5):
       return "oh tidak"
       # return "yey, waktunya berlibur"
   else:
       return "sibuk tapi santai"
       # return "yey, waktunya berlibur"

Bagian yang # merupakan mutant dari metode di atas.

Pada mutant pertama, test case yang akan strongly kill terhadap mutant pertama adalah:

    def test_function_message_is_five(self):
        _list = List.objects.create()
        for i in range(5):
            Item.objects.create(text='itemey 2', list=_list)
        self.assertEqual(message_todo(_list), "oh tidak")

Pada mutant pertama, dimana seharusnya output dari test case diatas adalah "oh tidak" namun jika ada mutant pertama, maka hasilnya adalah "yey waktunya berlibur". Dimana kembali lagi ke definisi strongly kill dimana jika ada mutan maka outputnya akan berbeda.

Sedangkan pada mutan 2, test case yang strongly kill terhadap mutan 1 adalah:

    def test_function_message_is_less_than_five(self):
        _list = List.objects.create()
        Item.objects.create(text='itemey 1', list=_list)
        self.assertEqual(message_todo(_list), "sibuk tapi santai")

Pada mutant kedua, dimana seharusnya output dari test case diatas adalah "sibuk tapi santai" namun jika ada mutant pertama, maka hasilnya adalah "yey waktunya berlibur". Dimana kembali lagi ke definisi strongly kill dimana jika ada mutan maka outputnya akan berbeda.

Saya baru pertama kali menggunakan mutation tool, mutation tool yang saya gunakan adalah django-mutpy. Cara penggunannya adalah dengan menginstall django-mutpy kemudian menambahkannya pada INSTALLED_APPS. Cara menggunakannya adalah:

python manage.py muttest <app1> <app2> ... [--modules <list of modules to include>]

Kemudian saya menjalankan pada app lists:

python manage.py muttest lists

Hasilnya adalah berikut:

[*] Mutation score [420.55547 s]: 58.9%

  • all: 112
  • killed: 66 (58.9%)
  • survived: 46 (41.1%)
  • incompetent: 0 (0.0%)
  • timeout: 0 (0.0%)

Dimana fungsi yang survived merupakan fungsi atau kode yang berada pada apps.py/migrations.py. Terlihat ketika saya menjalankan muttest hanya pada bagian views:

python manage.py muttest lists --modules lists.views

Hasilnya seperti berikut:

[*] Mutation score [104.53018 s]: 100.0%

  • all: 55
  • killed: 55 (100.0%)
  • survived: 0 (0.0%)
  • incompetent: 0 (0.0%)
  • timeout: 0 (0.0%)

Pada fungsi views yang ada, semua mutasi berhasil di kill oleh test case.

Berdasarkan perintah exercise, Ubah atau tambahkan test cases yang kalian miliki untuk memperbaiki kualitas yang kalian miliki berdasarkan hasil penggunaan mutation testing tool.

Maka saya memutuskan untuk mencoba melakukan tests pada apps.py (thx to StackOverflow), saya membuat file test_apps.py seperti berikut:

class ListsConfigTest(TestCase):
    def test_apps(self):
        self.assertEqual(ListsConfig.name, 'lists')
        self.assertEqual(apps.get_app_config('lists').name, 'lists')

Kemudian kembali menjalankan perintah berikut:

python manage.py muttest lists

Dan hasilnya bertambah menjadi seperti berikut: [*] Mutation score [439.10227 s]: 60.7%

  • all: 112
  • killed: 68 (60.7%)
  • survived: 44 (39.3%)
  • incompetent: 0 (0.0%)
  • timeout: 0 (0.0%)

Tutorial 7

Pada pengembangan tutorial kali ini dikenalkan dengan istilah Spike dan De-spike. Spiking secara definisi bisa diartikan sebagai proses dimana kita mencari solusi dari program yang akan kita buat. Contohnya pada tutorial kali ini kita ingin membuat link berdasarkan user tertentu dengan menggunakan Passwordless Auth. Namun, kita belum benar-benar mengetahui cara kerja dari Passwordless Auth tersebut. Sehingga bisa saja kita terjebak dalam kode yang salah.

Spiking secara praktik adalah mencoba membuat prototype dari program yang akan dibuat. Hal tersebut membuat ketika kita benar-benar mengimplementasikannya kita tahu bahwa program tersebut akan berjalan dengan baik. Spiking sendiri tidak harus menggunakan test karena pada dasarnya tidak akan masuk ke Production.

De-spiking secara praktik adalah membuat ulang dari hasil program spiking yang sudah dibuat sebelumnya. Dengan bantuan Spiking, kita sudah tahu bahwa program kita akan berhasil, maka dari itu pada proses ini akan dibuang kode-kode yang dinilai tidak penting pada proses Spiking dan juga menerapkan TDD.

Tutorial 8

Pada bagian 19.1 - 19.5, dijelaskan mengenai konsep mocking pada Python. Pada bagian ini dijelaskan mengenai dua cara untuk melakukan mocking. Secara konsep terlebih dahulu mock adalah proses dimana menimpa suatu fungsi dari fungsi yang ada. Hal tersebut dibutuhkan ketika test yang kita lakukan akan menimbulkan side effect, contohnya pada bab ini setiap kita akan menjalankan test otomatis akan mengirimkan email.

Selain itu, saya pribadi juga sering menggunakan Mockito di Java untuk melakukan mocking database. Seperti yang sudah disebutkan sebelumnya bahwa mocking terdapat dua cara, yaitu manual dan mock library. Dengan cara manual kita akan mengganti objek berdasarkan namespace ketika runtime test. Contohnya pada kode berikut:

def fake_send_mail(subject, body, from_email, to_list):
        self.send_mail_called = True
        self.subject = subject
        self.body = body
        self.from_email = from_email
        self.to_list = to_list

accounts.views.send_mail = fake_send_mail

Sedangkan jika menggunakan mock library kita dipermudah dengan berbagai fungsionalitas yang lebih mudah digunakan dibandingan dengan mock secara manual. Dengan patch kita bisa melakukan mock terhadap suatu unit test saja atau bahkan secara skala class. Hampir semua library untuk mock mempermudah beberapa fungsionalitas seperti Mockito Java yang mempunyai, Mockito.when, Mockito.verify dan sebagainya.

Kemudian, pada bagian 19.6 - 19.8 akan menyelesaikan Functional Test hasil spike pada exercise yang lalu. Pada bagian ini, ditanyakan " mengapa mocking dapat membuat implementasi yang kita buat tightly coupled"

Kita akan mencoba melihat pada kode berikut:

messages.success(
        request,
        "Check your email, we've sent you a link you can use to log in."
    )

Kode tersebut merupakan potongan kode dari fungsi send_login_email pada accounts/views.py. Unit test yang dilakukan adalah sebagai berikut:

    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")

Test tersebut akan melihat apakah message hasil dari fungsi tersebut sesuai dengan message yang diinginkan atau tidak, beserta tagsnya apakah success atau tidak.

Jika kita menggunakan mock library untuk melakukan test pada fungsi tersebut, berdasarkan ObeyTestingGoat akan membuat test seperti berikut:

@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),
        )

Kemudian, untuk menunjukkan tightly coupled. Fungsi messages.success juga mempunyai kesamaan dengan messages.add_message yaitu pengembalian hasilnya sama. Jika kita mengganti potongan kode sebelumnya menjadi kode berikut:

messages.add_message(
        request,
        messages.SUCCESS,
        "Check your email, we've sent you a link you can use to log in."
    )

Pada test tanpa mock akan tetap lolos karena hanya akan mengecek hasil akhirnya yaitu message dan tagsnya saja. Namun, test dengan mock gagal karena pada test mock kita mencoba melakukan mock pada fungsi message.success sedangkan fungsinya kita ganti dengan message.add_message. Walaupun hasilnya sama, namun mock bergantung dengan fungsi apa yang kita mock, tanpa memperdulikan hasilnya sama atau tidak. Jadi, ketika message.add_message dipanggil tetap akan memanggil seperti biasa, tanpa ditimpa hasil mock. Hal tersebut bisa dibilang mock sangat terikat dengan implementasinya, jika implementasinya mengubah sebuah pemanggilan fungsi walau hasilnya sama, test mock juga harus diubah. Hal tersebut dinamakan "tightly coupled with the implementation"

Tutorial 9

###Bagian 20.1-20.2 Pada tutorial kali ini, dijelaskan bagaimana membuat autentikasi di Django untuk melakukan functional test. Pertama-tama saya akan mencoba menjelaskan perbedaan FT di bagian 18.3 dan FT sekarang ketika melakukan spike, terdapat test berikut:

        # she is logged in!
        self.wait_for(
            lambda: self.browser.find_element_by_link_text('Log out')
        )
        navbar = self.browser.find_element_by_css_selector('.navbar')
        self.assertIn(TEST_EMAIL, navbar.text)

        # Now she logs out
        self.browser.find_element_by_link_text('Log out').click()

        # She is logged out
        self.wait_for(
            lambda: self.browser.find_element_by_name('email')
        )
        navbar = self.browser.find_element_by_css_selector('.navbar')
        self.assertNotIn(TEST_EMAIL, navbar.text)

Kemudian, pada bagian 20.1, kami membuat juga FT yang kegunaanya sama, sehingga membuat fungsi pada base.py seperti berikut:

    @wait
    def wait_to_be_logged_in(self, email):
        self.browser.find_element_by_link_text('Log out')
        navbar = self.browser.find_element_by_css_selector('.navbar')
        self.assertIn(email, navbar.text)

    @wait
    def wait_to_be_logged_out(self, email):
        self.browser.find_element_by_name('email')
        navbar = self.browser.find_element_by_css_selector('.navbar')
        self.assertNotIn(email, navbar.text)

Sehingga pada FT yang sekarang lebih simpel:

        # she is logged in!
        self.wait_to_be_logged_in(email=TEST_EMAIL)

        # Now she logs out
        self.browser.find_element_by_link_text('Log out').click()

        # She is logged out
        self.wait_to_be_logged_out(email=TEST_EMAIL)

Kemudian, yang paling baru dari tutorial kali ini adalah mengenai membuat session autentikasi yang bisa digunakan seperti berikut:

    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='/',
        ))

Hal tersebut memudahkan kita untuk melakukan FT yang berkaitan dengan login tanpa menuliskan FT untuk melakukan login seperti yang dilakukan pada FT di 18.3. Hal tersebut jelas mempersingkat waktu FT yang seringkali memakan waktu.

Namun, ada beberapa concern, seperti kita juga harus melakukan FT dimana proses yang dilakukan terurut sehingga semua FT yang kita lakukan sudah dalam proses yang benar.

###Bagian 21.1-21.2 Pertama, saya menambahkan logging pada settings.py seperti berikut:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
        },
    },
    'root': {'level': 'INFO'},
}

Namun, saya tidak menemukan error pada bagian logs di Heroku, hal tersebut karena saya sudah melakukan solve error tersebut pada tutorial sebelumnya dengan cara memasukkan env yang dibutuhkan email di settings.py pada environment variables di Heroku.

Tutorial 10

Bagian 1 (Kompetensi Minimal)

Pada tutorial kali ini tidak terlalu banyak perubahan kode. Namun, lebih menekankan mengenai pemahaman tentang migrasi data. Misalkan, pada kasus kali ini ingin membuat setiap To Do di list merupakan unique.

Sebelumnya, saya mengganti test yang sebelumnya membuat To Do yang membuat duplikasi To Do, misalkan pada fungsi test_views.py seperti berikut:

    def test_function_message_is_five(self):
        _list = List.objects.create()
        for i in range(5):
            Item.objects.create(text='itemey 2', list=_list)
        self.assertEqual(message_todo(_list), "oh tidak")

Menjadi seperti berikut:

    def test_function_message_is_five(self):
        _list = List.objects.create()
        for i in range(5):
            Item.objects.create(text='itemey 2' + str(i), list=_list)
        self.assertEqual(message_todo(_list), "oh tidak")

Kemudian, pada models.py menambahkan kode berikut:

    class Meta:
        ordering = ('id',)
        unique_together = ('list', 'text')

Sebelumnya, pada database saya sudah menambahkan sebuah list dengan duplikasi text seperti berikut:

1. Test
2. Test
3. Test

Ketika melakukan python3 manage.py migrate akan muncul berikut:

django.db.utils.IntegrityError: could not create unique index "lists_item_list_id_text_a3f43fb7_uniq"
DETAIL:  Key (list_id, text)=(2, test) is duplicated.

Hal tersebut terjadi karena ketika python ingin mengganti versi database dengan cara menambahkan constraint bahwa id dan text harus unique, data yang sudah ada (yang sudah saya tambahkan sebelumnya) merupakan duplikasi.

Cara yang biasanya dilakukan adalah menghapus database yang ada kemudian melakukan migrasi dari ulang. Sehingga tidak akan menimbulkan error seperti sebelumnya.

Cara kedua adalah melakukan migrasi data terlebih dahulu. Untuk melakukan migrasi dengan membuat constraint unique, kita harus memigrasi data yang sudah ada sebelumnya. Dengan membuat file migrasi yang akan dijalankan sebelum membuat constraint unique tersebut. Dengan perintah python3 manage.py makemigrations lists --empty akan otomatis membuat file migrasi dengan depedensi migrasi terakhir.

Kemudian, dengan menambahkan kode berikut:

def find_dupes(apps, schema_editor):
    List = apps.get_model("lists", "List")
    for list_ in List.objects.all():
        items = list_.item_set.all()
        texts = set()
        for ix, item in enumerate(items):
            if item.text in texts:
                item.text = '{} ({})'.format(item.text, ix)
                item.save()
            texts.add(item.text)

class Migration(migrations.Migration):

    dependencies = [
        ('lists', '0004_item_list'),
    ]

    operations = [
        migrations.RunPython(find_dupes)
    ]

Kita akan membuat data duplikasi di database menjadi seperti berikut:

1. Test
2. Test (1)
3. Test (2)

Kemudian kita melakukan python3 manage.py migrate tidak akan terjadi error karena seluruh data di database tidak akan melanggar constraint.

Hal tersebut bisa terjadi dalam beberapa kasus, misalnya dengan menambahkan kolom yang foreign key terhadap suatu table dengan nilai default "X" namun X pada tabel tersebut belum ada. Dengan cara tersebut jelas menguntungkan kita karena tidak memerlukan menghapus database secara berulang-berulang.

Bagian 2 (Kompetensi Tambahan)

Pada bagian kali ini, saya akan mencoba migrasi data dummy berjumlah 100 data. Dengan cara membuat 10 list dengan isi 10 item tiap listnya.

Saya membuat data dengan cara sebagai berikut:

def create_data(apps, schema_editor):
    List = apps.get_model("lists", "List")
    Item = apps.get_model("lists", "Item")
    for i in range(10):
        _list = List.objects.create()
        for z in range(10):
            Item.objects.create(text='itemey 2' + str(z), list=_list)

Kemudian, dengan mengikuti tutorial pada link berikut:

Link Berikut

Saya akan mencoba menambah kolom slug dimana akan menampilkan slugify dari text item tersebut. Hal tersebut dilakukan dengan cara menambahkan model seperti berikut:

slug = models.SlugField(blank=True)

Kemudian migrasi seperti berikut untuk melakukan backfill pada 100 data yang sudah ada:

from django.db import migrations
from django.utils.text import slugify

def slugify(apps, schema_editor):
    List = apps.get_model("lists", "List")
    for list_ in List.objects.all():
        items = list_.item_set.all()
        for ix, item in enumerate(items):
            item.slug = slugify(item.text)
            item.save()


class Migration(migrations.Migration):

    dependencies = [
        ('lists', '0007_item_slug'),
    ]

    operations = [
        migrations.RunPython(slugify)
    ]

Eksekusi migrasi bisa dilakukan dengan menggunakan scheduler, contoh caranya seperti berikut

Klik disini

Namun, kali ini saya langsung mengeksekusinya karena pada dasarnya yang menggunakan aplikasi ini saya sendiri. Pada kasus asli, bisa dilakukan ketika dinihari, atau ketika hari libur (jika memang aplikasi yang digunakan hanya digunakan di hari kerja saja).