译注:这是从 QUnit 官网上摘录的一篇关于如何利用 QUnit 进行单元测试的入门级文档。文章从最初的示例源代码开始,通过逐步分析、重构,最终实现了适应 QUnit 框架的可扩展的新代码,其演变过程与重构思路值得借鉴,因此决定试着翻译一下加深印象,同时也顺带熟悉 Markdown 编辑风格。由于水平有限,翻译不妥的地方,还望不吝赐教,共同进步。

单元测试简介 | QUnit


文章目录

  • 单元测试简介 | QUnit
    • 构建单元测试
    • 让代码可测
    • 重构,阶段0
    • JavaScript 的 QUnit 测试套件
    • 重构,阶段1
    • 测试 DOM 操作
    • 重构,阶段2
    • 回到最初的例子
    • 结语

你可能知道代码有单元测试是件好事,但对客户端代码进行单元测试所遇到的第一个拦路虎,就是缺少所谓的单元JavaScript 代码可能遍布某应用的各网页、各模块,并且很可能与后台业务逻辑、相关 HTML 紧密混合。最糟糕的情况,是代码以内联事件函数的方式完全与 HTML 混为一谈。

这种情况常见于手头缺少现成的处理 DOM 的 JS 库的场合。相较于调用 DOM 的 API 来绑定事件函数,编写内联代码要容易得多。越来越多的开发人员使用 jQuery 这样的库来操作 DOM,以便将这些内联代码抽取为独立的脚本,放到页面某个固定位置,甚至是放到单独的 JavaScript 文件中来引用。然而,这些经过处理的代码还不能被视为一个可供测试的单元。

那么单元究竟指什么?最理想的情况,单元是一个某种意义上的纯函数 —— 对给定输入始终输出同一结果的函数。纯函数的单元测试非常容易,但需要花大部分时间在消除其不良影响上。这里的不良影响特指 DOM 操作。此外,纯函数也有助于弄清将代码重构成哪些单元并构建相应的测试用例。

构建单元测试

有了前面的知识储备,着手进行单元测试就比完全从零开始容易得多了,但这不是本文的重点。这篇文章旨在处理更难的问题:抽取既有代码并测试其重点部分,暴露并调试代码中的潜在漏洞。

像这样,在不修改当前行为的情况下,提取代码并将其转换为其他形式的过程,叫做重构。重构是完善程序代码设计的一种绝佳手段,鉴于代码的任何改动都可能改变程序的实际行为,最保险的做法是在测试阶段进行重构。

这类鸡生蛋蛋生鸡的问题,意味着在现有代码上加入测试,就不得不承担相应的破坏性风险。为了将这样的风险降至最低,在对单元测试有很好的了解之前,还是有必要继续手动测试。

至此,理论就介绍得差不多了,来看一个实际的例子,测试某段与页面内容联系并混杂在一起的 JavaScript 代码。这段代码检索带 title 属性的链接,并将 title 属性值用于显示,相对于某个特定时间,某内容是在何时发布的:

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Mangled date examples</title><script>function prettyDate(time){var date = new Date(time || ""),diff = (((new Date()).getTime() - date.getTime()) / 1000),day_diff = Math.floor(diff / 86400);if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )return;return day_diff == 0 && (diff < 60 && "just now" ||diff < 120 && "1 minute ago" ||diff < 3600 && Math.floor( diff / 60 ) +" minutes ago" ||diff < 7200 && "1 hour ago" ||diff < 86400 && Math.floor( diff / 3600 ) +" hours ago") ||day_diff == 1 && "Yesterday" ||day_diff < 7 && day_diff + " days ago" ||day_diff < 31 && Math.ceil( day_diff / 7 ) +" weeks ago";}window.onload = function() {var links = document.getElementsByTagName("a");for ( var i = 0; i < links.length; i++ ) {if ( links[i].title ) {var date = prettyDate(links[i].title);if ( date ) {links[i].innerHTML = date;}}}};</script>
</head>
<body><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><!-- more list items -->
</ul></body>
</html>

