The importance of tests and next steps

Agenda

  • recap why we write tests & journey
  • facing the problem
  • testing in general & best practices
  • useful patterns
  • coding

The usefulness of testing

  1. you implement a new feature
  2. you manually test it locally, on staging, etc.
  3. other dev needs to change it weeks later
  4. other dev forgets to test the same previous use cases
  5. the feature gets broken

Outputs

1. software product for the end users

2. code (for ourselves)

So we have to care about the maintainability of our codebase

Question is that:

How we can write maintainable code?

Automated testing helps

to increase the maintainability of the entire codebase!

But how?

Tests

1. document the expected behaviour (use cases)

2. double-check during coding (less bug)

3. drive design into right direction (testability)

4. continuous feedback during development

(essential for refactoring)

What are the attributes of good test cases?

Tests

- are isolated (don't affect each other)

- are readable and understandable (structure & output)

- document the expected behaviour (BDD)

- don't depend on implementation

- easily executable and repeatable (it's essential)

 

Best practices

from our codebase

Naming and structure

 

class <EntityName>TestCase(TestCase):
    def test_<method_name>_<behaviour>_<condition>(self):
        # expected data & setup data
        
        # action
        
        # expectations

Using factories

class PlatformMessageTemplateFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = PlatformMessageTemplate

    key = factory.Faker("slug")
    miro_url = factory.Faker("url")
    value = factory.Faker("text")

Outgoing requests

class Five9ApiServiceTestCase(SimpleTestCase):
    @responses.activate
    def test_add_to_list_requests_contact_addition(self):
        expected_response = {"success": True}
        expected_query_parameters = {
            **self.expected_params,
            "F9domain": settings.FIVE9_API_DOMAIN,
            "F9list": "demo.list",
            "F9CallASAP": 1,
        }

        responses.get(
            f"{settings.FIVE9_API_BASE_URL}/web2campaign/AddToList",
            match=[responses.matchers.query_param_matcher(expected_query_parameters)],
            json=expected_response,
        )

        response = Five9ApiService.add_to_list(self.valid_payload)

        self.assertEqual(response.json(), expected_response)
        self.assertEqual(response.status_code, HTTP_200_OK)

Testing list of entities

class GuidebookPageListViewTestCase(APITestCase):
    def test_responds_with_ordered_guidebook_pages(self):
        self.guidebook_page.display_order = 1
        self.guidebook_page.save()

        expected_guidebook_pages: list[GuidebookPage] = [
            GuidebookPageFactory.create(
                display_order=0,
                publish=True,
            ),
            self.guidebook_page,
        ]

        response = self._fetch_guidebook_pages(self.mentor)

        self.assertEqual(len(response.data), len(expected_guidebook_pages))

        for index, guidebook_page in enumerate(expected_guidebook_pages):
            self.assertEqual(response.data[index]["id"], guidebook_page.id)

Reusable data

class DispositionWebhookAPITestCase(APITestCase):
    def test_returns_unauthorized_for_invalid_token(self):
        response = self._request_disposition_webhook({**self._valid_payload, "api_key": "wrong"})
        self.assertEqual(response.status_code, HTTP_401_UNAUTHORIZED)
  
    @property
    def _valid_payload(self) -> dict:
        return {
            "api_key": self.api_key_configuration.value,
            "disposition_id": f"{DispositionEventChoices.INTERVIEW_SCHEDULED}",
            "disposition_name": "This was a successful call!",
        }

    def _request_disposition_webhook(self, payload):
        return self.client.post("/integrations/five9/disposition/", payload, format="json")

Clean state

class DispositionWebhookAPITestCase(APITestCase):
    def setUp(self):
        self._clear_configuration()
        
        self.api_key_configuration: Configuration = ConfigurationFactory.create(
            key=ConfigurationOptions.F9_API_KEY,
            value="secret",
        )

    def tearDown(self):
        self._clear_configuration()

Time related tests

CURRENT_DATETIME = timezone.now().replace(microsecond=0)


class StudentFinalSurveyUpdateViewTestCase(APITestCase):
    def test_final_survey_submitted_at_set_to_current_time(self):
        with time_machine.travel(CURRENT_DATETIME):
            self._update_student_final_survey(self.student_final_survey.uuid, self._get_valid_payload(), self.student)

        self.student_final_survey.refresh_from_db()

        self.assertEqual(self.student_final_survey.submitted_at.replace(microsecond=0), CURRENT_DATETIME)

Unit tests on frontend

import { profileTimezoneOffset } from 'src/utils/profileTimezoneOffset';

describe('profileTimezoneOffset', () => {
  it('should match the following values for Budapest', () => {
    expect([-120, -60]).toContain(profileTimezoneOffset('Europe/Budapest'));
  });

  it('should match the following values for New York', () => {
    expect([240, 300]).toContain(profileTimezoneOffset('America/New_York'));
  });

  it('should match the following values for Vancouver', () => {
    expect([420, 480]).toContain(profileTimezoneOffset('America/Vancouver'));
  });

  it('should match the following values for Tokyo', () => {
    expect(profileTimezoneOffset('Asia/Tokyo')).toStrictEqual(-540);
  });
});

Gherkin E2E tests

Feature: Counselor Application

  Background: User is on counselor application page
    Given I am on the counselor application page

  Scenario: App stores input even after reload
    When I fill an input labeled 'Phone number:'
    And page is reloaded
    Then input labeled 'Phone number:' has correct value
  

thanks to Mate!

Test-driven development

1. write a failing test case for the expected behaviour

2. fix the failing test case with implementation

3. refactor

 

It might seem easy, but it takes a lot o practise!

https://www.youtube.com/watch?v=46F0e8Ye03I

Conclusion

Tests are also living part of the codebase!

Treat them as production code!

Write good quality production code,

and even better quality tests!

https://www.youtube.com/watch?v=1aQPdwuky0k

Thanks!

Copy of Internal Presentation #1: SPAs and modern front-end architecture

By Róbert Beretka

Copy of Internal Presentation #1: SPAs and modern front-end architecture

Presentation for Email Channel Crew about NodeJS project setup with TypeScript and best practices.

  • 152