Πώς να ξεκινήσετε τη δοκιμή μονάδας κώδικα JavaScript

Όλοι γνωρίζουμε ότι πρέπει να γράφουμε τεστ μονάδας. Όμως, είναι δύσκολο να γνωρίζουμε από πού να ξεκινήσετε και πόσο χρόνο αφιερώνετε στις δοκιμές σε σύγκριση με την πραγματική εφαρμογή. Λοιπόν, από πού να ξεκινήσετε; Και πρόκειται μόνο για τον κώδικα δοκιμών ή μήπως τα τεστ μονάδας έχουν άλλα οφέλη;

Σε αυτό το άρθρο, θα εξηγήσω τους διαφορετικούς τύπους δοκιμών και τα οφέλη που προσφέρει η δοκιμή μονάδας στις ομάδες ανάπτυξης. Θα παρουσιάσω το Jest - ένα πλαίσιο δοκιμών JavaScript.

Διαφορετικοί τύποι δοκιμών

Πριν βυθιστούμε σε συγκεκριμένες δοκιμές μονάδας, θέλω να κάνω μια γρήγορη διερεύνηση των διαφόρων τύπων δοκιμών. Υπάρχει συχνά κάποια σύγχυση γύρω τους και δεν εκπλήσσομαι. Μερικές φορές η γραμμή μεταξύ τους είναι αρκετά λεπτή.

Δοκιμές μονάδας

Οι δοκιμές μονάδας δοκιμάζουν μόνο ένα μέρος της υλοποίησής σας. Μια μονάδα. Χωρίς εξαρτήσεις ή ενσωματώσεις, χωρίς συγκεκριμένα πλαίσια. Είναι σαν μια μέθοδος που επιστρέφει έναν σύνδεσμο σε μια συγκεκριμένη γλώσσα:

export function getAboutUsLink(language){ switch (language.toLowerCase()){ case englishCode.toLowerCase(): return '/about-us'; case spanishCode.toLowerCase(): return '/acerca-de'; } return ''; }

Δοκιμές ολοκλήρωσης

Σε κάποιο σημείο, ο κώδικάς σας επικοινωνεί με μια βάση δεδομένων, σύστημα αρχείων ή άλλο τρίτο μέρος. Θα μπορούσε ακόμη και να είναι μια άλλη ενότητα στην εφαρμογή σας.

Αυτό το κομμάτι της εφαρμογής θα πρέπει να δοκιμαστεί με δοκιμές ολοκλήρωσης. Συνήθως έχουν μια πιο περίπλοκη εγκατάσταση που περιλαμβάνει την προετοιμασία δοκιμαστικών περιβαλλόντων, την προετοιμασία εξαρτήσεων και ούτω καθεξής.

Λειτουργικές δοκιμές

Οι δοκιμές μονάδων και οι δοκιμές ενοποίησης σάς δίνουν την εμπιστοσύνη ότι η εφαρμογή σας λειτουργεί. Οι λειτουργικές δοκιμές εξετάζουν την εφαρμογή από την άποψη του χρήστη και ελέγχουν ότι το σύστημα λειτουργεί όπως αναμένεται.

Στο παραπάνω διάγραμμα, βλέπετε ότι οι δοκιμές μονάδας αποτελούν τη μεγάλη βάση της σουίτας δοκιμών της εφαρμογής σας. Συνήθως, είναι μικρά, υπάρχουν πολλά και εκτελούνται αυτόματα.

Λοιπόν, ας πάμε σε δοκιμές μονάδας με λίγο περισσότερες λεπτομέρειες.

Γιατί πρέπει να ασχοληθώ με τις δοκιμές μονάδας γραφής;

Κάθε φορά που ρωτώ τους προγραμματιστές εάν έγραψαν δοκιμές για την εφαρμογή τους, πάντα μου λένε: "Δεν είχα χρόνο για αυτούς" ή "Δεν τους χρειάζομαι, ξέρω ότι λειτουργεί."

Έτσι χαμογελάω ευγενικά και τους λέω τι θέλω να σας πω. Οι δοκιμές μονάδας δεν αφορούν μόνο τις δοκιμές. Σας βοηθούν και με άλλους τρόπους, ώστε να μπορείτε:

Να είστε βέβαιοι ότι ο κώδικάς σας λειτουργεί. Πότε ήταν η τελευταία φορά που κάνατε αλλαγή κώδικα, η κατασκευή σας απέτυχε και η μισή εφαρμογή σας σταμάτησε να λειτουργεί; Η δική μου ήταν την περασμένη εβδομάδα.