若运行这个示例,会看到一个问题:没有任何日期被替换,尽管代码是有效的。它遍历了页面上的所有锚标记并逐一检查其 title 属性值,若存在,就传给 prettyDate 函数执行。如果 prettyDate 进一步返回一个值,就把这个结果值更新到该链接的 innerHTML 中。

让代码可测

问题出在对超过 31 天的日期,prettyDate 返回 undefined (通过一个单一的 return 语句隐式返回),这样就保留了原来锚点的文本。来看看硬编码一个“当前”日期,执行情况如何:

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Mangled date examples</title><script>function prettyDate(now, time){var date = new Date(time || ""),diff = (((new Date(now)).getTime() - date.getTime()) / 1000),day_diff = Math.floor(diff / 86400);if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )return;return day_diff == 0 && (diff < 60 && "just now" ||diff < 120 && "1 minute ago" ||diff < 3600 && Math.floor( diff / 60 ) +" minutes ago" ||diff < 7200 && "1 hour ago" ||diff < 86400 && Math.floor( diff / 3600 ) +" hours ago") ||day_diff == 1 && "Yesterday" ||day_diff < 7 && day_diff + " days ago" ||day_diff < 31 && Math.ceil( day_diff / 7 ) +" weeks ago";}window.onload = function() {var links = document.getElementsByTagName("a");for ( var i = 0; i < links.length; i++ ) {if ( links[i].title ) {var date = prettyDate("2008-01-28T22:25:00Z",links[i].title);if ( date ) {links[i].innerHTML = date;}}}};</script>
</head>
<body><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><!-- more list items -->
</ul></body>
</html>
  • 运行示例

可以看到,链接会显示“2 hours ago”,“Yesterday” 等字样。但这仍然不是可供测试的单元,在没有对代码作进一步改动的情况下,只能对 DOM 的变动情况进行测试。即便这样可行,任一标记上的改动都可能打断测试,这样的测试事倍功半。

重构,阶段0

相反地,我们来重构这段代码,使其仅仅足以运行单元测试。

需要做两件事:传入一个当前时间给 prettyDate 函数作参数,而不是用 new Date;再将函数抽取到一个独立文件中,以便在一个单独的页面引入该文件进行单元测试。

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Refactored date examples</title><script src="prettydate.js"></script><script>window.onload = function() {var links = document.getElementsByTagName("a");for ( var i = 0; i < links.length; i++ ) {if ( links[i].title ) {var date = prettyDate("2008-01-28T22:25:00Z",links[i].title);if ( date ) {links[i].innerHTML = date;}}}};</script>
</head>
<body><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><!-- more list items -->
</ul></body>
</html>

prettydate.js 内容如下:

function prettyDate(now, time){var date = new Date(time || ""),diff = (((new Date(now)).getTime() - date.getTime()) / 1000),day_diff = Math.floor(diff / 86400);if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )return;return day_diff == 0 && (diff < 60 && "just now" ||diff < 120 && "1 minute ago" ||diff < 3600 && Math.floor( diff / 60 ) +" minutes ago" ||diff < 7200 && "1 hour ago" ||diff < 86400 && Math.floor( diff / 3600 ) +" hours ago") ||day_diff == 1 && "Yesterday" ||day_diff < 7 && day_diff + " days ago" ||day_diff < 31 && Math.ceil( day_diff / 7 ) +" weeks ago";
}
  • 运行示例

有了可以测试的代码单元,就可以写一些实际的单元测试用例了:

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Refactored date examples</title><script src="prettydate.js"></script><script>function test(then, expected) {results.total++;var result = prettyDate("2008/01/28 22:25:00", then);if (result !== expected) {results.bad++;console.log("Expected " + expected +", but was " + result);}}var results = {total: 0,bad: 0};test("2008/01/28 22:24:30", "just now");test("2008/01/28 22:23:30", "1 minute ago");test("2008/01/28 21:23:30", "1 hour ago");test("2008/01/27 22:23:30", "Yesterday");test("2008/01/26 22:23:30", "2 days ago");test("2007/01/26 22:23:30", undefined);console.log("Of " + results.total + " tests, " +results.bad + " failed, " +(results.total - results.bad) + " passed.");</script>
</head>
<body></body>
</html>
  • 运行示例 (先确认启用类似 Firebug 或 Chrome 的 Web Inspector 这样的 console 控制台)

