⚡ JavaScript

JavaScript (Node.js) 代码测试:详细指南

```html

What is it and why? (a real-life analogy)

Imagine you wrote a program for a school cafeteria that automatically calculates the cost of a meal. You enter: "soup, cutlet, compote" — the program outputs the price. It works. But what happens if instead of "soup" you enter "Soup" with a capital letter? Or even "12345"? Or press the "Calculate" button before a dish is selected?

If you don't check all these cases, the program might output an incorrect price, freeze, or even delete the database. To prevent this, code testing was invented.

Testing is the process where we deliberately run our program under different conditions and see if it works as intended. It's like a test for your code: we give it tasks and check the answers.

In real life, we constantly test things. When you buy a new phone, you check: does it turn on, does the camera work, does the battery charge. If the manufacturer hadn't tested the phone before selling it, you could end up with a defective device. Similarly, programmers test their code so that users don't get a "defective" program.

Important: Testing is not about proving that the program works. It's about finding errors in it. The more errors you find yourself, the fewer the user will find.

Theory: how it works from the inside

In the programming world, there are different types of testing. We will focus on the most important one for beginners — unit testing.

Imagine the program is a LEGO set. Each piece is a function or module. Unit testing checks each piece individually: does it connect correctly, is it broken.

At the core of testing is a simple idea: we take a function, give it some input data, and compare the result with what we expect to get. If the result matches — the test passes. If not — the test fails.

In JavaScript (Node.js), there is a built-in module assert. It checks whether our assertion is true. For example:

// We assert that 2 + 2 equals 4
// If this is true — the test passes, if not — an error
const assert = require('assert');
assert.strictEqual(2 + 2, 4); // All good, test passed

But writing tests manually using assert is inconvenient. Therefore, programmers use special libraries (frameworks) for testing. The most popular one in the JavaScript world is Jest. It can:

  • Automatically find tests in the project
  • Show beautiful reports on which tests passed and which failed
  • Measure how much code is covered by tests (code coverage)
  • Create stubs (mocks) for external services

How is a test structured in Jest? Very simple:

// describe — is a group of tests (a suite)
// test or it — is one specific test
// expect — is what we are checking
// toBe — is a comparison method (matcher)

describe('Calculator', () => {
  test('adds 2 and 3', () => {
    expect(2 + 3).toBe(5);
  });
});

When you run Jest, it goes through all files, finds tests (usually files with the extension .test.js or .spec.js), runs them, and shows the result.

Important: Tests must be isolated. This means one test should not depend on another. If you run the tests in a different order, the result should be the same.

Syntax in JavaScript (Node.js): all forms and variants

Let's break down the main constructs used in testing in JavaScript. We will use the Jest library, as it is the most popular and easiest for beginners.

Installing Jest

First, you need to install Jest in the project. Open a terminal in the project folder and type:

npm init -y           # Creates package.json
npm install --save-dev jest  # Installs Jest as a dev dependency

Then, in package.json, replace the line "test": "..." with:

"scripts": {
  "test": "jest"
}

Now you can run tests with the command npm test.

Main Jest Functions

Function Description Example
describe(name, function) Groups tests by meaning describe('Calculator', () => { ... })
test(name, function) Creates a single test test('adds numbers', () => { ... })
it(name, function) Same as test (just a different style) it('should return 5', () => { ... })
expect(value) Creates an object for assertion expect(result)
toBe(expected) Checks strict equality (===) expect(2+2).toBe(4)
toEqual(expected) Checks deep equality (for objects and arrays) expect({a:1}).toEqual({a:1})
not Negates the assertion expect(2+2).not.toBe(5)

Popular Matchers (Assertion Methods)

Matcher What it checks Example
toBeNull() Value is null expect(null).toBeNull()
toBeDefined() Value is defined (not undefined) expect(1).toBeDefined()
toBeTruthy() Value is truthy (in a boolean context) expect(1).toBeTruthy()
toBeFalsy() Value is falsy expect(0).toBeFalsy()
toContain(item) Array or string contains an item expect([1,2,3]).toContain(2)
toThrow() Function throws an error expect(() => { throw new Error() }).toThrow()

Test Structure Example

Let's look at a complete example. Suppose we have a function that adds two numbers:

// math.js
function add(a, b) {
  return a + b;
}

module.exports = add;

Now let's write a test for it:

// math.test.js
const add = require('./math');

describe('add function', () => {
  test('adds positive numbers correctly', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('adds negative numbers correctly', () => {
    expect(add(-1, -2)).toBe(-3);
  });

  test('adds zero correctly', () => {
    expect(add(0, 5)).toBe(5);
  });
});

Run the tests with npm test. Jest will find the file math.test.js, run the tests, and show the result.

Practical Examples

Example 1: Testing a function that checks if a number is even

// isEven.js
function isEven(num) {
  return num % 2 === 0;
}

module.exports = isEven;
// isEven.test.js
const isEven = require('./isEven');

describe('isEven', () => {
  test('returns true for an even number', () => {
    expect(isEven(4)).toBe(true);
  });

  test('returns false for an odd number', () => {
    expect(isEven(5)).toBe(false);
  });

  test('returns true for zero', () => {
    expect(isEven(0)).toBe(true);
  });
});

Example 2: Testing a function that greets a user

// greet.js
function greet(name) {
  return `Hello, ${name}!`;
}

module.exports = greet;
// greet.test.js
const greet = require('./greet');

describe('greet', () => {
  test('greets the user by name', () => {
    expect(greet('Alice')).toBe('Hello, Alice!');
  });

  test('greets an anonymous user', () => {
    expect(greet('Guest')).toBe('Hello, Guest!');
  });
});

Common Mistakes and Tips

  • Don't test everything. Focus on the most important parts of your code: complex logic, boundary conditions, error handling.
  • Keep tests simple. Each test should check one thing. If a test fails, you should immediately understand what went wrong.
  • Use descriptive names. The test name should explain what it checks. For example: 'returns error for empty input'.
  • Don't forget edge cases. Test with empty strings, zeros, negative numbers, large values, etc.
  • Run tests often. After every change, run the tests to make sure you didn't break anything.

Conclusion

Testing is an essential part of programming. It helps you write more reliable code, find errors early, and save time in the long run. Start with simple unit tests, and gradually you will learn more complex testing techniques. Remember: a well-tested program is a program you can trust.

Remember: Tests are not just for finding errors. They are also documentation for your code. By reading the tests, another programmer (or you in the future) can understand how your functions should work.

Good luck with testing!

```