ToDo list: a great project to learn TDD

Pablo Alonso Landa
Strategio
Published in
4 min readFeb 14, 2023

Time is a precious treasure for everyone. Planning the day well is a good practice to avoid wasting time and focusing on what we want. Writing down what we want to do in a day is a good way to achieve this planning. With a to-do list we can dedicate ourselves to things without thinking about what to do next, which takes a lot of time; and most of the time if we are already relatively exhausted from the last task we prefer to procrastinate. Similarly, checking that we have everything done in a day is an incentive for our brain, we feel more productive and we tend to want to feel that feeling again. Being disciplined with oneself is important, because if we are not disciplined with ourselves, we will be less disciplined with other people, and this could cause both professional and personal problems.

As even a to-do list saves time, testing our code is a very good practice. It helps us to define the requirements of the features we want to implement, to check that we don’t alter the performance of other parts of the code when we add new code. Not doing this could cause us to implement things we are not going to need or have to waste time fixing unexpected bugs once we add new features.

As you have noticed I am obsessed with managing my time well, that’s why a few months ago I decided to learn about Test Driven Development (TDD), and the best way to do it was to create a To-Do list (learn how to save time by creating a tool to manage time).

Requirements

I needed to make an Todo list as simple as possible. Something that would not take me too much time to achieve a Todo. The first thing I needed was a platform where I could create a list and add items to that list, at first that was my MVP.

Later, I would need an application that would allow me to register, create my lists and add items to those lists. A way to save them so that my lists would be persistent over time and would not be deleted every time I closed my browser or left the site. Also, I needed a way to authenticate as easy as possible, something with security but not having to remember another password all the time (I have a very bad memory, another reason why making a ToDo list is very convenient for me).

Plan

To achieve this I decided to use Python and django to build a monolithic web application. To test it I would use django.test and selenium to build functional tests.

In order to create a development environment I would use venv.
To handle authentications I would implement an email based authentication system, where the user would access his token in the webapp through an email that would be sent to him.

As version control system we would use Git and Github.
To achieve persistence in the data of our web we had to use a database, in this case a relational one, because our data are related to each other. SQLite was chosen because the amount of data will not be so large and it is a very light database.

Implementation

As I mentioned the purpose of creating this project was to study TDD, so to implement the code I followed the TDD lifecycle:

Following this workflow was how I managed to make my ToDo list webapp. I will show how this process worked in the implementation of the list logic to not allow the creation of an empty list item:

def test_cannot_add_empty_list_items(self):
#Marcos accidentally tries to submit an empty list item.
#he hits enter on the empty input box
self.browser.get(self.live_server_url)
self.get_item_input_box().send_keys(Keys.ENTER)

#The home page refreshes, and there is an error message saying
#that lists items cannot be blank
self.wait_for(lambda: self.browser.find_element_by_css_selector(
'#id_text:invalid'
))

#He tries again with some text and it works
self.get_item_input_box().send_keys('Buy milk')
self.wait_for(lambda: self.browser.find_element_by_css_selector(
'#id_text:valid'
))

self.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for_row_in_list_table('1: Buy milk')

#Perversaly, he now decides to submit a second blank list item
self.get_item_input_box().send_keys(Keys.ENTER)

#He receives a similar warning on the list page
self.wait_for(lambda: self.browser.find_element_by_css_selector(
'#id_text:invalid'
))

#And he can correct it by filling some text in
self.get_item_input_box().send_keys('Buy tea')
self.get_item_input_box().send_keys(Keys.ENTER)
self.wait_for_row_in_list_table('1: Buy milk')
self.wait_for_row_in_list_table('2: Buy tea')

Once we have this Functional test and we run it, it is logical that at first our code does not pass the test. Therefore, we must create some unit tests to gradually achieve to pass this test:

  def post_invalid_input(self):
list_ = List.objects.create()
return self.client.post(
f'/lists/{list_.id}/',
data={'text': ''}
)

def test_for_invalid_input_nothing_saved_to_db(self):
self.post_invalid_input()
self.assertEqual(Item.objects.count(), 0)

def test_for_invalid_input_renders_list_template(self):
response = self.post_invalid_input()
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'list.html')

def test_for_invalid_input_passes_form_to_template(self):
response = self.post_invalid_input()
self.assertIsInstance(response.context['form'], ExistingListItemForm)

def test_for_invalid_input_shows_error_on_page(self):
response = self.post_invalid_input()
self.assertContains(response, escape(EMPTY_ITEM_ERROR))

These are some tests for the view in charge of the lists, but there are also tests to ensure the correctness of our project that test the model of the items and the corresponding form.

Once the code passes all the unit tests and is refactored we run the corresponding functional test to check that everything is correct.

Conclusion

That was the workflow I followed to build each of the features of the project and meet the requirements I had proposed. If you want to know more about the implementation of the project or collaborate, feel free to do so, this is the link to the repository on GitHub. Remember testing our code is a very good practice, as good as managing our time correctly. TDD gives us the facility to focus on our requirements and achieve the YAGNI principle.

--

--