上述示例将创建一个随机测试框架,仅使用控制台作输出。由于不依赖 DOM,可以将代码放入文件中的 script 标签直接在无浏览器的 JavaScript 环境中运行,如 Node.jsRhino

若测试失败,控制台会输出测试的期望值和实际值,最后给出一段测试小结,显示测试总数,失败总数和通过总数。

如果通过所有测试,控制台会看到如下结果:

Of 6 tests, 0 failed, 6 passed.

可以改动部分内容,来看看断言失败的情况:

Expected 2 day ago, but was 2 days ago.
Of 6 tests, 1 failed, 5 passed.

这段随机测试代码旨在进行概念验证(proof of concept),你也可以再写一些代码作测试程序运行,但更实际的做法是选用能提供更好的输出、预设更多的基础环境的现成的单元测试框架。

JavaScript 的 QUnit 测试套件

测试框架的选取通常因人而异。本文剩余篇幅将使用 QUnit(读作 “q-unit”),因其描述测试的风格与文中的随机测试框架更为吻合。

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Refactored date examples</title><link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css"><script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script><script src="prettydate.js"></script><script>QUnit.test("prettydate basics", function( assert ) {var now = "2008/01/28 22:25:00";assert.equal(prettyDate(now, "2008/01/28 22:24:30"), "just now");assert.equal(prettyDate(now, "2008/01/28 22:23:30"), "1 minute ago");assert.equal(prettyDate(now, "2008/01/28 21:23:30"), "1 hour ago");assert.equal(prettyDate(now, "2008/01/27 22:23:30"), "Yesterday");assert.equal(prettyDate(now, "2008/01/26 22:23:30"), "2 days ago");assert.equal(prettyDate(now, "2007/01/26 22:23:30"), undefined);});</script>
</head>
<body><div id="qunit"></div></body>
</html>
  • 运行示例

这里有三处值得留意。

一是在常规 HTML 样板外引入的三个文件:其中两个是与 QUnit 相关的(qunit.cssqunit.js),以及之前的 prettydate.js

再者,引入了新的脚本代码块,调用了一次 test 方法,传入一个字符串作第一参数(为本次测试命名)、一个函数作第二参数。该函数具体执行本次测试代码。测试代码先定义了一个变量 now,便于下文重用,然后用不同的参数多次调用了 equal 方法。equal 方法是 QUnit 内置的一个断言方法,通过测试代码块中、回调函数的第一个参数进行调用。equal 方法的第一个参数,是 prettyDate 函数的执行结果,该函数的第一个参数是变量 now,第二个参数是一个字符串 dateequal 方法的第二个参数是期望结果,如果 equal 的两个参数是同一个值,则断言通过,否则断言失败。

最后,是 body 元素中与 QUnit 相关的标记。这些元素是可选的,引入后,QUnit 会将测试结果写入这些标记。

测试结果如下:

若测试未通过,会得到类似下面的运行结果:

由于包含断言失败的测试用例,QUnit 不会收起该用例的测试结果,以便立即查看出错信息。除了显示期望值与实际值,还可以看到两者的差异 diff,适用于比较更长的字符串。本示例中的出错信息一目了解。

重构,阶段1

上述断言部分还不太完整,因为漏测了 n weeks ago(n 周前)的情况。补充完整前,应考虑再次重构测试用例代码。注意到在每例断言中调用了 prettyDate 函数并传入参数 now。因此可以将其重构到一个自定义断言方法中:

QUnit.test("prettydate basics", function( assert ) {function date(then, expected) {assert.equal(prettyDate("2008/01/28 22:25:00", then), expected);}date("2008/01/28 22:24:30", "just now");date("2008/01/28 22:23:30", "1 minute ago");date("2008/01/28 21:23:30", "1 hour ago");date("2008/01/27 22:23:30", "Yesterday");date("2008/01/26 22:23:30", "2 days ago");date("2007/01/26 22:23:30", undefined);
});
  • 运行示例

