Fakultas Ilmu Komputer UI

Skip to content
Snippets Groups Projects

Repository Tutorial PMPL

Izzan Fakhril Islam (1606875806)

Daftar Tutorial:

  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
  4. Tutorial 4: Prettification Test (Functional test pada CSS Django)
  5. Tutorial 5: Test Organization
  6. Tutorial 6: Mutation Testing
  7. Tutorial 7: Spiking & De-Spiking

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:

$ python manage.py test -p "functional_test*.py"

Sedangkan pada job UnitTest berisikan script sebagai berikut:

$ 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:

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:

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:

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.

Penjelasan Tutorial 4

Berdasarkan buku Test-Driven Development with Python 2nd Edition, tutorial ini memiliki beberapa keterkaitan dengan materi yang disajikan di bab 7 (Working Incrementally), diantaranya adalah sebagai berikut.

  • Implementasi method-method pada unit test untuk pengecekan redirection dan penggunaan template file oleh pada berkas views.py. Diantaranya sebagai berikut.

    class Tutorial2UnitTest(TestCase):
    
        def test_use_correct_template(self):
            response = self.client.get('/tutorial-2/')
            self.assertTemplateUsed(response, 'tutorial_2.html')
    
        def test_can_save_POST_request_todo(self):
            self.client.post(
                '/tutorial-2/add_todo/',
                data={
                    'date': '2019-09-05T14:31',
                    'activity': DUMMY_TODO_ITEM
                }
            )
            todo_count = TodoList.objects.count()
            new_todo = TodoList.objects.first()
    
            self.assertEqual(todo_count, 1)
            self.assertEqual(getattr(new_todo, 'todo_list'), DUMMY_TODO_ITEM)
    
        def test_can_save_POST_request_todo_commentary(self):
            self.client.post(
                '/tutorial-2/add_todo_commentary/',
                data={
                    'date': '2019-09-12',
                    'comment': DUMMY_TODO_COMMENTARY_ITEM
                }
            )
            todo_commentary_count = TodoListCommentary.objects.count()
            new_todo_commentary = TodoListCommentary.objects.first()
    
            self.assertEqual(todo_commentary_count, 1)
            self.assertEqual(getattr(new_todo_commentary, 'comment'), DUMMY_TODO_COMMENTARY_ITEM)
    
        def test_redirects_after_POST_todo(self):
            response = self.client.post(
                '/tutorial-2/add_todo/',
                data={
                    'date': '2019-09-05T14:31',
                    'activity': DUMMY_TODO_ITEM
                }
            )
            self.assertEqual(response.status_code, 302)
            self.assertRedirects(response, '/tutorial-2/')
    
        def test_redirects_after_POST_todo_commentary(self):
            response = self.client.post(
                '/tutorial-2/add_todo_commentary/',
                data={
                    'date': '2019-09-12',
                    'comment': DUMMY_TODO_COMMENTARY_ITEM
                }
            )
            self.assertEqual(response.status_code, 302)
            self.assertRedirects(response, '/tutorial-2/')

    Selain itu, saya juga menambahkan implementasi unit tests dengan menggunakan LiveServerTestCase yaitu sebuah library bawaan dari Django, sehingga saya tidak perlu menjalankan perintah runserver lagi.

  • Pengimplementasian functional test yang sudah disesuaikan dengan unit test yang diimplementasikan berdasarkan bab 7, sehingga ketika dijalankan automated script untuk unit test maupun functional test, berhasil.

Selanjutnya, untuk pengimplementasian tutorial 4 ini, saya menambahkan aesthetics test, yaitu functional testing pada CSS dengan menggunakan modul StaticLiveServerTestCase yang merupakan bawaan dari Django. Berikut adalah salah satu contoh implementasinya.

def test_layout_and_styling_todo_input_textbox(self):
  selenium = self.selenium
  selenium.get(self.host)
  selenium.set_window_size(1024, 768)

  todo_date = selenium.find_element_by_id('todo_date')
  self.assertAlmostEqual(
    todo_date.location['x'] + todo_date.size['width'] / 2,
    740,
    delta=10
  )

