☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Nền tảng (Fundamentals)

Testing (Kiểm thử)

Bản dịch từ docs.nestjs.com/fundamentals/testing

Automated testing (kiểm thử tự động) được coi là một phần thiết yếu của bất kỳ dự án phát triển phần mềm nghiêm túc nào. Automation giúp việc lặp lại các test riêng lẻ hay cả test suite trở nên nhanh chóng và dễ dàng trong suốt quá trình phát triển. Điều này giúp đảm bảo mỗi release đạt được mục tiêu về chất lượng và hiệu năng. Automation giúp tăng độ phủ (coverage) và mang lại feedback loop nhanh hơn cho developer. Automation vừa tăng năng suất của từng developer, vừa đảm bảo test được chạy ở những thời điểm quan trọng trong vòng đời phát triển, như khi check-in source code, tích hợp tính năng, hay phát hành phiên bản.

Các test như vậy thường trải rộng trên nhiều loại: unit test, end-to-end (e2e) test, integration test, v.v. Dù lợi ích là không thể bàn cãi, việc thiết lập chúng có thể khá tốn công. Nest hướng tới việc thúc đẩy các best practice (cách làm khuyến nghị) trong phát triển phần mềm, bao gồm cả việc testing hiệu quả, vì vậy nó cung cấp sẵn các tính năng sau để giúp developer và team xây dựng, tự động hóa việc test. Nest:

  • tự động scaffold (tạo khung) unit test mặc định cho các component, và e2e test cho ứng dụng
  • cung cấp sẵn tooling mặc định (như một test runner tự xây dựng bộ loader module/ứng dụng độc lập)
  • tích hợp với Vitest và Supertest trong khi vẫn giữ tính trung lập (agnostic) với công cụ test
  • cho phép dùng hệ thống dependency injection (tiêm phụ thuộc, DI) của Nest ngay trong môi trường testing, giúp dễ dàng mock component

Như đã nói, bạn có thể dùng bất kỳ testing framework nào mình thích, vì Nest không ép buộc bạn theo bất kỳ tooling cụ thể nào. Chỉ cần thay thế các phần cần thiết (ví dụ như test runner), bạn vẫn sẽ tận hưởng được các tiện ích testing có sẵn của Nest.

Gợi ý Các project mới tạo mặc định dùng Vitest. Các testing API mà Nest cung cấp không phụ thuộc vào một test runner cụ thể nào, nên cùng những pattern này vẫn hoạt động tốt với các công cụ khác.

Installation (Cài đặt)

Để bắt đầu, trước tiên hãy cài package cần thiết:

$ npm i --save-dev @nestjs/testing

Unit testing (Kiểm thử đơn vị)

Trong ví dụ sau, ta test hai class: CatsController và CatsService với Vitest. Vitest đóng vai trò vừa là test runner, vừa cung cấp các hàm assert và các test-double utility (công cụ tạo đối tượng giả lập cho test) giúp mocking, spying và stubbing. Trong ví dụ cơ bản dưới đây, ta tự tay khởi tạo (instantiate) các class này, và đảm bảo controller cùng service thực hiện đúng API contract của chúng.

import { vi } from 'vitest';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

