PMPL Sprint 1 — Upgrading Django Version, Using the New JSONField, and Increasing Code Coverage in DIGIPUS Application

Farhan Azmi
5 min readOct 14, 2020

This article is written as a part of Individual Reflection competency for Software Quality Assurance course 2020 at Faculty of Computer Science, University of Indonesia.

Introduction

In Software Quality Assurance (or known as Penjaminan Mutu Perangkat Lunak/PMPL in Bahasa) course, every student needs to work on a class project in order for them to gain competency on the course. The students are given a finished, already-working web application. Mainly, they need to improve the quality of the project by applying known software quality assurance techniques, e.g. increasing code coverage, adding new feature or improving upon existing features, refactoring, bug fixing, etc.

Throughout the course, the class project is divided into two sprints/iterations. Each student will assign themselves to one or more GitLab issue (since we’re using Git as Version Control System). They need to work on the assigned issue(s) during the sprint period on their own individual Git branches, then finally merge them into the main/master branch.

About the Application

Taken from the application documentation,

Digipus is a system application that can accommodate archiving educational material from regional apparatus, academics, and practitioners, and organizing it properly so that it becomes material for increasing knowledge and skills for the community at large.

There are three personas here, Admin, Contributor, and User (General Public). As admins, they monitor contributors and material uploaded by contributors by rejecting or approving uploaded material. As contributors, they can upload a material. And as a user, they can look at the material uploaded by contributor.

The application is built on Django framework and uses PostgreSQL as relational database.

Background on the Task

DIGIPUS application was made on Django 3.0.3. A particular model class, VerificationReport uses django.contrib.postgres.fields.JSONField as the report field of the model.

For particular use of PostgreSQL, this worked well. But what if in the future, there would be needs of migrating to another relational database management system (DBMS), e.g. MySQL, OracleDB, or MySQL. Using this JSONField would cause the application to not work properly because it would only be compatible for PostgreSQL. In other word, this application was heavily coupled on said DBMS.

Fortunately, Django 3.1 introduced a more general JSONField under django.db.models package, alongside django.forms.JSONField . This field supports MariaDB 10.2.7+, MySQL 5.7.8+, Oracle, PostgreSQL, and SQLite 3.9.0+. Migrating the application to this particular JSONField would benefit the developer in the long run. So I thought this will be worth the shot for this sprint.

In order to close this issue, I needed to do several steps:

  • Make sure to create tests for every implementation code that uses VerificationReport.report .
  • Upgrading Django Version to 3.1.
  • Replace django.contrib.postgres.fields.JSONField to django.db.models.JSONField for VerificationReport.report field.
  • Rerun all test cases to make sure that no regression occured.

Creating Test Cases for Implementation Code that Uses VerificationReport.report field

For this step, at the time of implementationVerificationReport.report was only used in generatedummy management command. So I needed to create some test cases for said command.

Here is the test cases, implemented in as Django test case.

class GenerateDummyCommandTest(TestCase):

material_numbers = [5, 10, 25, 100]
invalid_material_numbers = [-100, -10, -1, 0, 1, 2, 3, 4]
stdout = StringIO()

def test_command_output_with_given_num_of_materi(self):
for num_of_materi in self.material_numbers:
call_command("generatedummy", num_of_materi, stdout=self.stdout)
self.assertIn(
f"Successfully created {num_of_materi} materi\n",
self.stdout.getvalue()
)

def test_command_should_generate_materi_objects(self):
for num_of_materi in self.material_numbers:
call_command("generatedummy", num_of_materi, stdout=self.stdout)
self.assertEqual(Materi.objects.count(), num_of_materi)
Materi.objects.all().delete()

def test_command_should_raise_exception_if_invalid_values_are_given(self):
with self.assertRaises(IndexError):
for num_of_materi in self.invalid_material_numbers:
call_command("generatedummy", num_of_materi)
  • The first test case, test_command_output_with_given_num_of_materi asks that when the generatedummy command is called with a number of material as its argument, it should return an output in format Successfully created {num_of_material} materi. Example: generatedummy 100 should produce Successfully created 100 materi to the console.
  • The second test case, test_command_should_generate_materi_objects asks that when the generatedummy command is called with a number of material as its argument, it should record the asked number of material to the database. Example: generatedummy 100 should record 100 Materi objects.
  • The third test case, test_command_should_raise_exception_if_invalid_values_are_given asks that when the generatedummy command is called with invalid number of material, that is, any value lower than 5 should raise an IndexError .

Upgrading Django Version to 3.1

After creating the test cases, next step was to upgrade Django version to 3.1 and both asgiref version to 3.2.10 as Django was dependent on it. But since the project doesn’t utilize ASGI, asgiref was upgraded just for the sake of fulfilling Django 3.1 dependency.

Since this project uses requirements.txt to store its dependency, I added these lines to the file:

...
asgiref==3.2.10
...
Django==3.1
...

And upgraded the dependency via PIP by running

pip install -r requirements.txt

Fortunately there were no errors or problems when upgrading the dependencies and after re-running all test cases, so I could just move on to the next step.

Using the New JSONField

This step was pretty straightforward: replace the use of django.contrib.postgres.fields.JSONField to django.db.models.JSONField for VerificationReport.report field, as shown below, with emphasised lines indicating the changes:

from django.db.models import JSONField
from django.db import models
from django.utils import timezone

from app.models import VERIFICATION_STATUS, Materi
from authentication.models import User

class VerificationReport(models.Model):
report = JSONField()
materi = models.ForeignKey(Materi, models.SET_NULL, null=True)
user = models.ForeignKey(User, models.SET_NULL, null=True)
timestamp = models.DateTimeField(default=timezone.now)
status = models.CharField(
max_length=30, choices=VERIFICATION_STATUS, default=VERIFICATION_STATUS[0][0])

To apply the changes to the database, I generated the migration file by running the command:

python manage.py makemigrations

This resulted in a new migration file, shown below:

# Generated by Django 3.1 on 2020-09-29 14:25

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authentication', '0005_auto_20200519_1021'),
]

operations = [
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]

At this point, re-running all test cases showed no regression, indicating that the integration to the new JSONField was successful.

Merging to Master Branch and Code Coverage Change

The changes were merged to master branch on October 1, 2020. Screenshots below show the change to code coverage, going up from 78.2% to 85.8%.

Before merging to master branch
After merging to master branch

Lessons Learned

Maintaining an existing software is not an easy task. In my case, I had to make sure that the version upgrade and database schema change did not create any regression or break existing features. I managed to do these changes by creating test cases for the component that I was going to change, which previously did not have any test cases at all. That way, I could confidently apply the changes while keeping the application at a tip-top shape.

Conclusion

During Sprint 1, I had the opportunity to upgrade the Django framework to version 3.1 and change the JSONField usage to the newer one that supports most of the popular DBMS, not just PostgreSQL. I also created test cases for the existing implementation code that uses said field, which did not have any test cases previously. I also showed the change to code coverage after merging these changes to master branch.

This wraps up the article. Thank you for reading :)

--

--