这里将 prettyDate 函数的调用提取到自定义的 date 函数。该函数内置了变量 now。最终,各断言仅用到了相关的数据,可读性更强;同时底层抽象的逻辑也很清晰。

测试 DOM 操作

prettyDate 函数测得差不多了,再回到先前的例子。除了 prettyDate 函数,源代码还通过 window加载事件选取了 DOM 元素并更新了元素内容。这部分代码也能用与之前相同的原则进行重构并测试。这里将为这两个函数引入一个模块,以免混淆全局命名空间,同时也可以给每个函数起一个更有意义的名称。

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Refactored date examples</title><link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css"><script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script><script src="prettydate2.js"></script><script>QUnit.test("prettydate.format", function( assert ) {function date(then, expected) {assert.equal(prettyDate.format("2008/01/28 22:25:00", then),expected);}date("2008/01/28 22:24:30", "just now");date("2008/01/28 22:23:30", "1 minute ago");date("2008/01/28 21:23:30", "1 hour ago");date("2008/01/27 22:23:30", "Yesterday");date("2008/01/26 22:23:30", "2 days ago");date("2007/01/26 22:23:30", undefined);});QUnit.test("prettyDate.update", function( assert ) {var links = document.getElementById("qunit-fixture").getElementsByTagName("a");assert.equal(links[0].innerHTML, "January 28th, 2008");assert.equal(links[2].innerHTML, "January 27th, 2008");prettyDate.update("2008-01-28T22:25:00Z");assert.equal(links[0].innerHTML, "2 hours ago");assert.equal(links[2].innerHTML, "Yesterday");});QUnit.test("prettyDate.update, one day later", function( assert ) {var links = document.getElementById("qunit-fixture").getElementsByTagName("a");assert.equal(links[0].innerHTML, "January 28th, 2008");assert.equal(links[2].innerHTML, "January 27th, 2008");prettyDate.update("2008/01/29 22:25:00");assert.equal(links[0].innerHTML, "Yesterday");assert.equal(links[2].innerHTML, "2 days ago");});</script>
</head>
<body><div id="qunit"></div>
<div id="qunit-fixture"><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li>
</ul></div></body>
</html>

prettydate2.js 内容如下:

var prettyDate = {format: function(now, time){var date = new Date(time || ""),diff = (((new Date(now)).getTime() - date.getTime()) / 1000),day_diff = Math.floor(diff / 86400);if ( isNaN(day_diff) || day_diff < 0 || day_diff >= 31 )return;return day_diff === 0 && (diff < 60 && "just now" ||diff < 120 && "1 minute ago" ||diff < 3600 && Math.floor( diff / 60 ) +" minutes ago" ||diff < 7200 && "1 hour ago" ||diff < 86400 && Math.floor( diff / 3600 ) +" hours ago") ||day_diff === 1 && "Yesterday" ||day_diff < 7 && day_diff + " days ago" ||day_diff < 31 && Math.ceil( day_diff / 7 ) +" weeks ago";},update: function(now) {var links = document.getElementsByTagName("a");for ( var i = 0; i < links.length; i++ ) {if ( links[i].title ) {var date = prettyDate.format(now, links[i].title);if ( date ) {links[i].innerHTML = date;}}}}
};
  • 运行示例

新函数 prettyDate.update 是对原例的提取,并带了一个参数 now 以供内部 prettyDate.format 调用。这个基于 QUnit 的测试用例先选取了 #qunit-fixture 元素内所有 a 元素。执行更新后,body 元素内的 <div id="qunit-fixture">…</div> 也更新了,里面包含了抽取自原例的内容,以便进行更多有用的测试。将其放入 #qunit-fixture 元素后,不必担心某次测试操作完 DOM 引起的变动对另一次测试的影响,因为 QUnit 会在每次测试完成后自动重置标记中的内容。