describe('CatsController', () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(() => {
    catsService = new CatsService();
    catsController = new CatsController(catsService);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      vi.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});
import { vi } from 'vitest';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

describe('CatsController', () => {
  let catsController;
  let catsService;

  beforeEach(() => {
    catsService = new CatsService();
    catsController = new CatsController(catsService);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      vi.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

Gợi ý Hãy để các file test nằm gần với class mà chúng test. File test nên có hậu tố .spec hoặc .test.

Vì ví dụ trên rất đơn giản, ta thực ra chưa test cái gì đặc thù của Nest cả. Thậm chí ta còn chưa dùng đến dependency injection (chú ý là ta tự truyền một instance của CatsService vào catsController). Kiểu test này — nơi ta tự tay khởi tạo các class được test — thường được gọi là isolated testing (kiểm thử độc lập), vì nó không phụ thuộc vào framework. Hãy cùng tìm hiểu thêm một số khả năng nâng cao hơn, giúp bạn test các ứng dụng sử dụng nhiều tính năng của Nest hơn.

Ghi chú người dịch “Isolated testing” ở đây tương tự việc bạn new trực tiếp một struct ở Go và tự tay truyền các dependency giả vào constructor, thay vì để DI container lo việc đó — nhanh gọn cho unit test đơn giản, nhưng không kiểm chứng được rằng module của bạn wiring đúng trong Nest.

Testing utilities (Công cụ hỗ trợ testing)

Package @nestjs/testing cung cấp một bộ utility giúp quá trình testing chắc chắn (robust) hơn. Hãy viết lại ví dụ trước bằng class Test có sẵn:

import { vi } from 'vitest';
import { Test } from '@nestjs/testing';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

describe('CatsController', () => {
  let catsController: CatsController;
  let catsService: CatsService;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
        controllers: [CatsController],
        providers: [CatsService],
      }).compile();

    catsService = moduleRef.get(CatsService);
    catsController = moduleRef.get(CatsController);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      vi.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});
import { vi } from 'vitest';
import { Test } from '@nestjs/testing';
import { CatsController } from './cats.controller.js';
import { CatsService } from './cats.service.js';

describe('CatsController', () => {
  let catsController;
  let catsService;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
        controllers: [CatsController],
        providers: [CatsService],
      }).compile();

    catsService = moduleRef.get(CatsService);
    catsController = moduleRef.get(CatsController);
  });

  describe('findAll', () => {
    it('should return an array of cats', async () => {
      const result = ['test'];
      vi.spyOn(catsService, 'findAll').mockImplementation(() => result);

      expect(await catsController.findAll()).toBe(result);
    });
  });
});

Test class rất hữu ích vì nó cung cấp một application execution context (ngữ cảnh thực thi ứng dụng) mô phỏng gần như toàn bộ Nest runtime, nhưng đồng thời cho bạn các hook giúp dễ dàng quản lý instance của class, bao gồm cả mocking và overriding. Test class có phương thức createTestingModule(), nhận vào một module metadata object làm argument (giống hệt object bạn truyền cho decorator @Module()). Phương thức này trả về một instance TestingModule, đến lượt nó cung cấp thêm vài phương thức khác. Với unit test, phương thức quan trọng nhất là compile(). Phương thức này bootstrap một module cùng các dependency của nó (tương tự cách một ứng dụng được bootstrap trong file main.ts thông thường bằng NestFactory.create()), và trả về một module đã sẵn sàng để test.

Gợi ý Phương thức compile() là bất đồng bộ (asynchronous) nên phải dùng await. Sau khi module được compile, bạn có thể lấy bất kỳ static instance nào nó khai báo (controller và provider) bằng phương thức get().

TestingModule kế thừa từ class module reference, do đó cũng thừa hưởng khả năng resolve động (dynamically resolve) các scoped provider (transient hoặc request-scoped). Dùng phương thức resolve() để làm việc này (phương thức get() chỉ lấy được static instance).

const moduleRef = await Test.createTestingModule({
  controllers: [CatsController],
  providers: [CatsService],
}).compile();

catsService = await moduleRef.resolve(CatsService);

Cảnh báo Phương thức resolve() trả về một instance duy nhất (unique) của provider, lấy từ DI container sub-tree (cây con của DI container) riêng của nó. Mỗi sub-tree có một context identifier riêng biệt. Vì vậy, nếu bạn gọi phương thức này nhiều lần rồi so sánh tham chiếu instance, bạn sẽ thấy chúng không bằng nhau.

Gợi ý Tìm hiểu thêm về module reference tại đây.

Thay vì dùng phiên bản production của một provider, bạn có thể override nó bằng một custom provider chỉ dùng cho mục đích testing. Ví dụ, bạn có thể mock một database service thay vì kết nối tới database thật. Ta sẽ tìm hiểu về override ở phần sau, nhưng chúng cũng dùng được cho unit test.

Auto mocking (Tự động mock)

