kiểm tra đơn vị các chức năng riêng tư với mocha và node.js


131

Tôi đang sử dụng mocha để kiểm tra đơn vị một ứng dụng được viết cho node.js

Tôi tự hỏi nếu nó có thể đơn vị chức năng kiểm tra chưa được xuất khẩu trong một mô-đun.

Thí dụ:

Tôi có rất nhiều hàm được định nghĩa như thế này trong foobar.js

function private_foobar1(){
    ...
}

function private_foobar2(){
    ...
}

và một vài chức năng được xuất dưới dạng công khai:

exports.public_foobar3 = function(){
    ...
}

Trường hợp thử nghiệm được cấu trúc như sau:

describe("private_foobar1", function() {
    it("should do stuff", function(done) {
        var stuff = foobar.private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

Rõ ràng điều này không hoạt động, vì private_foobar1không được xuất khẩu.

Cách chính xác để kiểm tra đơn vị phương pháp riêng là gì? Có mocha có một số phương pháp tích hợp để làm điều đó?


Câu trả lời:


64

Nếu chức năng không được mô-đun xuất ra, nó không thể được gọi bằng mã kiểm tra bên ngoài mô-đun. Đó là do cách JavaScript hoạt động và Mocha không thể tự mình phá vỡ điều này.

Trong một số trường hợp tôi xác định rằng kiểm tra một chức năng riêng tư là điều nên làm, điều tôi đã làm là đặt một số biến môi trường mà mô-đun của tôi kiểm tra để xác định xem nó có chạy trong thiết lập thử nghiệm hay không. Nếu nó chạy trong thiết lập thử nghiệm, thì nó sẽ xuất các chức năng bổ sung mà sau đó tôi có thể gọi trong khi thử nghiệm.

Từ "môi trường" được sử dụng một cách lỏng lẻo ở đây. Nó có thể có nghĩa là kiểm tra process.envhoặc một cái gì đó khác có thể giao tiếp với mô-đun "bạn đang được thử nghiệm ngay bây giờ". Các trường hợp tôi phải làm điều này là trong môi trường RequireJS và tôi đã sử dụng module.configcho mục đích này.


2
Các giá trị xuất có điều kiện dường như không tương thích với các mô-đun ES6. Tôi đang nhận đượcSyntaxError: 'import' and 'export' may only appear at the top level
aij

1
@aij có do xuất khẩu tĩnh ES6 mà bạn không thể sử dụng import, exportbên trong một khối. Cuối cùng, bạn sẽ có thể thực hiện loại điều này trong ES6 với Trình tải hệ thống. Một cách để khắc phục nó bây giờ là sử dụng module.exports = process.env.NODE_ENV === 'production' ? require('prod.js') : require('dev.js')và lưu trữ sự khác biệt về mã es6 của bạn trong các tệp tương ứng.
cchamberlain

2
Tôi đoán rằng nếu bạn có phạm vi bảo hiểm đầy đủ, thì bạn đang kiểm tra tất cả các chức năng riêng tư của mình, cho dù bạn có tiếp xúc với chúng hay không.
Ziggy

1
@aij Bạn có thể xuất điều kiện ... xem câu trả lời này: stackoverflow.com/questions/39583958/NH
RayLovless

187

Kiểm tra các mô-đun tua lại . Nó cho phép bạn có được (và thao tác) các biến và hàm riêng trong một mô-đun.

Vì vậy, trong trường hợp của bạn, việc sử dụng sẽ là một cái gì đó như:

var rewire = require('rewire'),
    foobar = rewire('./foobar'); // Bring your module in with rewire

describe("private_foobar1", function() {

    // Use the special '__get__' accessor to get your private function.
    var private_foobar1 = foobar.__get__('private_foobar1');

    it("should do stuff", function(done) {
        var stuff = private_foobar1(filter);
        should(stuff).be.ok;
        should(stuff).....

3
@Jaro Hầu hết mã của tôi ở dạng mô-đun AMD, không thể xử lý việc tua lại (vì mô-đun AMD là chức năng nhưng tua lại không thể xử lý "các biến trong hàm"). Hoặc được phiên mã, một kịch bản khác mà tua lại không thể xử lý. Trên thực tế, những người sẽ xem xét tua lại sẽ làm tốt để đọc những hạn chế (được liên kết trước đó) trước khi họ cố gắng sử dụng nó. Tôi không có một ứng dụng duy nhất mà a) cần xuất nội dung "riêng tư" và b) không bị giới hạn trong việc tua lại.
Louis

1
Chỉ cần một điểm nhỏ, phạm vi bảo hiểm mã có thể không nhận các bài kiểm tra được viết như thế này. Ít nhất đó là những gì tôi đã thấy bằng cách sử dụng công cụ bảo hiểm được xây dựng sẵn của Jest.
Mike Stead

Phần thưởng cũng không chơi tốt với công cụ tự động chế giễu của jest. Tôi vẫn đang tìm cách để tận dụng lợi ích của jest và truy cập vào một số lọ riêng.
btburton42

Vì vậy, tôi đã cố gắng để làm cho công việc này nhưng tôi đang sử dụng bản in, mà tôi đoán là gây ra vấn đề này. Về cơ bản tôi nhận được lỗi sau : Cannot find module '../../package' from 'node.js'. Có ai quen với điều này không?
clu

tua lại đang hoạt động tốt .ts, typescripttôi chạy bằng ts-node @clu
muthukumar selvaraj

24

Đây là một quy trình làm việc thực sự tốt để kiểm tra các phương pháp riêng tư của bạn được giải thích bởi Philip Walton, một kỹ sư của Google trên blog của mình.

Nguyên tắc

  • Viết mã của bạn bình thường
  • Liên kết các phương thức riêng tư của bạn với đối tượng trong một khối mã riêng biệt, đánh dấu nó bằng một _ví dụ
  • Bao quanh khối mã đó bằng cách bắt đầu và kết thúc nhận xét

Sau đó, sử dụng một tác vụ xây dựng hoặc hệ thống xây dựng của riêng bạn (ví dụ mã grunt-dải mã) để loại bỏ khối này cho các bản dựng sản xuất.

Bản dựng thử nghiệm của bạn có quyền truy cập vào api riêng của bạn và bản dựng sản xuất của bạn thì không.

Đoạn trích

Viết mã của bạn như thế này:

var myModule = (function() {

  function foo() {
    // private function `foo` inside closure
    return "foo"
  }

  var api = {
    bar: function() {
      // public function `bar` returned from closure
      return "bar"
    }
  }

  /* test-code */
  api._foo = foo
  /* end-test-code */

  return api
}())

Và những nhiệm vụ khó khăn của bạn như thế

grunt.registerTask("test", [
  "concat",
  "jshint",
  "jasmine"
])
grunt.registerTask("deploy", [
  "concat",
  "strip-code",
  "jshint",
  "uglify"
])

Sau hon

Trong một bài viết sau , nó giải thích "tại sao" của "thử nghiệm các phương thức riêng tư"


1
Cũng tìm thấy một plugin webkit có vẻ như nó có thể hỗ trợ một quy trình công việc tương tự: webpack-dải-block
JRulle

21

Nếu bạn muốn giữ cho nó đơn giản, chỉ cần xuất các thành viên riêng tư, nhưng tách biệt rõ ràng với API công khai với một số quy ước, ví dụ: tiền tố chúng với một _hoặc lồng chúng dưới một đối tượng riêng tư .

var privateWorker = function() {
    return 1
}

var doSomething = function() {
    return privateWorker()
}

module.exports = {
    doSomething: doSomething,
    _privateWorker: privateWorker
}

7
Tôi đã thực hiện điều này trong trường hợp toàn bộ mô-đun thực sự là riêng tư và không dành cho tiêu dùng chung. Nhưng đối với các mô-đun có mục đích chung, tôi thích chỉ hiển thị những gì tôi cần để kiểm tra chỉ khi mã đang được thử nghiệm. Đúng là cuối cùng không có gì ngăn cản ai đó truy cập vào các mục riêng tư bằng cách giả mạo môi trường thử nghiệm nhưng khi một người đang thực hiện gỡ lỗi trên ứng dụng của riêng họ, tôi muốn họ không nhìn thấy các biểu tượng không cần phải có một phần của API công khai. Bằng cách này, không có sự cám dỗ ngay lập tức để lạm dụng API cho các mục đích mà nó không được thiết kế cho.
Louis

2
bạn cũng có thể sử dụng cú pháp lồng nhau {... private : {worker: worker}}
Jason

2
Nếu mô-đun là tất cả các chức năng thuần túy, thì tôi thấy không có nhược điểm nào để làm điều này. Nếu bạn đang giữ và biến đổi trạng thái, thì hãy cẩn thận ...
Ziggy

5

Tôi đã thực hiện một gói npm cho mục đích này mà bạn có thể thấy hữu ích: request-from

Về cơ bản, bạn phơi bày các phương pháp không công khai bằng cách:

module.testExports = {
    private_foobar1: private_foobar1,
    private_foobar2: private_foobar2,
    ...
}

lưu ý: testExports có thể là bất kỳ tên hợp lệ nào bạn muốn, ngoại trừ exportstất nhiên.

Và từ một mô-đun khác:

var requireFrom = require('require-from');
var private_foobar1 = requireFrom('testExports', './path-to-module').private_foobar1;

1
Tôi thấy không có lợi thế thực tế cho phương pháp này. Nó không làm cho các biểu tượng "riêng tư" trở nên riêng tư hơn. (Bất kỳ ai cũng có thể gọi requireFromvới các tham số phù hợp.) Ngoài ra, nếu mô-đun textExportsđược tải bởi một requirecuộc gọi trước khi requireFrom tải, requireFromsẽ trả về undefined. (Tôi vừa mới thử nghiệm.) Mặc dù thường có thể kiểm soát thứ tự tải của các mô-đun, nhưng nó không phải lúc nào cũng thực tế. (Bằng chứng là một số câu hỏi Mocha về SO.) Giải pháp này thường không hoạt động với các mô-đun kiểu AMD. (Tôi tải các mô-đun AMD trong Node hàng ngày để thử nghiệm.)
Louis

Nó không hoạt động với các mô-đun AMD! Node.js sử dụng common.js và nếu bạn thay đổi nó để sử dụng AMD, thì bạn đang làm điều đó vượt quá định mức.
jemiloii

@JemiloII Hàng trăm nhà phát triển sử dụng Node.js hàng ngày để kiểm tra các mô-đun AMD. Không có gì "ngoài định mức" khi làm điều đó. Điều bạn có thể nói nhiều nhất là Node.js không đi kèm với trình tải AMD nhưng điều này không nói nhiều, vì Node cung cấp các móc rõ ràng để mở rộng trình tải của nó để tải bất kỳ định dạng nào mà các nhà phát triển quan tâm để phát triển.
Louis

Nó nằm ngoài định mức. Nếu bạn phải bao gồm một trình tải amd theo cách thủ công, thì đó không phải là tiêu chuẩn cho node.js. Tôi hiếm khi thấy AMD cho mã node.js. Tôi sẽ thấy nó cho trình duyệt, nhưng nút. Không. Tôi không nói rằng nó không được thực hiện, chỉ là câu hỏi và câu trả lời này chúng tôi đang bình luận, không nói gì về các mô-đun amd. Vì vậy, không có ai tuyên bố rằng họ đang sử dụng trình tải amd, xuất nút, không nên làm việc với amd. Mặc dù tôi muốn lưu ý, nhưng Commonjs có thể đang trên đường xuất khẩu es6. Tôi chỉ hy vọng rằng một ngày nào đó tất cả chúng ta có thể chỉ sử dụng một phương thức xuất khẩu.
jemiloii

4

Tôi đã thêm một hàm bổ sung mà tôi đặt tên là Internal () và trả về tất cả các hàm riêng tư từ đó. Hàm Internal () này sau đó được xuất. Thí dụ:

function Internal () {
  return { Private_Function1, Private_Function2, Private_Function2}
}

// Exports --------------------------
module.exports = { PublicFunction1, PublicFunction2, Internal }

Bạn có thể gọi các chức năng nội bộ như thế này:

let test = require('.....')
test.Internal().Private_Function1()

Tôi thích giải pháp này nhất vì:

  • chỉ có một hàm Internal () luôn được xuất. Hàm Internal () này luôn được sử dụng để kiểm tra các hàm riêng.
  • Thật đơn giản để thực hiện
  • Tác động thấp đến mã sản xuất (chỉ có một chức năng bổ sung)

2

Tôi đã làm theo câu trả lời của @barwin và kiểm tra cách kiểm tra đơn vị có thể được thực hiện với mô đun tua lại . Tôi có thể xác nhận rằng giải pháp này chỉ đơn giản là hoạt động.

Các mô-đun nên được yêu cầu trong hai phần - một phần công cộng và một phần riêng tư. Đối với các chức năng công cộng, bạn có thể làm điều đó theo cách tiêu chuẩn:

const { public_foobar3 } = require('./foobar');

Đối với phạm vi riêng tư:

const privateFoobar = require('rewire')('./foobar');
const private_foobar1 = privateFoobar .__get__('private_foobar1');
const private_foobar2 = privateFoobar .__get__('private_foobar2');

Để biết thêm về chủ đề này, tôi đã tạo một ví dụ hoạt động với thử nghiệm mô-đun đầy đủ, thử nghiệm bao gồm phạm vi riêng tư và công khai.

Để biết thêm thông tin, tôi khuyến khích bạn kiểm tra bài viết ( https://medium.com/@macsikora/how-to-test-private-fifts-of-es6-module-fb8c1345b25f ) mô tả đầy đủ chủ đề, nó bao gồm các mẫu mã.


2

Tôi biết rằng đây không nhất thiết là câu trả lời mà bạn đang tìm kiếm, nhưng điều tôi đã tìm thấy là hầu hết thời gian nếu một chức năng riêng tư đáng để thử nghiệm, nó đáng để nằm trong tệp riêng của nó.

Ví dụ, thay vì có các phương thức riêng tư trong cùng một tệp với các phương thức công khai, như thế này ...

src / điều / PublicInterface.js


function helper1 (x) {
    return 2 * x;
}

function helper2 (x) {
    return 3 * x;
}

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

... bạn chia nó ra như thế này:

src / điều / PublicInterface.js

import {helper1} from './internal/helper1.js';
import {helper2} from './internal/helper2.js';

export function publicMethod1(x) {
    return helper1(x);
}

export function publicMethod2(x) {
    return helper1(x) + helper2(x);
}

src / điều / nội bộ / helper1.js

export function helper1 (x) {
    return 2 * x;
}

src / điều / nội bộ / helper2.js

export function helper2 (x) {
    return 3 * x;
}

Bằng cách đó, bạn có thể dễ dàng kiểm tra helper1và thực tế helper2, mà không cần sử dụng Rewire và "ma thuật" khác (mà tôi đã tìm thấy, có những điểm đau riêng trong khi gỡ lỗi hoặc khi bạn cố gắng tiến về TypeScript, không đề cập đến việc kém hơn dễ hiểu cho đồng nghiệp mới). Và chúng nằm trong một thư mục con được gọi internal, hoặc một cái gì đó tương tự, sẽ giúp tránh việc sử dụng chúng vô tình ở những nơi ngoài ý muốn.


PS: Một vấn đề thường gặp với các phương pháp "tư nhân" ở đây là nếu bạn muốn thử nghiệm publicMethod1publicMethod2và nhạo báng những người giúp việc, một lần nữa, bạn thường cần một cái gì đó giống như ReWire để làm điều đó. Tuy nhiên, nếu chúng nằm trong các tệp riêng biệt, bạn có thể sử dụng Proxyquire để thực hiện, không giống như Rewire, không cần bất kỳ thay đổi nào đối với quy trình xây dựng của bạn, dễ đọc và gỡ lỗi và hoạt động tốt ngay cả với TypeScript.


1

Để làm cho các phương thức riêng tư có sẵn để thử nghiệm, tôi làm điều này:

const _myPrivateMethod: () => {};

const methods = {
    myPublicMethod1: () => {},
    myPublicMethod2: () => {},
}

if (process.env.NODE_ENV === 'test') {
    methods._myPrivateMethod = _myPrivateMethod;
}

module.exports = methods;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.