The Problem with my demo code
Based on the interview meeting, the biggest problem with my code is a lot of coding logic jammed into one function. This makes the code structure very unclear, difficult to read and track the logic, which, in turn, makes it almost impossible to do the unit test.
I have no experience of coding in a team before, so I am unaware of the importance of the unit test, the importance of clear, concise module code for unit testing.
A few days ago, I read some articles about Jest, React Hooks Testing Library, and watched some videos, and then I struggled to write the first unit test. However, I stared at my demo and was intimidated by my spaghetti code before it even started. Honestly, it’s impossible to write a unit test with my messy code.
At that time, a question popped into my mind, why are the unit test examples given by others so simple and easy, why there is no example suited for my complex code situation?
From the interview I got the core answer, my code is an untestable mess.
I’m free to write my own code without having to consider the constraints of unit testing in the past. So I can write whatever I want in any style.
It is obvious that the code with cluttered logic will be difficult to maintain and troubleshoot in the future as the program grows, and the number of codes increases. If we don’t spend time writing unit tests at the early stage, we will definitely spend more time in the future constantly reworking, debugging and troubleshooting. Therefore, to ensure the quality of code at the beginning, writing unit tests immediately after each little coding function is completed will be a multiplier thing with half effort.
What is the unit test?
- UNIT TESTING is a type of software testing where individual units or components of a software are tested. The purpose is to validate that each unit of the software code performs as expected.
How will I prepare to improve?
- Since our goal is to write testable code, the first thing I need to become familiar with standard methods that libraries, such as Jest, and the React Hooks Testing Library provide. I only had a general understanding of tests through reading articles before, I didn’t get to the point where I could use them freely. So I have to be more familiar with these test libraries as a quick learner.
- My purpose is to observe and summarize the commonalities of these testable codes. Whenever I write code, I will be aware of these standards and write reliable code accordingly.
- Of course, when reading other articles, some people mentioned that you should avoid deceiving yourself, that is, don’t write easy code just to pass the test, it is meaningless.
- Next, review my code and determine which parts can be separated and moved into the smallest test function. Split the crowded code into small functional modules that match the single responsibility principle.
- I will post the refactored code in this blog later.
Here is the code I need to refactor.
useEffect(() => {
if (questions.length > 0 && number === questions.length) {
const obj = {
id: incrementIndex(),
createdAt: new Date().toLocaleString(),
category: formData.category,
type: formData.type,
difficulty: formData.difficulty,
score: score,
totalNumber: formData.amount
}
setRows(rows => [...rows, {...obj}])
const key = new Date().toLocaleDateString()
setTable(table => {
let result = {} as ParentRowProps
let keysLength = Object.keys(table).length
if (keysLength > 10) {
while (Object.keys(table).length > 10) {
let temp = Object.keys(table).shift() as string
delete table[temp]
result = {...table, [key]: [...rows.filter(r => r.createdAt.includes(key)), obj]}
}
} else {
result = {...table, [key]: [...rows.filter(r => r.createdAt.includes(key)), obj]}
}
return result
}
)
}
table && Object.entries(table).map(([k, v]) => {
let origin = v.reduce((acc: number[], {score, totalNumber}) => [...acc, (score / totalNumber)], [])
const high = (Math.max(...origin) * 100).toFixed(2)
const low = (Math.min(...origin) * 100).toFixed(2)
const avg = ((v.reduce((acc: number, {
score,
totalNumber
}) => acc + (score / totalNumber), 0) / v.length) * 100).toFixed(2)
setParentRecords(prev => {
const recordObj = {
date: k,
frequency: v.length,
highest: high,
lowest: low,
average: avg
}
let res = prev.find(p => p.date === k)
if (res) {
res.average = avg
res.frequency = v.length
res.highest = high
res.lowest = low
return [...prev]
} else {
return [recordObj, ...prev]
}
})
return ""
}
)
// eslint-disable-next-line
}, [number, table])
Here are part of the functions I refactored.
updateTables.ts
// get the object length by Object.keys()
export const ObjectLength = <T>(obj: T) => Object.keys(obj).length
export const AddNewRowToTable = (newRow: SubRowProps, key: string, rows: SubRowProps[], table: ParentRowProps) => {
return {...table, [key]: [...rows.filter(r => r.createdAt.includes(key)), newRow]}
}
// remove the first date row in the table
export const deleteFirstDateParentRow = (LimitedEntries: number, table: ParentRowProps) => {
let tableLength = ObjectLength(table)
if (tableLength > LimitedEntries) {
let lastDateKey = Object.keys(table).shift() as string
delete table[lastDateKey]
}
return table
}
export const updateTable = (newRow: SubRowProps, key: string, rows: SubRowProps[], LimitedEntries: number, table: ParentRowProps) => {
let result
let tableLength = ObjectLength(table)
if (tableLength > LimitedEntries) {
const cutTable = deleteLastDateParentRow(10, table)
result = AddNewRowToTable(newRow, key, rows, cutTable)
} else {
result = AddNewRowToTable(newRow, key, rows, table)
}
return result
}
updateRows.ts
export const updateRows = (rows:SubRowProps[],newRow: SubRowProps) => {
return [...rows, {...newRow}]
}
updateRecords.ts
export const getAllScoresFromOneDay = (subRows: SubRowProps[]) => {
return subRows.reduce((prev: number[], current: SubRowProps) => {
const {score, totalNumber} = current
const subAverageScore = score / totalNumber
return [...prev, subAverageScore]
}, [])
}
export const getHighest = (allSubScores: number[]) => {
return (Math.max(...allSubScores) * 100).toFixed(2)
}
export const getLowest = (allSubScores: number[]) => {
return (Math.min(...allSubScores) * 100).toFixed(2)
}
export const getAvg = (subRows: SubRowProps[]) => {
const rawAverage = subRows.reduce((prev: number, subRow: SubRowProps) => {
const {score, totalNumber} = subRow
return prev + score / totalNumber
}, 0)
return (rawAverage / subRows.length * 100).toFixed(2)
}
The tips or warnings about writing unit tests
- Write while developing
- Write a small amount of functional code first, then write unit tests, and repeat these two processes until the functional development is completed.
- Unit testing after development
- Firstly, the effect is the worst. The first thing we need to consider is the testability of the code. Code that has already been completed may not be testable. After all, when writing code, you can do whatever you want, and you will ignore the need to consider unit testing
- Secondly, the supplementary unit test is easy to write the test code according to the current implementation, and it is easy to ignore the actual logic requirements, which makes the unit test invalid.
The benefit of unit testing
- Improve the working efficiency
- Improve the quality of code
- Save the time or money cost for the group
- It forces you to plan before you code
- Unit tests make better designs.
Which methods, functions or components need to be unit tested?
- Transaction related code corrections
- More answers maybe need to be required from the real work situation. However, the basic principle is that the code that can cause major damage to the project should be thoroughly tested.