Nest cũng cho phép bạn định nghĩa một mock factory để áp dụng cho tất cả dependency còn thiếu (missing dependencies). Điều này hữu ích khi class của bạn có rất nhiều dependency, và việc mock từng cái sẽ tốn nhiều thời gian và công sức thiết lập. Để dùng tính năng này, createTestingModule() cần được chain (nối) tiếp với phương thức useMocker(), truyền vào một factory tạo mock cho các dependency của bạn. Factory này có thể nhận vào một token (không bắt buộc) — là một instance token, bất kỳ token nào hợp lệ đối với một Nest provider — và trả về một mock implementation. Ví dụ dưới đây tạo một mock riêng cho CatsService bằng vi.fn().

import { vi } from 'vitest';

describe('CatsController', () => {
  let controller: CatsController;

  beforeEach(async () => {
    const moduleRef = await Test.createTestingModule({
      controllers: [CatsController],
    })
      .useMocker((token) => {
        const results = ['test1', 'test2'];
        if (token === CatsService) {
          return { findAll: vi.fn().mockResolvedValue(results) };
        }
      })
      .compile();

    controller = moduleRef.get(CatsController);
  });
});

Bạn cũng có thể lấy các mock này ra khỏi testing container giống như cách lấy một custom provider thông thường: moduleRef.get(CatsService).

Gợi ý Bạn cũng có thể truyền trực tiếp một mock factory helper tái sử dụng được, khi muốn chia sẻ chung các test double (đối tượng giả lập) giữa nhiều test suite.

Gợi ý Các provider REQUEST và INQUIRER không thể được auto-mock vì chúng đã được định nghĩa sẵn (pre-defined) trong context. Tuy nhiên, bạn vẫn có thể ghi đè (overwrite) chúng bằng cú pháp custom provider, hoặc dùng phương thức .overrideProvider.

End-to-end testing (Kiểm thử end-to-end)

Khác với unit testing — vốn tập trung vào từng module, class riêng lẻ — end-to-end (e2e) testing bao quát sự tương tác giữa các class và module ở mức tổng thể hơn, gần với kiểu tương tác mà end-user thực sự có với hệ thống production. Khi ứng dụng ngày càng lớn, việc test thủ công hành vi end-to-end của từng API endpoint trở nên khó khăn. Automated end-to-end test giúp ta đảm bảo hành vi tổng thể của hệ thống là đúng đắn và đáp ứng yêu cầu dự án. Để thực hiện e2e test, ta dùng cấu hình tương tự như phần unit testing vừa nói ở trên. Ngoài ra, Nest còn giúp việc dùng thư viện Supertest để mô phỏng HTTP request trở nên dễ dàng.

import request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/cats/cats.module.js';
import { CatsService } from '../../src/cats/cats.service.js';
import { INestApplication } from '@nestjs/common';

describe('Cats', () => {
  let app: INestApplication;
  let catsService = { findAll: () => ['test'] };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [CatsModule],
    })
      .overrideProvider(CatsService)
      .useValue(catsService)
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer())
      .get('/cats')
      .expect(200)
      .expect({
        data: catsService.findAll(),
      });
  });

  afterAll(async () => {
    await app.close();
  });
});
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { CatsModule } from '../../src/cats/cats.module.js';
import { CatsService } from '../../src/cats/cats.service.js';
import { INestApplication } from '@nestjs/common';

describe('Cats', () => {
  let app: INestApplication;
  let catsService = { findAll: () => ['test'] };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [CatsModule],
    })
      .overrideProvider(CatsService)
      .useValue(catsService)
      .compile();

    app = moduleRef.createNestApplication();
    await app.init();
  });

  it(`/GET cats`, () => {
    return request(app.getHttpServer())
      .get('/cats')
      .expect(200)
      .expect({
        data: catsService.findAll(),
      });
  });

  afterAll(async () => {
    await app.close();
  });
});

Gợi ý Nếu bạn dùng Fastify làm HTTP adapter, nó cần một cấu hình hơi khác một chút, và có sẵn khả năng testing riêng:

let app: NestFastifyApplication;

beforeAll(async () => {
  app = moduleRef.createNestApplication<NestFastifyApplication>(
    new FastifyAdapter(),
  );

  await app.init();
  await app.getHttpAdapter().getInstance().ready();
});