Αλλά αυτό είναι εντάξει. Το πραγματικό πρόβλημα είναι όταν πετύχει η έκδοση, αναπτύσσεται η αλλαγή και η εφαρμογή σας αρχίζει να είναι ασταθής.

Όταν συμβεί αυτό, αρχίζετε να χάνετε την εμπιστοσύνη στον κωδικό σας και τελικά να προσεύχεστε για να λειτουργήσει η εφαρμογή. Οι δοκιμές μονάδας θα σας βοηθήσουν να ανακαλύψετε ζητήματα πολύ νωρίτερα και να αποκτήσετε αυτοπεποίθηση.

Λάβετε καλύτερες αρχιτεκτονικές αποφάσεις. Ο κώδικας αλλάζει, αλλά ορισμένες αποφάσεις σχετικά με την πλατφόρμα, τις ενότητες, τη δομή και άλλες πρέπει να ληφθούν κατά τα πρώτα στάδια ενός έργου.

Όταν αρχίσετε να σκέφτεστε τη δοκιμή μονάδας από την αρχή, θα σας βοηθήσει να δομήσετε καλύτερα τον κώδικά σας και να επιτύχετε τον κατάλληλο διαχωρισμό των ανησυχιών. Δεν θα μπείτε στον πειρασμό να αναθέσετε πολλαπλές ευθύνες σε μονά μπλοκ κώδικα, καθώς αυτά θα ήταν ένας εφιάλτης στο τεστ μονάδας.

Εντοπίστε τη λειτουργικότητα πριν από την κωδικοποίηση. Γράφετε την υπογραφή της μεθόδου και αρχίζετε αμέσως να την εφαρμόζετε. Ω, αλλά τι πρέπει να συμβεί σε περίπτωση που μια παράμετρος είναι μηδενική; Τι γίνεται αν η τιμή του είναι εκτός του αναμενόμενου εύρους ή περιέχει πάρα πολλούς χαρακτήρες; Πετάτε μια εξαίρεση ή επιστρέφετε μηδέν;

Οι δοκιμές μονάδας θα σας βοηθήσουν να ανακαλύψετε όλες αυτές τις περιπτώσεις. Κοιτάξτε ξανά τις ερωτήσεις και θα βρείτε ακριβώς αυτό που καθορίζει τις περιπτώσεις δοκιμής μονάδας.

Είμαι βέβαιος ότι υπάρχουν πολλά περισσότερα οφέλη στη συγγραφή τεστ μονάδας. Αυτά είναι μόνο αυτά που θυμάμαι από την εμπειρία μου. Εκείνα που έμαθα με τον σκληρό τρόπο.

Πώς να γράψετε το πρώτο σας τεστ JavaScript

Αλλά ας επιστρέψουμε στη JavaScript. Θα ξεκινήσουμε με το Jest, το οποίο είναι ένα πλαίσιο δοκιμών JavaScript. Είναι ένα εργαλείο που επιτρέπει την αυτόματη δοκιμή μονάδων, παρέχει κάλυψη κώδικα και μας επιτρέπει να κοροϊδεύουμε εύκολα αντικείμενα. Το Jest διαθέτει επίσης μια επέκταση για τον Visual Studio Code που διατίθεται εδώ.

Υπάρχουν επίσης και άλλα πλαίσια, αν σας ενδιαφέρει, μπορείτε να τα ελέγξετε σε αυτό το άρθρο.

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 

Great job! This was the first simple JavaScript unit test from start to end. If you installed the Visual Studio Code extension, it will run tests automatically once you save a file. Let's try it by extending the test with this line:

expect(getAboutUsLink("cs-CZ")).toBe("/o-nas"); 

Once you save the file, Jest will inform you that the test failed. That helps you discover potential issues even before committing your changes.

Testing Advanced Functionality and Mocking Services

In real life, the language codes for the getAboutUsLink method would not be constants in the same file. Their value is typically used throughout the project so they would be defined in their own module and imported into all functions that use them.

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.

Έχετε τη δική σας εμπειρία με τον κώδικα δοκιμών; Θα ήθελα πολύ να το ακούσω, να με ενημερώσετε στο Twitter ή να συμμετάσχω σε μια από τις ροές μου Twitch.