כולנו יודעים שעלינו לכתוב מבחני יחידות. אבל, קשה לדעת מאיפה להתחיל וכמה זמן להקדיש לבדיקות בהשוואה ליישום בפועל. אז מאיפה להתחיל? והאם מדובר רק בבדיקת קוד או שיש לבדיקות יחידה יתרונות אחרים?
במאמר זה אסביר את סוגי הבדיקות השונות, ואילו תועלת מביאה בדיקות היחידה לצוותי הפיתוח. אציג את Jest - מסגרת בדיקות JavaScript.
סוגים שונים של בדיקות
לפני שנצלול לפרטי בדיקות היחידה, אני רוצה לבצע ריצה מהירה של סוגי הבדיקות השונות. לעתים קרובות יש איזה בלבול סביבם ואני לא מופתע. לפעמים הקו ביניהם די דק.
מבחני יחידות
בדיקות יחידה בודקות רק חלק בודד מההטמעה שלך. יחידה. אין תלות או אינטגרציות, אין פרטי מסגרת. הם כמו שיטה המחזירה קישור בשפה מסוימת:
export function getAboutUsLink(language){ switch (language.toLowerCase()){ case englishCode.toLowerCase(): return '/about-us'; case spanishCode.toLowerCase(): return '/acerca-de'; } return ''; }
מבחני אינטגרציה
בשלב מסוים, הקוד שלך מתקשר עם מסד נתונים, מערכת קבצים או צד שלישי אחר. זה יכול להיות אפילו עוד מודול באפליקציה שלך.
את פיסת היישום הזו צריך לבדוק על ידי מבחני שילוב. בדרך כלל יש להם מערך מסובך יותר שכולל הכנת סביבות בדיקה, אתחול תלות וכו '.
מבחנים פונקציונליים
מבחני יחידות ומבחני אינטגרציה נותנים לך ביטחון שהאפליקציה שלך עובדת. בדיקות פונקציונליות בוחנות את האפליקציה מנקודת מבט המשתמש ובודקות שהמערכת עובדת כצפוי.