考察对 prettyDate.update 的第一次测试。选中锚点后执行的两个断言用于验证它们的初始文本值,然后调用 prettyDate.update,传入一个固定的日期(同前例)。接着又执行了两次断言,此时验证这些元素中的 innerHTML 属性值分别是变更后的格式化日期:“2 hours ago” 与“Yesterday”。

重构,阶段2

另一个对 prettyDate.update, one day later 的测试,大同小异,只是传入了一个不同于 prettyDate.update 的日期,由此得到两个不同的结果。让我们看看是否可以重构这些测试用例来消除代码上的重复。

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Refactored date examples</title><link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css"><script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script><script src="prettydate2.js"></script><script>QUnit.test("prettydate.format", function( assert ) {function date(then, expected) {assert.equal(prettyDate.format("2008/01/28 22:25:00", then),expected);}date("2008/01/28 22:24:30", "just now");date("2008/01/28 22:23:30", "1 minute ago");date("2008/01/28 21:23:30", "1 hour ago");date("2008/01/27 22:23:30", "Yesterday");date("2008/01/26 22:23:30", "2 days ago");date("2007/01/26 22:23:30", undefined);});function domtest(name, now, first, second) {QUnit.test(name, function( assert ) {var links = document.getElementById("qunit-fixture").getElementsByTagName("a");assert.equal(links[0].innerHTML, "January 28th, 2008");assert.equal(links[2].innerHTML, "January 27th, 2008");prettyDate.update(now);assert.equal(links[0].innerHTML, first);assert.equal(links[2].innerHTML, second);});}domtest("prettyDate.update", "2008-01-28T22:25:00Z", "2 hours ago", "Yesterday");domtest("prettyDate.update, one day later", "2008/01/29 22:25:00", "Yesterday", "2 days ago");</script>
</head>
<body><div id="qunit"></div>
<div id="qunit-fixture"><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z">January 28th, 2008</a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-27T22:24:17Z">January 27th, 2008</a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li>
</ul></div></body>
</html>
  • 运行示例

至此,出现一个新函数 domtest,它封装了前两次 test 方法的调用,引入了测试名称、日期字符串,以及两个期望值等参数,然后被调用了两次。

回到最初的例子

回到最开始介绍的源代码中,看看重构之后的样子。

<!doctype html>
<html>
<head><meta charset="utf-8"><title>Final date examples</title><script src="prettydate2.js"></script><script>window.onload = function() {prettyDate.update("2008-01-28T22:25:00Z");};</script>
</head>
<body><ul><li class="entry"><p>blah blah blah...</p><small class="extra">Posted <span class="time"><a href="#2008/01/blah/57/" title="2008-01-28T20:24:17Z"><span>January 28th, 2008</span></a></span>by <span class="author"><a href="#john/">John Resig</a></span></small></li><!-- more list items -->
</ul></body>
</html>
  • 运行示例

作为非静态的示例,还应该移除 prettyDate.update 方法的参数。总而言之,重构较最初的示例有了很大的改进。借助引入的 prettyDate 模块,可以在不破坏全局命名空间的情况下添加更多的测试功能。

结语

测试 JavaScript 代码不仅仅是使用某个测试程序和编写几个测试用例的问题。在对以往手动测试的源代码执行单元测试时,通常需要作一些重大的结构性变更。我们已经介绍了一个示例,演示了如何变更现有模块的代码结构,以便利用一个随机测试框架来运行一些测试用例,然后将其替换为更全面的框架,以期更实用的视觉呈现。

QUnit 还有更多功能有待发掘,如支持对超时、AJAX 及事件处理等异步代码的测试。其可视化的测试程序有助于代码调试,以便重新运行特定的测试,并为失败的断言和捕获的异常提供堆栈跟踪信息。更多详情,参考 QUnit Cookbook.


本文最早发表于 Smashing Magazine,2012 年 6 月