def test_layout_and_styling_todo_commentary_input_textbox(self):
  selenium = self.selenium
  selenium.get(self.host)
  selenium.set_window_size(1024, 768)

  todo_commentary = selenium.find_element_by_id('comment_date')
  self.assertAlmostEqual(
    todo_commentary.location['x'] + todo_commentary.size['width'] / 2,
    780,
    delta=10
  )

Untuk templating languange pada HTML, saya sudah mengimplementasikan sebelumya, dimulai dari app tutorial-1 dan tutorial-2

{% load staticfiles %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="description" content="Tutorial 2 PMPL">
    <meta name="author" content="{{ author }}">
    <!-- bootstrap csss -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">

    <title>
        {% block title %} Tutorial 2 PMPL by {{ author }} - {{ npm }}{% endblock %}
    </title>
</head>
<style>
    @import url(https://fonts.googleapis.com/css?family=Roboto|Roboto+Slab);
    .container, .footer-down, .header-up{
        font-family: "Roboto", "sans-serif";
        font-weight: normal;
    }

</style>
<body style="background-color: darkseagreen">
    <header class="header-up">
        {% include "tutorial_2/../partials/header.html" %}
    </header>
    <content>
        <div class="container">
            {% block content %}

            {% endblock %}
        </div>

    </content>
    <footer class="footer-down">
        <!-- TODO Block Footer dan include footer.html -->
        {% block footer %}
        {% include "tutorial_2/../partials/footer.html" %}
        {% endblock %}
    </footer>

    <!-- Jquery n Bootstrap Script -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script type="application/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
</html>

Dan berikut adalah konten dari tutorial_2.html saya.

{% extends "tutorial_2/../layout/base.html" %}

{% block content %}
    <style>
    .form-control {
        @import url(https://fonts.googleapis.com/css?family=Droid+Sans+Mono);
        margin-top: 3px;
        margin-bottom: 3px;
        font-family: "Droid Sans Mono";
    }
    table {
        text-align: left;
        padding: 8px;
        width: 100%;
    }
    </style>
    <h1 align="center" style="font-weight: bold">{{ author }}'s Todo List</h1>
    <br><br>
    <div class="row">
        <div class="col-md-6 col-xs-6">
            <h4>Todo List Table</h4>
            <br>
            <table id="todo_table">
                <tr>
                    <th><h5><b>Date/Time</b></h5></th>
                    <th><h5><b>Todo</b></h5></th>
                </tr>
                {% for todos in todos_dict %}
                    <tr>
                        <td>{{ todos.date }}</td>
                        <td>{{ todos.todo_list }}</td>
                    </tr>
                {% endfor %}
            </table>
            <br>
            <h4>Daily Todo List Comments</h4>
            <br>
            <table id="todo_comment_table">
                <tr>
                    <th><h5><b>Date</b></h5></th>
                    <th><h5><b>Comment</b></h5></th>
                </tr>
                {% for commentary in todos_commentary_dict %}
                    <tr>
                        <td>{{ commentary.date }}</td>
                        <td>{{ commentary.comment }}</td>
                    </tr>
                {% endfor %}
            </table>
            <br>
        </div>
        <div class="col-md-6 col-xs-6">
            <h4><b>Add Todo</b></h4>
            <form method="POST" action="{% url 'tutorial-2:add_todo' %}">
                {% csrf_token %}
                <table>
                    <tr>
                        <td>
                            Date/Time
                        </td>
                        <td>
                            <input type="datetime-local" name="date" id="todo_date" required="required" size="27"/>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Todo
                        </td>
                        <td>
                            <input type="text" name="activity" id="activity" required="required" size="27">
                        </td>
                    </tr>
                </table>
                <br>
                <button type="submit" id="todo_submit" class="button btn-success">Submit</button>
            </form>
            <br>
            <div class="errornote">
                <p style="color: black; font-family: 'Roboto Slab'">{{ error_msg }}</p>
            </div>
            <h4><b>Add Daily Todo List Comment</b></h4>
            <form method="POST" action="{% url 'tutorial-2:add_todo_commentary' %}">
                {% csrf_token %}
                <table>
                    <tr>
                        <td>
                            Date
                        </td>
                        <td>
                            <input type="date" name="date" id="comment_date" required="required" size="27"/>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            Todo List Comment
                        </td>
                        <td>
                            <input type="text" name="comment" id="comment" required="required" size="27">
                        </td>
                    </tr>
                </table>
                <br>
                <button type="submit" id="comment_submit" class="button btn-success">Submit</button>
            </form>
            <br>
            <div class="errornote">
                <p style="color: black; font-family: 'Roboto Slab'">{{ commentary_error_msg }}</p>
            </div>
        </div>
    </div>
{% endblock %}

Penjelasan Tutorial 5

Berdasarkan buku Test-Driven Development with Python 2nd Edition, tutorial ini memiliki beberapa keterkaitan dengan materi yang disajikan di bab 12 (Splitting Our Tests into Multiple Files, and a Generic Wait Helper) dan bab 13 (Validation at the Database Layer), diantaranya adalah sebagai berikut.

  1. Membuat direktori baru untuk pemecahan file-file unit test dan functional test dalam folder django app tutorial_5 menjadi seperti dibawah ini.
  • .
    └── tutorial_5
        ├── __init__.py
        ├── apps.py
        ├── functional_tests
        |		├── __init__.py
        |		├── base.py
        |		├── test_css_aesthetics.py
        |		├── test_todo_commentary.py
        |		├── test_todo_item.py
        |		└── test_website_elements.py
        └── unit_tests
        		├── __init__.py
        		├── test_todo_comment_table_models_django_testcase.py
        		└── test_todo_table_models_django_testcase.py

    Pada tutorial 5, saya diminta untuk memisahkan beberapa test case dalam unit test dan functional test menjadi beberapa file yang terpisah, dan menerapkan inheritance pada modul functional test saya, dengan parent class nya ada di potongan file base.py seperti berikut.

    class FunctionalTest(StaticLiveServerTest):
        def setUp(self):
            chrome_options = Options()
            chrome_options.add_argument('--no-sandbox')
            chrome_options.add_argument('--headless')
            chrome_options.add_argument('--disable-dev-shm-usage')
            # 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/"
            self.selenium.implicitly_wait(3)
    
            super(FunctionalTest, self).setUp()
    
        def tearDown(self):
            self.selenium.quit()
            super(FunctionalTest, self).tearDown()

    Selanjutnya, pada file-file yang lain saya meng-extend class FunctionalTest yang sudah dibuat, seperti contoh pada potongan file test_todo_item.py berikut.

    from .base import FunctionalTest
    from selenium.webdriver.common.keys import Keys
    
    class TodoItemTest(FunctionalTest):
        def test_input_todo_item(self):
            selenium_host = self.get_host_from_selenium(self.host)
    
            todo_date = selenium_host.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_host.find_element_by_id('activity')
            todo_text.send_keys('Dummy')
            todo_text.send_keys(Keys.ENTER)
    
            self.wait_for_row_list_in_table("Dummy", "todo_table")

    Hal yang sama juga berlaku untuk file-file dalam folder unit_tests yang dimana setiap test case dipisahkan menjadi beberapa file terpisah berdasarkan jenis models dan forms yang ingin dites.

  1. Mengimplementasikan generic explicit wait helper dalam functional test dengan membuat method berikut.

    # Same as wait_for_row_list_in_table but using lambda expressions
    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)

    Kemudian, method yang sudah dibuat digunakan dalam bentuk lambda expressions potongan kode berikut.

    def test_input_todo_commentary_item(self):
        selenium_host = self.get_host_from_selenium(self.host)
    
        todo_date = selenium_host.find_element_by_id('comment_date')
        todo_date.send_keys('23122019')
    
        todo_comment = selenium_host.find_element_by_id('comment')
        todo_comment.send_keys("Dummy comment")
        todo_comment.send_keys(Keys.ENTER)
    
        self.wait_for(
          lambda: self.assertIn("Dummy comment", selenium_host.page_source)
        )

    Salah satu keuntungan dalam pengimplementasian generic explicit wait helper adalah parameternya yang merupakan sebuah fungsi, bukan sebuah primitive data type sehingga dapat digunakan untuk berbagai macam variasi fungsi waiter.

  2. Mengimplementasikan database layer validation pada unit test dengan membuat model-layer validation seperti pada potongan kode dibawah ini.

    # 1
    def test_add_todo_item_via_model(self):
        TodoList.objects.create(
          date=timezone.now(),
          todo_list=DUMMY_TODO_ITEM
        )
        todo_count = TodoList.objects.all().count()
        self.assertEqual(todo_count, 1)
        
    #2
    def test_cannot_add_empty_todo_model(self):
        new_todo = TodoList(
          date="",
          todo_list="",
        )
        with self.assertRaises(ValidationError):
          new_todo.save()
          new_todo.full_clean()

    Penjelasan: Pada method nomor 1, dilakukan tes untuk memverifikasi apabila sebuah model memenuhi persyaratan dari properti-propertinya, dan dapat di-save dengan sukses, dengan melakukan assertion pada jumlah model TodoList yang berhasil dibuat. Sedangkan pada method nomor 2, dilakukan tes untuk mengecek apabila sebuah model tidak memenuhi persyaratan dari properti-propertinya, tidak dapat di-save dan akan melempar exception ValidationError

    Selain mengimplementasikan dari model-layer validation saya juga membuat test untuk memvalidasi database dengan mengeceknya menggunakan POST request, seperti ditunjukkan pada potongan kode dibawah.

    # 1
    def test_POST_request_create_todo(self):
        self.client.post(
          '/tutorial-2/add_todo/',
          data={
            'date': '2019-09-12T15:15',
            'activity': DUMMY_TODO_ITEM
          }
        )
        todo_count = TodoList.objects.all().count()
        page_response = self.client.get('/tutorial-2/')
        html_response = page_response.content.decode('utf8')
    
        self.assertEqual(todo_count, 1)
        self.assertIn(DUMMY_TODO_ITEM, html_response)
    
    # 2
    def test_error_create_todo_from_view(self):
        response = self.client.post(
          '/tutorial-2/add_todo/',
          data={
            'date': '',
            'activity': ''
          }
        )
        error_msg = "ERROR: Failed to add Todo List"
    
        self.assertEqual(response.status_code, 200)
        self.assertRaises(ValueError)
        self.assertContains(response, error_msg)

    Penjelasan: Pada method nomor 1, dilakukan POST request ke URL /tutorial-2/add_todo/ dengan memasukkan data-data yang valid (memenuhi persyaratan dari properti-properti model TodoList ), dan dilakukan pengecekan atas 2 hal, apakah instance dari model TodoList berhasil terbentuk, dan apakah tulisan yang telah dibuat berhasil ditampilkan pada halaman website setelah di-refresh .

    Sedangkan pada method nomor 2, dilakukan POST request ke URL /tutorial-2/add_todo/ dengan memasukkan data-data yang tidak valid, dan dillakukan pengecekan atas 3 hal, apakah status code yang ditampilkan bernilai 200 (halaman input todo dengan pesan error berhasil ditampilkan), apakah exception ValueError berhasil ter-trigger dan apakah pesan error berhasil muncul pada halaman website setelah di-refresh .

Ada beberapa keuntungan dari pemisahan file-file unit tests dan functional tests atau yang disebut juga Test Organization, antara lain sebagai berikut.

  • Pengelompokkan setiap test case berdasarkan fungsinya, halaman web spesifik yang akan ditest, akan membuat kode lebih tertata.

  • Maintenance test case yang menjadi lebih mudah dikarenakan pengelompokkan kode test case

  • Dapat dilakukan eksekusi test case tertentu saja, tidak harus mengeksekusi semua kode test case yang ada pada sebuah project.

Penjelasan Tutorial 6

Pada pengerjaan Tutorial 6 ini, menggunakan cosmic-ray yang merupakan sebuah library dari Python yang digunakan untuk melakukan mutation testing dan menghasilkan sebuah report dalam bentuk file HTML. Instalasi cosmic-ray dilakukan dengan cara sebagai berikut.

$ pip install cosmic-ray
$ pip freeze > requirements.txt # update requirements.txt

Selanjutnya, dilakukan pembuatan configuration files yang akan digunakan oleh cosmic-ray dalam menjalankan mutation testing, file tersebut bernama config.toml

[cosmic-ray]
module-path = "tutorial_2/views.py"
python-version = ""
timeout = 10.0
excluded-modules = []
test-command = "/Users/izznfkhrlislm/Documents/Projects/PMPL/lab-pmpl/bin/python manage.py test tutorial_2.unit_tests"

[cosmic-ray.execution-engine]
name = "local"

[cosmic-ray.cloning]
method = "copy"
commands = []

[cosmic-ray.interceptors]
enabled = [ "spor", "pragma_no_mutate", "operators-filter",]

[cosmic-ray.operators-filter]

Terlihat pada configuration files diatas, saya menggunakan berkas unit tests Tutorial 2, dikarenakan pada tugas tutorial 6 ini diminta untuk melakukan mutation testing pada halaman to-do list, yang merupakan implementasi dari pengerjaan Tutorial 2.

Selanjutnya, dilakukan pembuatan testing database yang akan digunakan oleh cosmic-ray untuk menyimpan data-data yang dibuat dari eksekusi unit tests yang dibuat mutation testing-nya.

$ cosmic-ray init config.toml mut_test.sqlite

Selanjutnya, dilakukan eksekusi mutation testing.

$ cosmic-ray exec mut_test.sqlite

Setelah eksekusi mutation testing selesai, dilakukan report generating terhadap hasil mutation testing, dengan cara sebagai berikut.

$ cr-report mut_test.sqlite

Perintah tersebut menghasilkan output sebagai berikut.

total jobs: 21
complete: 21 (100.0%)
survival rate: 28.57%

Penjelasan: pada hasil output dari report hasil mutation testing tersebut, terlihat bahwa cosmic-ray melakukan pengujian sebanyak 21 kali terhadap 8 unit test cases yang terdapat dalam file unit_tests.py yang ada di modul Tutorial 2. Dari 21 kali pengujian, menghasilkan survival rate sebesar 28.57%, yang menandakan bahwa 28.57% dari keseluruhan pengujian yang dilakukan berhasil survive, tidak berhasil di-kill oleh mutation testing.

Berikut adalah salah satu potongan dari report dalam bentuk HTML yang dihasilkan.

tutorial_2/views.py start pos: (30, 22), end pos: (30, 24)
operator: core/ReplaceComparisonOperator_Eq_Lt, occurence: 0
--- mutation diff ---
--- atutorial_2/views.py
+++ btutorial_2/views.py
@@ -27,7 +27,7 @@
 
 
 def add_todo(request):
-    if request.method == 'POST':
+    if request.method < 'POST':
         try:
             date = datetime.strptime(request.POST['date'], '%Y-%m-%dT%H:%M')
             TodoList.objects.create(
             
worker outcome: normal, test outcome: killed

Pada potongan report tersebut, terlihat bahwa dilakukan operasi replace comparison operator oleh cosmic-ray, dengan mengubah potongan kode if request.method == 'POST' menjadi if request.method < 'POST', dan behasil membuat unit test menjadi gagal, yang berarti unit test tersebut strongly killed oleh mutation testing.

Penjelasan Tutorial 7

Berdasarkan buku Test-Driven Development with Python 2nd Edition, tutorial ini memiliki beberapa keterkaitan dengan materi yang disajikan di bab 18 (User Authentication, Spiking, and De-Spiking), proses pengerjaan tutorial 7 ini dibagi menjadi beberapa tahap.

  1. Mengimplementasikan custom dedicated list untuk setiap pengguna, dengan menerapkan Passwordless Authentication, dengan menyimpan entity Token di file models.py dari app yang berisikan email pengguna dan sebuah unique id (uid) yang di-generate secara otomatis oleh sistem. Pengimplementasian fitur baru ini dilakukan di development branch dan tidak disertai TDD, dikarenakan masih bersifat trial and error. Hal ini dapat disebut juga dengan Spiking.
  2. Setelah fitur passwordless authentication berhasil diimplementasikan pada development branch, dilakukan implementasi secara menyeluruh dengan melibatkan proses TDD didalamnya, dan dilakukan pada staging/production branch. Hal ini disebut juga dengan De-Spiking.