如何测试JavaScript压缩输出

16
我们最近升级了一个JavaScript缩小库的新版本。
经过测试团队的大量质量保证工作后,发现我们的缩小器的新版本存在问题,会改变一段代码块的意图和含义。
(生活教训:除非你确信需要新版本,否则不要升级JS缩小器。)
该缩小器用于客户端JavaScript代码,重点是DOM相关的活动,而不是“业务逻辑”。
以下是被缩小器升级破坏的简化示例:
function process(count)
{
     var value = ""; 
     value += count; //1. Two consecutive += statements
     value += count;
     count++;        //2. Some other statement
     return value;   //3. Return
}

被错误压缩成以下内容:

function process(n){var t="";return t+n+n,n++,t}

虽然我们可以编写一些单元测试来捕捉一些潜在问题,但是考虑到JavaScript与DOM交互(数据输入等)的重要性,如果没有用户测试(非自动化),很难进行全面测试。我们曾考虑使用JS to AST库(如Esprima),但考虑到可能对缩小代码进行的更改的性质,它将产生太多错误的结果。

我们还考虑尝试编写代表性测试,但这似乎是一项永无止境的任务(且可能会错过某些情况)。

提示:这是一个非常复杂的Web应用程序,有数十万行JavaScript代码。

我们正在寻找一种测试缩小过程的方法,而不是“只是再次彻底测试并重复”。我们希望为该过程应用更多的严谨/科学方法。

理想情况下,如果我们有更好的科学方法来测试,我们可以尝试多个缩小器,而不必担心每个缩小器都会以新的微妙方式破坏我们的代码。

更新:

我们有一个想法:

  1. 使用旧版本进行缩小
  2. 美化
  3. 使用新版本进行缩小
  4. 美化
  5. 进行可视化比较

这似乎是个好主意,但差异非常普遍,差异工具标记了几乎每一行都不同。


3
你使用的是哪个压缩器? - Blender
1
可能可以使用源映射并将压缩后的代码与原始版本进行比较。 - Will Hawker
@WillHawker - 很有趣。假设有人调整了缩小器以生成源映射,那么这不是依赖于源映射的准确性吗?(我很难想象它--即使是我提供的例子也很难解析和与优化/缩小版本进行比较,不是吗?) - WiredPrairie
1
@WiredPrairie,你为什么会相信微软能够正确地处理JavaScript呢?你有使用过Internet Exploder吗?建议使用YUI-Compressor或者Closure Compiler。 - zzzzBov
1
@WiredPrairie,更改不会解决您发布的问题,这就是为什么我发布了评论而不是答案的原因。我的评论更多是让您使用一个做得更好的工具。比较源映射听起来是一个更好的选择。 - zzzzBov
显示剩余9条评论
6个回答

1

你是否考虑过像 QUnitjs 这样的单元测试框架?编写单元测试可能需要一些工作,但最终你会拥有一个可重复的测试流程。


1
这是一个有趣的想法,适用于某些场景 - 因为我们的 Web 应用程序非常丰富,单元测试 JavaScript UI 部分已经变得很困难。您是否有任何评论要添加,关于使用单元测试框架来测试 JS/DOM 交互较重的部分? - WiredPrairie

1
听起来你需要在你的CI(持续集成环境)中开始使用自动化单元测试。QUnit 被提及过,但实际上 QUnit 是一个相当薄弱的测试系统,其断言最少只是基本的(它甚至没有真正使用好的基于断言的语法)。它只勉强符合 TDD,并且对 BDD 的处理也不是很好。
就我个人而言,我建议使用 JasmineJsTestDriver(它可以使用其他 UT 框架或自己的框架,并且非常快...尽管它有一些稳定性问题,我真的希望他们能解决),并设置单元测试,可以通过多个比较来检查缩小处理。
一些比较可能需要包括:
- 原始代码和其功能的预期行为 - 与缩小代码进行比较(这就是 BDD 的作用,期望缩小代码具有相同的功能性能/结果) - 我甚至会更进一步(根据您的缩小方法),并进行测试,然后美化缩小并进行另一个比较(这使您的测试更加健壮,并且更加保证有效性)。
这些测试是为什么你可能会受益于像Jasmine这样的支持BDD的框架,而不是纯TDD(即你发现视觉差异很难处理的结果),因为你正在测试行为、比较和功能/行为的先前/后状态,而不仅仅是a是否为真,并在解析后仍然为真。
设置这些单元测试可能需要一段时间,但是对于那么大的代码库来说,这是一个迭代的过程...快速而早期地测试您的初始关键瓶颈或脆弱点,然后将测试扩展到所有内容(我一直将我的团队设置为从此时起,除非具有单元测试,否则不认为任何东西是完整的并且RC...任何旧的没有单元测试并且必须更新/接触/维护的东西,在接触时必须编写单元测试,以便您不断改进并以更可管理和逻辑的方式缩小未经测试的代码量,同时增加您的代码覆盖率)。
一旦您在CI中运行了单元测试,您就可以将它们与构建过程联系起来:失败的构建没有单元测试,或者当单元测试失败时发送警报,每次检入时进行积极监视等等。使用JSDoc3自动生成文档等等。
你所描述的问题正是CI和单元测试的用武之地,尤其是在你的情况下,这种方法最大限度地减少了代码库大小的影响...大小不会使它更复杂,只会使得测试工作跨全局范围的时间更长。
然后,再结合使用JSDoc3,你就比大多数前端商店的90%都更加出色了。它非常强大且对工程师非常有用,从而实现了自我延续。
我真的可以无限制地谈论这个话题,因为在如何处理它并让团队支持它以及使其自成形和自我延续方面有很多微妙之处,其中最重要的是编写可测试的代码......但从概念层面来说......编写单元测试并自动化它们。始终如此。
太长时间以来,前端开发人员一直在半吊子地开发,没有应用实际的工程严谨性和纪律性。随着前端越来越强大和热门,这种情况必须改变,而且正在改变。对于前端/RIA应用程序而言,经过充分测试、覆盖率高、自动化测试和持续集成的概念是这种变革的巨大需求之一。