it(`/GET cats`, () => {
  return app
    .inject({
      method: 'GET',
      url: '/cats',
    })
    .then((result) => {
      expect(result.statusCode).toEqual(200);
      expect(result.payload).toEqual(/* expectedPayload */);
    });
});

afterAll(async () => {
  await app.close();
});

Trong ví dụ này, ta xây dựng dựa trên một số khái niệm đã mô tả trước đó. Bên cạnh phương thức compile() đã dùng trước đây, giờ ta dùng thêm phương thức createNestApplication() để khởi tạo một Nest runtime environment đầy đủ.

Một điểm cần lưu ý là khi ứng dụng của bạn được compile bằng phương thức compile(), HttpAdapterHost#httpAdapter sẽ là undefined tại thời điểm đó. Lý do là vì chưa có HTTP adapter hay server nào được tạo ra trong giai đoạn compile này. Nếu test của bạn cần đến httpAdapter, bạn nên dùng phương thức createNestApplication() để tạo application instance, hoặc tái cấu trúc (refactor) project để tránh dependency này khi khởi tạo dependency graph.

Được rồi, hãy phân tích ví dụ trên:

Ta lưu tham chiếu tới app đang chạy vào biến app, để dùng nó mô phỏng HTTP request.

Ta mô phỏng HTTP test bằng hàm request() của Supertest. Ta muốn các HTTP request này được định tuyến (route) tới ứng dụng Nest đang chạy, nên ta truyền cho hàm request() một tham chiếu tới HTTP listener nằm bên dưới Nest (mà, đến lượt nó, có thể được cung cấp bởi nền tảng Express). Do đó mới có cấu trúc request(app.getHttpServer()). Lời gọi request() trả cho ta một HTTP Server đã được bọc lại (wrapped), giờ đã kết nối với ứng dụng Nest, cung cấp các phương thức để mô phỏng một HTTP request thực sự. Ví dụ, dùng request(...).get('/cats') sẽ khởi tạo một request tới ứng dụng Nest, giống hệt như một HTTP request thực sự như get '/cats' đến qua network.

Trong ví dụ này, ta cũng cung cấp một implementation thay thế (test-double) cho CatsService, chỉ đơn giản trả về một giá trị hard-code để ta kiểm tra. Dùng overrideProvider() để cung cấp implementation thay thế như vậy. Tương tự, Nest cũng cung cấp các phương thức để override module, guard, interceptor, filter và pipe, lần lượt là overrideModule(), overrideGuard(), overrideInterceptor(), overrideFilter(), và overridePipe().

Mỗi phương thức override (trừ overrideModule()) trả về một object với 3 phương thức khác nhau, tương ứng với những gì đã mô tả cho custom provider:

  • useClass: bạn cung cấp một class sẽ được khởi tạo để tạo ra instance thay thế cho đối tượng bị override (provider, guard, v.v.).
  • useValue: bạn cung cấp một instance dùng để override đối tượng đó.
  • useFactory: bạn cung cấp một hàm trả về instance dùng để override đối tượng đó.

Còn overrideModule() trả về một object với phương thức useModule(), dùng để cung cấp một module thay thế cho module gốc, như sau:

const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideModule(CatsModule)
  .useModule(AlternateCatsModule)
  .compile();

Mỗi loại phương thức override, đến lượt nó, đều trả về instance TestingModule, nên có thể được chain (nối) với các phương thức khác theo fluent style. Bạn nên gọi compile() ở cuối chuỗi chain như vậy, để Nest khởi tạo và initialize module.

Ngoài ra, đôi khi bạn có thể muốn cung cấp một custom logger, ví dụ khi test chạy trên CI server. Dùng phương thức setLogger() và truyền vào một object thỏa mãn interface LoggerService, để chỉ định cho TestModuleBuilder cách log trong lúc test (mặc định, chỉ log loại “error” mới được in ra console).

Module đã được compile có sẵn một số phương thức hữu ích, được mô tả trong bảng dưới đây:

createNestApplication() Tạo và trả về một Nest application (instance INestApplication) dựa trên module đã cho. Lưu ý bạn phải tự khởi tạo (initialize) ứng dụng bằng phương thức init().
createNestMicroservice() Tạo và trả về một Nest microservice (instance INestMicroservice) dựa trên module đã cho.
get() Lấy ra một static instance của controller hoặc provider (kể cả guard, filter, v.v.) có sẵn trong application context. Kế thừa từ class module reference.
resolve() Lấy ra một scoped instance (request hoặc transient) được tạo động của controller hoặc provider (kể cả guard, filter, v.v.) có sẵn trong application context. Kế thừa từ class module reference.
select() Duyệt qua dependency graph của module; có thể dùng để lấy một instance cụ thể từ module đã chọn (dùng cùng với strict mode (strict: true) trong phương thức get()).

Gợi ý Hãy để các file e2e test bên trong thư mục test. File test nên có hậu tố .e2e-spec.

Overriding globally registered enhancers (Override các enhancer đăng ký toàn cục)

Nếu bạn có một guard (hoặc pipe, interceptor, filter) được đăng ký ở phạm vi global (toàn cục), bạn cần thêm vài bước nữa để override enhancer đó. Nhắc lại, đoạn đăng ký gốc trông như sau:

providers: [
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
],

Đây là cách đăng ký guard như một “multi”-provider thông qua token APP_*. Để có thể thay thế JwtAuthGuard ở đây, việc đăng ký cần dùng một provider đã tồn tại sẵn (existing provider) ở vị trí này:

providers: [
  {
    provide: APP_GUARD,
    useExisting: JwtAuthGuard,
    // ^^^^^^^^ notice the use of 'useExisting' instead of 'useClass'
  },
  JwtAuthGuard,
],

Gợi ý Đổi useClass thành useExisting để tham chiếu tới một provider đã đăng ký, thay vì để Nest tự khởi tạo nó đằng sau token.

Giờ JwtAuthGuard đã hiển thị với Nest như một provider thông thường, có thể được override khi tạo TestingModule:

const moduleRef = await Test.createTestingModule({
  imports: [AppModule],
})
  .overrideProvider(JwtAuthGuard)
  .useClass(MockAuthGuard)
  .compile();

Giờ tất cả các test của bạn sẽ dùng MockAuthGuard cho mọi request.

Testing request-scoped instances (Kiểm thử instance có phạm vi request)

Các provider request-scoped (có phạm vi inject là request) được tạo riêng cho mỗi request đến. Instance đó sẽ bị garbage-collect (thu gom rác) sau khi request xử lý xong. Điều này gây ra một vấn đề, vì ta không thể truy cập được dependency injection sub-tree được tạo riêng cho một request đang test.

Ta biết (dựa vào các phần trên) rằng phương thức resolve() có thể dùng để lấy ra một class được khởi tạo động. Cũng như đã mô tả ở đây, ta biết mình có thể truyền một context identifier duy nhất để kiểm soát vòng đời của một DI container sub-tree. Vậy làm sao tận dụng điều này trong bối cảnh testing?

Chiến lược ở đây là tạo trước một context identifier, rồi buộc Nest dùng đúng ID này để tạo sub-tree cho tất cả các request đến. Bằng cách này ta sẽ có thể lấy ra được các instance được tạo cho một request đang test.

Để làm điều này, dùng vi.spyOn() trên ContextIdFactory:

const contextId = ContextIdFactory.create();
vi
  .spyOn(ContextIdFactory, 'getByRequest')
  .mockImplementation(() => contextId);

Giờ ta có thể dùng contextId để truy cập một DI container sub-tree duy nhất, dùng chung cho mọi request tiếp theo.

catsService = await moduleRef.resolve(CatsService, contextId);

Ghi chú người dịch Cơ chế “sub-tree theo context identifier” này khá giống ý tưởng context.Context mang theo request-scoped value ở Go, hoặc request-scoped bean trong Spring: mỗi request có một “nhánh” instance riêng, không dùng chung giữa các request. Trong test, vì không có request thật đi qua, ta phải “giả lập” bằng cách ép ContextIdFactory luôn trả về cùng một contextId đã biết trước, để có thể resolve() đúng instance đó ra kiểm tra.

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.