如何对非导出函数进行单元测试?

Posted

技术标签:

【中文标题】如何对非导出函数进行单元测试?【英文标题】:How can I unit test non-exported functions? 【发布时间】:2019-06-04 13:52:45 【问题描述】:

在一个 javascript ES6 模块中,可能有许多小的、易于测试的函数应该被测试,但不应该被导出。如何在不导出模块的情况下测试模块中的函数? (不使用Rewire)。

【问题讨论】:

如果它们没有被导出,那意味着它们不是public,对吧?我假设在这种情况下,它们被公开导出的函数/类/任何东西使用,因此您可以通过公开可用的内容来测试它们。 @AlexSzabó 是的,但我正在寻找一种直接测试它们的方法。使用它们的公共函数(在我的例子中)是一个复杂的生成器函数,因此很难通过公共函数测试它们 首先,很抱歉我不能以任何有意义的方式帮助你:( - 但是仅仅为了测试而公开私有函数不是一个好的做法。如果函数足够通用,您可以通过显式单元测试公开它们,但如果它们不是,那么您正在使您的测试与实现细节高度耦合,因此它们可以(例如)在重构时中断,而无需任何真正的改变行为。 @AlexSzabó 是的,我完全同意你的看法。感谢您的输入!由于我必须为此项目使用特别复杂的工具,这可能只是我遇到的一个问题。 我也需要想办法做到这一点。通过执行公共方法来测试私有方法的功能并不是一个非常有意义的单元测试。该公共方法可能会调用大量私有方法,当它失败时,您将不知道在哪里或为什么。 【参考方案1】:

也许是 necro-posting,但我解决这个问题的方法是使用“index.js”,它只导出你想要公开的函数。

您仍然需要导出私有函数,但这种方式确实在测试和生产之间增加了一层抽象。

模块/startingFile.js

function privateFunction1() /**/;
function privateFunction2() /**/;

// Different syntax is a good visual indicator that this is different to public function
exports.privateFunction1 = privateFunction1;
exports.privateFunction2 = privateFunction2;

exports.publicFunction1 = function() /**/;
exports.publicFunction2 = function() /**/;

模块/index.js

exports.publicFunction1 = require('./startingFile.js').publicFunction1;
exports.publicFunction2 = require('./startingFile.js').publicFunction2;

ImportingFile.js

const  publicFunction1, publicFunction2  = require('./module');

您甚至可以使用 NODE_ENV 变量仅在不生产时导出私有函数。

【讨论】:

这对于公共模块来说似乎是一个很好的解决方案 :)【参考方案2】:

导出一个“exportedForTesting”常量

function shouldntBeExportedFn()
  // Does stuff that needs to be tested
  // but is not for use outside of this package


export function exportedFn()
  // A function that should be called
  // from code outside of this package and
  // uses other functions in this package


export const exportedForTesting = 
  shouldntBeExportedFn

以下可用于生产代码:

import  exportedFn  from './myPackage';

这可以用于单元测试:

import  exportedFn, exportedForTesting  from './myPackage';
const  shouldntBeExportedFn  = exportedForTesting;

此策略为我团队中的其他开发人员保留了上下文线索,即 shouldntBeExportedFn() 不应在包外使用,除非用于测试。

我已经用了很多年了,我发现它的效果很好。

【讨论】:

恐怕这是唯一的答案。反射类会很方便。允许您导入模块定义文件并访问非导出字段的东西。 这是一个实用的想法,但无论如何它正在导出。如果有条件地导出可能会更好,比如使用process.env.NODE_ENV === 'test' @Kennyhyun,有趣的想法!随着 webpack 的 DefinePlugin 和 UglifyJS 删除 if('test' === 'test') 我认为这可以工作 - 它只需要输出具有***导出。【参考方案3】:

乔丹,我希望我能给你一个更好的答案。 ? 过去我在 JavaScript 和 C# 上下文中都有非常相似的问题...

回答/不回答

在某些时候,我不得不接受这样一个事实,即如果我想要涵盖未导出/私有函数/方法的精细单元测试,我真的应该公开它们。有人会说这是违反封装的,但也有人不同意。前一组人还会说,在导出/公开函数之前,它本质上是一个实现细节,因此不应该进行单元测试。

如果您正在练习 TDD 然后 Mark Seeman's explanation should be relevant (Pluralsight),希望它能够阐明为什么可以公开事物。

我不知道您是否可以找到一些技巧来直接从您的单元测试中调用未导出的函数,而无需更改被测代码,但我个人不会这样做。

只是一种选择

另一种选择是将您的图书馆分成两部分。比如说,A 库是您的应用程序代码,B 库是包含您希望避免从A 的界面导出的所有功能的包。

如果它们是两个不同的库,您可以非常精细地控制公开的内容和测试方式。库A 将仅依赖于B,而不会泄露B 的任何详细信息。然后AB 都可以独立测试。

这当然需要不同的代码组织,但它会起作用。 Lerna 等工具简化了 JavaScript 代码的多包存储库。

旁注

I don't agree with AlexSzabó,说实话。通过测试使用它的函数来测试非导出函数并不是真正的单元测试。

【讨论】:

我同意“私有函数是一个实现细节”,需要测试的不是它们,而是你的公开可用函数、类或你手头的任何东西的行为。就像你说的,当你在做 TDD 时,你有一个打算在其他地方使用的单元,并且你不断添加测试来描述在特定条件下它应该做什么 - 因此测试它的行为,而不是它的内部。 @AlexSzabó 我建议您观看我添加链接的 Mark Seeman 的视频。他解释说,使用 TDD 测试“内部”可能完全没问题。多年来,我越来越多地发现“内部”与“接口”在单元测试的上下文中是非常相关的证据。 你不能在不拼错“answer”的情况下表达它吗?完全不清楚你的意思。

以上是关于如何对非导出函数进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

笑话:当同一个模块也有命名导出时,如何模拟默认导出组件?

如何测试私有而不是导出的函数

如何访问和测试 node.js 模块中的内部(非导出)函数?

检查通过 rewire 可见的(私有/非导出)函数上的方法调用

gtest使用小结

如何在没有评估环境的情况下对函数进行集群导出