0
你可以看一下 Selenium Web Driver 这样的工具,它可以让你在不同的环境中自动化测试 Web 应用程序。还有一些云托管的虚拟机解决方案,可以进行多环境测试,这样当在 Webkit 中运行正常但在 IE 中出现问题时就不会被卡住了。

1
我们今天实际上使用它来进行一些自动化测试。我们的应用程序工作流非常互动,所以这是很多工作。这涵盖了基础知识 - 但是我们正在寻找更深入(和更科学)的东西,理想情况下。 - WiredPrairie

0

你应该一定要考虑使用源代码映射来帮助调试JavaScript的最小化。源代码映射也适用于JavaScript的超集,如CoffeeScript或者我新喜欢的TypeScript。

我使用的是Closure编译器,它不仅可以进行代码最小化,还可以创建源代码映射。更不用说它是最有效率的,并且可以生成最小的文件。最后,你需要了解代码最小化的运作方式并编写兼容的代码,你的示例代码可能需要进行重构。

查看这篇关于源代码映射的文章: http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/

同时,查看Closure编译器的文档,它提供了有关如何为最小化编写更好代码的建议: https://developers.google.com/closure/compiler/


在某些情况下,使用源映射调试可能会很有趣,但我们如何进行所需的比较呢?您认为我们能够轻松地比较两个版本吗?(该源代码只是一个非常简化的示例,最初导致了此问题的代码块。) - WiredPrairie
我认为在你的情况下,需要尽可能多的可见性,因此源代码映射只能起到帮助作用。归根结底,没有什么可以替代测试,在任何大型 Web 应用程序中,你绝对需要使用单元测试、自动化测试(我使用 Canoo Webtest / http://webtest.canoo.com/)和手动测试。至于直接比较你的旧缩小器和新缩小器如何完成它们的工作,我认为没有好的方法可以做到。它们故意采用不同的方式,因此输出将不同,并且在我的看法中,一对一比较有点无意义。 - pmko

0

我们在高级模式下使用闭包编译器,它可以同时进行代码缩小和更改。因此,我们也编译我们的单元测试,所以您可以考虑将您的测试与您的代码一起进行缩小并以这种方式运行。


如果您的测试由于缩小而产生故障,那么谁来测试这些测试呢? - Sammaye
编译器显然有针对它编写的测试,我们想要避免的是人们尝试像 someObj'someFunc' 这样寻址函数,因为这会在高级优化时出现问题,并且这是一种非常常见的 JavaScript 习惯用法。另一件事情(这是闭包特定的全栈问题),在我们的回归测试中,我们希望确保 CSS 名称通过重命名映射进行引用。 - lennel
"闭包编译器"是什么?Google的东西 - undefined
是的,它是一个非常好的JS编译器,但与2011年后出现的JS生态系统不太兼容。如果你在高级模式下使用他们的库和编译器,结果会非常惊人。 - undefined

0

虽然这不是一个测试解决方案,但是您考虑过使用TypeScript来编写像您这样的大型JS应用程序吗?

我已经使用TypeScript和它的默认min引擎进行了测试,结果表明它可以正常工作。

假设您的count参数是一个数字。

TypeScript代码将如下所示:

class ProcessorX {
    ProcessX(count: number): string {
        var value = '';
        value += count.toString();
        value += count.toString();
        count++;
        return value;
    }
}

这将生成类似于以下的js代码:

var ProcessorX = (function () {
    function ProcessorX() { }
    ProcessorX.prototype.ProcessX = function (count) {
        var value = '';
        value += count.toString();
        value += count.toString();
        count++;
        return value;
    };
    return ProcessorX;
})();

然后被压缩为:

var ProcessorX=function(){function n(){}return n.prototype.ProcessX=function(n){var t="";return t+=n.toString(),t+=n.toString(),n++,t},n}()

它在jsfiddle上。

如果你的count是一个string,那么这个fiddle


新的代码压缩工具也不再有这个问题。 - WiredPrairie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接