בתרשים לעיל, אתה רואה שמבחני יחידות מהווים את הבסיס הגדול של חבילת הבדיקות של היישום שלך. בדרך כלל הם קטנים, יש הרבה כאלה והם מבוצעים אוטומטית.
אז עכשיו בואו ניכנס למבחני יחידות בפירוט רב יותר.
מדוע עלי להתמודד עם מבחני יחידות כתיבה?
בכל פעם שאני שואל מפתחים אם הם כתבו מבחנים ליישום שלהם, הם תמיד אומרים לי: "לא היה לי זמן בשבילם" או "אני לא צריך אותם, אני יודע שזה עובד."
אז אני מחייך בנימוס ואומר להם מה אני רוצה להגיד לך. בדיקות יחידה אינן קשורות רק לבדיקות. הם עוזרים לך גם בדרכים אחרות, כך שתוכל:
היה בטוח שהקוד שלך עובד. מתי בפעם האחרונה ביצעת שינוי קוד, ה- build שלך נכשל, ומחצית מהאפליקציה שלך הפסיקה לעבוד? שלי היה בשבוע שעבר.
אבל זה עדיין בסדר. הבעיה האמיתית היא כאשר הבנייה מצליחה, השינוי נפרס והאפליקציה שלך מתחילה להיות לא יציבה.
כשזה קורה, אתה מתחיל לאבד את הביטחון בקוד שלך ובסופו של דבר פשוט להתפלל שהאפליקציה תעבוד. מבחני יחידות יעזרו לך לגלות בעיות הרבה יותר מהר ולקבל ביטחון.
קבל החלטות אדריכליות טובות יותר. שינויים בקוד, אך יש לקבל החלטות מסוימות לגבי פלטפורמה, מודולים, מבנה ואחרים בשלבים המוקדמים של הפרויקט.
כאשר אתה מתחיל לחשוב על בדיקות יחידות מיד עם ההתחלה, זה יעזור לך לבנות את הקוד שלך טוב יותר ולהשיג הפרדה נכונה בין החששות. לא תתפתו להקצות תחומי אחריות מרובים לגושי קוד בודדים מכיוון שאלה יהיו סיוט לבדיקת יחידות.
פונקציונליות נקודתית לפני קידוד. אתה כותב את חתימת השיטה ומתחיל ליישם אותה מיד. אה, אבל מה צריך לקרות במקרה שפרמטר הוא אפס? מה אם הערך שלה נמצא מחוץ לטווח הצפוי או מכיל יותר מדי תווים? האם אתה זורק חריג או מחזיר null?
מבחני יחידות יעזרו לכם לגלות את כל המקרים הללו. בדוק שוב את השאלות ותגלה שזה בדיוק מה שמגדיר את מקרי מבחן היחידות שלך.
אני בטוח שיש יתרונות רבים יותר לכתיבת מבחני יחידות. אלה הם רק אלה שזכור לי מניסיוני. אלה שלמדתי בדרך הקשה.
כיצד לכתוב את בדיקת יחידת JavaScript הראשונה שלך
אבל בוא נחזור ל- JavaScript. נתחיל ב- Jest, שהיא מסגרת לבדיקת JavaScript. זהו כלי המאפשר בדיקות יחידה אוטומטיות, מספק כיסוי קוד ומאפשר לנו ללעוג לאובייקטים בקלות. ל- Jest יש הרחבה עבור קוד Visual Studio הזמין כאן.
יש גם מסגרות אחרות, אם אתה מעוניין, תוכל לבדוק אותן במאמר זה.
npm i jest --save-dev
בואו נשתמש בשיטה שהוזכרה בעבר כהטמעה getAboutUsLink
שאנחנו רוצים לבדוק:
const englishCode = "en-US"; const spanishCode = "es-ES"; function getAboutUsLink(language){ switch (language.toLowerCase()){ case englishCode.toLowerCase(): return '/about-us'; case spanishCode.toLowerCase(): return '/acerca-de'; } return ''; } module.exports = getAboutUsLink;
הכנסתי את זה index.js
לקובץ. אנו יכולים לכתוב מבחנים באותו קובץ, אך נוהג טוב הוא להפריד מבחני יחידות לקובץ ייעודי.
דפוסי השמות הנפוצים כוללים {filename}.test.js
ו {filename}.spec.js
. השתמשתי בראשון index.test.js
,:
const getAboutUsLink = require("./index"); test("Returns about-us for english language", () => { expect(getAboutUsLink("en-US")).toBe("/about-us"); });
ראשית, עלינו לייבא את הפונקציה אותה אנו רוצים לבדוק. כל בדיקה מוגדרת כקריאה test
לפונקציה. הפרמטר הראשון הוא שם הבדיקה לעיונך. השנייה היא פונקציית חץ בה אנו מכנים את הפונקציה אותה אנו רוצים לבדוק ומציינים לאיזו תוצאה אנו מצפים. אני
במקרה זה אנו מכנים getAboutUsLink
פונקציה עם en-US
פרמטר השפה. אנו מצפים שהתוצאה תהיה /about-us
.
כעת אנו יכולים להתקין את ה- Jest CLI ברחבי העולם ולהריץ את הבדיקה:
npm i jest-cli -g jest
אם אתה רואה שגיאה הקשורה בתצורה, ודא שקיים את package.json
הקובץ שלך . במקרה שלא, צור אחד באמצעות npm init
.
אתה אמור לראות משהו כזה:
PASS ./index.test.js √ Returns about-us for english language (4ms) console.log index.js:15 /about-us Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.389s
עבודה טובה! זו הייתה בדיקת יחידת JavaScript הפשוטה הראשונה מההתחלה ועד הסוף. אם התקנת את סיומת Visual Studio Code, היא תריץ בדיקות באופן אוטומטי לאחר שמירת קובץ. בואו ננסה זאת על ידי הרחבת הבדיקה בשורה זו:
expect(getAboutUsLink("cs-CZ")).toBe("/o-nas");
לאחר שמירת הקובץ, Jest יודיע לך שהבדיקה נכשלה. זה עוזר לך לגלות בעיות פוטנציאליות עוד לפני ביצוע השינויים שלך.
בדיקת שירותי פונקציונליות ולועגים מתקדמים
בחיים האמיתיים, קודי השפה של שיטת getAboutUsLink לא יהיו קבועים באותו קובץ. הערך שלהם משמש בדרך כלל במהלך הפרויקט, כך שהם יוגדרו במודול שלהם ויובאו לכל הפונקציות המשתמשות בהם.
import { englishCode, spanishCode } from './LanguageCodes'
You can import these constants into the test the same way. But the situation will get more complicated if you're working with objects instead of simple constants. Take a look at this method:
import { UserStore } from './UserStore' function getUserDisplayName(){ const user = UserStore.getUser(userId); return `${user.LastName}, ${user.FirstName}`; }
This method uses imported UserStore
:
class User { getUser(userId){ // logic to get data from a database } setUser(user){ // logic to store data in a database } } let UserStore = new User(); export { UserStore }
In order to properly unit test this method, we need to mock UserStore
. A mock is a substitute for the original object. It allows us to separate dependencies and real data from the tested method's implementation just like dummies help with crash tests of cars instead of real people.
If we didn't use the mock, we'd be testing both this function and the store. That would be an integration test and we would likely need to mock the used database.
Mocking a Service
To mock objects, you can either provide a mocking function or a manual mock. I will focus on the latter as I have a plain and simple use-case. But feel free to check out other mocking possibilities Jest provides.
jest.mock('./UserStore', () => ({ UserStore: ({ getUser: jest.fn().mockImplementation(arg => ({ FirstName: 'Ondrej', LastName: 'Polesny' })), setUser: jest.fn() }) }));
First, we need to specify what are we mocking - the ./UserStore
module. Next, we need to return the mock that contains all exported objects from that module.
In this sample, it's only the User
object named UserStore
with the function getUser
. But with real implementations, the mock may be much longer. Any functions you don't really care about in the scope of unit testing can be easily mocked with jest.fn()
.
The unit test for the getUserDisplayName
function is similar to the one we created before:
test("Returns display name", () => { expect(getUserDisplayName(1)).toBe("Polesny, Ondrej"); })
As soon as I save the file, Jest tells me I have 2 passing tests. If you're executing tests manually, do so now and make sure you see the same result.
Code Coverage Report
Now that we know how to test JavaScript code, it's good to cover as much code as possible with tests. And that is hard to do. In the end, we're just people. We want to get our tasks done and unit tests usually yield an unwanted workload that we tend to overlook. Code coverage is a tool that helps us fight that.
Code coverage will tell you how big a portion of your code is covered by unit tests. Take for example my first unit test checking the getAboutUsLink
function:
test("Returns about-us for english language", () => { expect(getAboutUsLink("en-US")).toBe("/about-us"); });
It checks the English link, but the Spanish version stays untested. The code coverage is 50%. The other unit test is checking the getDisplayName
function thoroughly and its code coverage is 100%. Together, the total code coverage is 67%. We had 3 use cases to test, but our tests only cover 2 of them.
To see the code coverage report, type the following command into the terminal:
jest --coverage
Or, if you're using Visual Studio Code with the Jest extension, you can run the command (CTRL+SHIFT+P) Jest: Toggle Coverage Overlay. It will show you right in the implementation which lines of code are not covered with tests.

By running the coverage check, Jest will also create an HTML report. Find it in your project folder under coverage/lcov-report/index.html
.

Now, I don't have to mention that you should strive for 100% code coverage, right? :-)
Summary
In this article, I showed you how to start with unit testing in JavaScript. While it's nice to have your code coverage shine at 100% in the report, in reality, it's not always possible to (meaningfully) get there. The goal is to let unit tests help you maintain your code and ensure it always works as intended. They enable you to:
- clearly define implementation requirements,
- better design your code and separate concerns,
- discover issues you may introduce with your newer commits,
- and give you confidence that your code works.
The best place to start is the Getting started page in the Jest documentation so you can try out these practices for yourself.
Do you have your own experience with testing code? I'd love to hear it, let me know on Twitter or join one of my Twitch streams.