The Power of Test-Driven Development: Building Robust Software Systems

Raden Dhaneswara
5 min readMar 24, 2024

--

Illustration from: https://vincentdnl.com/drawings/testing-tdd

Abstract

In the ever-evolving landscape of software development, ensuring the reliability, maintainability, and scalability of applications remains paramount. Test-Driven Development (TDD) emerges as a fundamental methodology to achieve these objectives. This abstract delves into the essence of TDD, exploring its principles, methodologies, and its profound impact on the software development lifecycle.

At its core, TDD advocates for a cyclical process where developers iteratively write tests before implementing the corresponding functionality. By adopting this approach, developers gain a clear understanding of the desired behavior of their code, promoting a more structured and deliberate development process. TDD encourages the creation of modular, loosely coupled components, fostering code that is inherently easier to maintain and extend.

Introduction

Illustration from: https://www.programonaut.com/a-practical-example-of-test-drive-development/

Introduction

Test-Driven Development (TDD) stands as a beacon of modern software engineering, embodying a paradigm shift in the way developers conceive, implement, and validate code. At its essence, TDD advocates for a disciplined approach where tests are crafted before writing production code, fostering a symbiotic relationship between testing and development. This introduction explores the foundational principles of TDD, its significance in contemporary software development practices, and the transformative impact it imparts on code quality, maintainability, and developer productivity

Implementation and Example

To give a concrete example about TDD, we can take a look from an example that is given below from one of the author’s project about a medical calculator that will focus on the Front-End side of a LoginForm

And we are going to focus more on the styling of the Front-end styling of the Login Form and to minimize the percentage of the branch or functionalities that haven’t been covered

To implement the refactoring on the LoginForm and increase the percentage, we can add the covered lines with the necessary codes, these includes

test('Renders LoginForm with empty input fields', () => {
render(<LoginForm />)
const usernameInput = screen.getByPlaceholderText(
'Username'
) as HTMLInputElement
expect(usernameInput.value).toBe('')
const passwordInput = screen.getByPlaceholderText(
'Password'
) as HTMLInputElement
expect(passwordInput.value).toBe('')
})

test('Submits login form with valid username and password', () => {
render(<LoginForm />)
const usernameInput = screen.getByPlaceholderText(
'Username'
) as HTMLInputElement
const passwordInput = screen.getByPlaceholderText(
'Password'
) as HTMLInputElement
const loginButton = screen.getByRole('button', { name: 'Login' })
fireEvent.change(usernameInput, { target: { value: 'testuser' } })
fireEvent.change(passwordInput, { target: { value: 'testpassword' } })
fireEvent.click(loginButton)
expect(usernameInput.value).toBe('testuser')
expect(passwordInput.value).toBe('testpassword')
})

test('Renders LoginForm with empty input fields', () => {
render(<LoginForm />)
const usernameInput = screen.getByPlaceholderText(
'Username'
) as HTMLInputElement
expect(usernameInput.value).toBe('')
const passwordInput = screen.getByPlaceholderText(
'Password'
) as HTMLInputElement
expect(passwordInput.value).toBe('')
})

test('Submits login form with valid username and password', () => {
render(<LoginForm />)
const usernameInput = screen.getByPlaceholderText(
'Username'
) as HTMLInputElement
const passwordInput = screen.getByPlaceholderText(
'Password'
) as HTMLInputElement
const loginButton = screen.getByRole('button', { name: 'Login' })
fireEvent.change(usernameInput, { target: { value: 'testuser' } })
fireEvent.change(passwordInput, { target: { value: 'testpassword' } })
fireEvent.click(loginButton)
expect(usernameInput.value).toBe('testuser')
expect(passwordInput.value).toBe('testpassword')
})

test('Submits login form with loading state', async () => {
render(<LoginForm />)
const usernameInput = screen.getByPlaceholderText(
'Username'
) as HTMLInputElement
const passwordInput = screen.getByPlaceholderText(
'Password'
) as HTMLInputElement
const loginButton = screen.getByRole('button', { name: 'Login' })
fireEvent.change(usernameInput, { target: { value: 'testuser' } })
fireEvent.change(passwordInput, { target: { value: 'testpassword' } })
fetchMock.mockResponseOnce(JSON.stringify({}), { status: 200 })
fireEvent.click(loginButton)
await waitFor(() => {
expect(screen.getByText('Loading')).toBeInTheDocument()
})
expect(await screen.findByTestId('loading-spinner')).toBeInTheDocument()
})

test('Toggles password visibility icon', async () => {
render(<LoginForm />)
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument()
expect(screen.queryByTestId('eye-slash-icon')).not.toBeInTheDocument()
fireEvent.click(screen.getByTestId('password-toggle'))
expect(screen.getByPlaceholderText('Password')).toBeInTheDocument()
expect(screen.queryByTestId('eye-slash-icon')).toBeInTheDocument()
})

After we implement the code above, we can run the test by simply using the command (using yarn)

yarn run test

Thus, it will create a significant increase inside the tests

Even if the coverage is still hasn’t approached to 100%, there is a significant increase inside the LoginForm itself. Also, as we can see after we push the improvisation to the branch, we will get the information on Sonarqube that all of the tests has passed

Test: passed
Sonarqube:passed

And if we click the sonarqube, we will be provided with the details of the link on the details of the Sonarqube job, it will be provided with the detailed information on the Statistical analysis itself

Click the underlined link will refer you to the link
The clicked link will refer you to the detailed information

Conclusion

In conclusion, Test-Driven Development (TDD) transcends being merely a methodology; it represents a cultural shift towards excellence in software engineering. Through the rigorous adherence to writing tests before code implementation, TDD instills a mindset of proactive problem-solving, fostering a development environment where bugs are identified early, requirements are clarified thoroughly, and code quality is elevated to new heights. As developers embrace TDD, they embark on a journey of continuous improvement, iteratively refining their codebases while simultaneously honing their skills and craftsmanship

--

--