QUnit 单元测试简介相关推荐

  1. Lua Busted 单元测试简介(Windows 环境)

    简介 本文目标是在 Windows 环境下使用 Busted 进行 Lua 单元测试. Busted 是一款 BDD 风格的 Lua 单元测试框架,支持 TAP 风格输出. 环境 Lua 5.3.5 ...

  2. python单元测试简介

    您可能知道测试很好,但是在尝试为客户端代码编写单元测试时要克服的第一个障碍是缺少任何实际的单元.JavaScript代码是为网站的每个页面或应用程序的每个模块编写的,并与后端逻辑和相关的HTML紧密混 ...

  3. 单元测试 python_Python单元测试简介

    单元测试 python You just finished writing a piece of code and you are wondering what to do. Will you sub ...

  4. Android 单元测试-简介

    官方文档对单元测试有比较详细的介绍,这里不再赘述,可直接查看官方文档: 测试基础知识 | Android 开发者 | Android Developers (google.cn): Android 单 ...

  5. Node.js 单元测试:我要写测试 - Mocha - Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率

    -------------------------------------- 单元测试Express/NodeJs 个人理解, 1,如果不是测试http请求的单元测试,用Mocha, Chai等基本够 ...

  6. 软件测试系列之四 单元测试(Junit)

    在线课堂:https://edu.csdn.net/lecturer/1516  单元测试简介 单元测试就是测试程序员依据其所设想的方式开发出来的程序是否产生了预期的结果.单元测试是与软件开发生命周期 ...

  7. PHP单元测试使用手册

    1 单元测试简介 1.1 单元测试 单元测试:是指对软件中的最小可测试单元进行检查和验证.(单一模块.一个过程.一个函数等) 1.2 单元测试范围和目标 单元测试包含计划阶段.设计阶段.实现阶段和执行 ...

  8. 好代码是管出来的——.Net Core中的单元测试与代码覆盖率

    测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是 ...

  9. 用于单元测试的JUnit教程–最终指南(PDF下载)

    编者注: 我们在Java Code Geeks上提供了许多JUnit教程,例如JUnit入门示例 , 使用断言和注释的 JUnit 示例 , JUnit注释示例等. 但是,为了方便读者,我们希望将所有 ...

最新文章

  1. Cannot find class for bean with name解决
  2. 瓜子二手车CEO杨浩涌:创业要建立势能,瓜子的技术能力是护城河,“瓜子大脑”能预测成交概率...
  3. 外观、体验升级 HUAWEI WATCH 2 Pro成智能手表领航者
  4. Primefaces Spring和Hibernate集成示例教程
  5. Mr.J--蓝桥杯--明明的随机数
  6. display: inline-block;水平居中
  7. jQuery常用的查找Dom元素方法
  8. flash cs4 调整渐变工具
  9. 郭天祥的10天学会51单片机_第十二节
  10. JUC-05-ForkJoin,Future(了解即可)
  11. 视觉和听觉的双重盛宴,富有正能量的B站美食UP主。
  12. 天嵌开发版 imx6 移植qt
  13. 东南亚痴狂诈骗的背后,意外暴露一个大型“围猎”程序员的现场
  14. 一行命令aigc stable-diffusion 文本生成图片(动漫,艺术图,涩图,成人) 快速部署体验,微信端,小程序
  15. Data Binding的报错集合 例如Error 10 54 错误 程序包com kodulf recycl
  16. python 正弦曲线_使用python生成正弦波数据
  17. 用java输出自己的名字_java 实现输出姓和名
  18. 【JavaScript详解】JavaScript语言的特性以及DOM操作和表单操作
  19. uni-app+websocket实现语音聊天小程序
  20. 初尝Mcafee之ePO端口修改【03】

热门文章

  1. TestDirector其他
  2. 【案例】下载站自动化 一个月发布3000+原创文章
  3. 在Gitee搭建属于自己的博客
  4. 关于开发网站--工作室
  5. PLC梯形图编程实战【逻辑实现】
  6. wince 访问共享文件_WINCE6.0建立共享文件夹
  7. 图解进程的(三种、五种)状态
  8. MobaXterm 11.1汉化版
  9. 高级项目管理师培训!看优秀的领导者如何给团队注入激情?
  10. LightCNN核心点解析