单元测试,集成测试和功能测试是构成连续交付必不可少的基础的所有类型的自动化测试,这是一种开发方法,可让您在数天或数小时内而不是数月或数年内安全地将更改交付到生产中。
自动化测试通过在软件到达最终用户之前捕获更多错误来增强软件稳定性。 它们提供了一个安全网,使开发人员可以进行更改,而不必担心他们会在不知不觉中破坏流程中的某些内容。
与普遍的直觉相反,维护质量测试套件可以通过立即发现错误来显着提高开发人员的生产率。 没有它们,最终用户将遇到更多错误,这可能导致对客户服务,质量保证团队和错误报告的依赖增加。
测试驱动开发需要花费更多的时间,但是,到达客户的错误在许多方面的成本更高:
投入生产的bug的成本比自动测试套件捕获的bug的成本高很多倍。 换句话说,TDD的投资回报率非常高。
关于不同类型的测试,您需要了解的第一件事是它们都有工作要做。 它们在持续交付中发挥着重要作用。
不久前,我正在为一个雄心勃勃的项目提供咨询,该团队很难建立一个可靠的测试套件。 因为很难使用和理解,所以很少使用或维护它。
我在现有测试套件中观察到的问题之一是,它混淆了单元测试,功能测试和集成测试。 它们之间绝对没有区别。
结果是测试套件并不是特别适合任何事物。
每种类型的测试都有其独特的作用。 您不能在单元测试,功能测试和集成测试之间进行选择。 使用所有这些,并确保可以独立运行每种类型的测试套件。
大多数应用程序都需要单元测试和功能测试,许多复杂的应用程序也需要集成测试。
您应该将单元测试,集成测试和功能测试相互隔离,以便可以在开发的不同阶段轻松地分别运行它们。 在持续集成过程中,经常以三种方式使用测试:
您应该使用哪种测试类型? 他们全部。
为了了解不同的测试如何适合您的软件开发过程,您需要了解每种测试都有其工作要做,并且这些测试大致分为三大类:
用户体验测试使用实际的用户界面(通常使用目标平台或设备)从用户的角度检查系统。
开发人员API测试从开发人员的角度检查系统。 当我说API时,我并不是说HTTP API。 我的意思是单元的表面积API:开发人员用来与模块,函数,类等进行交互的接口…
单元测试可确保各个组件彼此独立地工作。 单元通常是模块,功能等…
例如,您的应用可能需要将URL路由到路由处理程序。 可以针对URL解析器编写单元测试,以确保正确解析URL的相关组件。 另一个单元测试可以确保路由器为给定的URL调用正确的处理程序。
但是,如果要测试在将特定URL发布到后是否将相应的记录添加到数据库中,那将是集成测试,而不是单元测试。
单元测试在开发过程中经常用作开发者反馈机制。 例如,我对每个文件更改运行lint和单元测试,并在开发控制台中监视结果,该控制台在我工作时为我提供实时反馈。
为了使它正常运行,单元测试必须非常快地运行,这意味着在单元测试中应避免异步操作,例如网络和文件I / O。
由于集成测试和功能测试非常频繁地依赖于网络连接和文件I / O,因此在进行大量测试时,它们往往会大大降低测试运行速度,这可能会将运行时间从毫秒延长到几分钟。 对于非常大的应用程序,完整的功能测试运行可能需要一个多小时。
单元测试应该是:
“良好的错误报告”是什么意思?
我的意思是,无论您使用什么测试运行程序和断言库,失败的单元测试都应该让您一目了然:
前四个问题应该在故障报告中可见。 最后一个问题应该从测试的实现中明确。 某些断言类型不能回答故障报告中的所有这些问题,但大多数equal
, same
或deepEqual
断言应该可以。 实际上,如果这些是任何断言库中唯一的断言,则大多数测试套件可能会更好。 简化。
这是使用Tape的真实项目中的一些简单的单元测试示例:
// Ensure that the initial state of the "hello" reducer gets set correctly
import test from 'tape';
import hello from 'store/reducers/hello';
test('...initial', assert => {
const message = `should set { mode: 'display', subject: 'world' }`;
const expected = {
mode: 'display',
subject: 'World'
};
const actual = hello();
assert.deepEqual(actual, expected, message);
assert.end();
});
// Asynchronous test to ensure that a password hash is created as expected.
import test from 'tape',
import credential from '../credential';
test('hash', function (t) {
// Create a password record
const pw = credential();
// Asynchronously create the password hash
pw.hash('foo', function (err, hash) {
t.error(err, 'should not throw an error');
t.ok(JSON.parse(hash).hash,
'should be a json string representing the hash.');
t.end();
});
});
集成测试可确保各个单元正常协作。 例如,节点路由处理程序可能会将记录器作为依赖项。 集成测试可能会击中该路由,并测试连接是否已正确记录。
在这种情况下,我们要测试两个单元:
如果我们正在对记录器进行单元测试,那么我们的测试将不会调用路由处理程序,也不会知道任何信息。
如果我们在对路由处理程序进行单元测试,则我们的测试将对记录器进行存根测试,并忽略与之交互,仅测试路由是否对伪造的请求做出了适当的响应。
让我们更深入地看一下。 路由处理程序是一种工厂函数,它使用依赖项注入将记录程序注入到路由处理程序中。 让我们看一下签名(有关读取签名的帮助,请参见rtype文档 ):
createRoute({ logger: LoggerInstance }) => RouteHandler
让我们看看如何测试这个:
import test from 'tape';
import createLog from 'shared/logger';
import routeRoute from 'routes/my-route';
test('logger/route integration', assert => {
const msg = 'Logger logs router calls to memory';
const logMsg = 'hello';
const url = `http://127.0.0.1/msg/${ logMsg }`;
const logger = createLog({ output: 'memory' });
const routeHandler = createRoute({ logger });
routeHandler({ url });
const actual = logger.memoryLog[0];
const expected = logMsg;
assert.equal(actual, expected, msg);
assert.end();
});
我们将更详细地介绍重要的部分。 首先,我们创建记录器并告诉它登录内存:
const logger = createLog({ output: 'memory' });
创建路由器并传递记录器依赖项。 这就是路由器访问记录器API的方式。 请注意,在单元测试中,可以对记录器存根并单独测试路由:
const routeHandler = createRoute({ logger });
使用伪请求对象调用路由处理程序以测试日志记录:
routeHandler({ url });
记录器应通过将消息添加到内存日志中进行响应。 现在我们要做的就是检查消息是否存在:
const actual = logger.memoryLog[0];
同样,对于写入数据库的API,您可以连接到数据库并检查数据是否正确更新,等等。
许多集成测试测试与服务(例如第三方API)的交互,并且可能需要进入网络才能正常工作。 因此,集成测试应始终与单元测试分开,以使单元测试尽可能快地运行。
功能测试是自动测试,可确保您的应用程序从用户的角度进行应做的工作。 功能测试将输入馈送到用户界面,并对输出进行断言,以确保软件以应有的方式做出响应。
功能测试有时被称为端到端测试,因为它们会测试整个应用程序以及从前端UI到后端数据库系统的硬件和网络基础结构。 从这个意义上讲,功能测试也是集成测试的一种形式,可确保机器和组件的协作按预期进行。
功能测试通常会对“快乐之路”进行彻底的测试-确保关键的应用程序功能(例如用户登录,注册,购买工作流程以及所有关键的用户工作流程)均按预期方式运行。
功能测试应该能够在Sauce Labs等服务上的云中运行,该服务通常通过Selenium等项目使用WebDriver API 。
这需要一些杂耍。 幸运的是,有一些很棒的开源项目使它变得相当容易。
我最喜欢的是Nightwatch.js 。 这是一个简单的Nightwatch功能测试套件,类似于Nightwatch文档中的此示例:
module.exports = {
'Demo test Google' : function (browser) {
browser
.url('http://www.google.com')
.waitForElementVisible('body', 1000)
.setValue('input[type=text]', 'nightwatch')
.waitForElementVisible('button[name=btnG]', 1000)
.click('button[name=btnG]')
.pause(1000)
.assert.containsText('#main', 'Night Watch')
.end();
}
};
如您所见,功能测试在登台环境和生产环境中都会命中真实的URL。 他们通过模拟最终用户为了在应用程序中实现目标而可能采取的行动来工作。 他们可以单击按钮,输入文本,等待页面上发生的事情以及通过查看实际的UI输出进行断言。
在将新版本部署到生产中之后,立即找出它在生产环境中是否按预期工作很重要。 您不希望您的用户在找到错误之前就发现它们-可能会将它们赶走!
对于新部署的版本,维护一套自动化功能测试(就像冒烟测试一样)非常重要。 测试应用程序中的所有关键功能:大多数用户在典型会话中会遇到的东西。
冒烟测试不是功能测试的唯一用途,但在我看来,它们是最有价值的。
在持续交付革命之前,使用瀑布式流程发布了软件。 软件将一次执行以下步骤。 在继续下一步之前,必须完成每个步骤:
之所以称为瀑布,是因为如果您按从右到左的时间进行绘制,它看起来就像瀑布从一个任务到下一个任务级联。 换句话说,从理论上讲,您不能真正同时执行这些操作。
理论上。 实际上,在项目开发过程中发现了很多项目范围,范围的变化通常会导致灾难性的项目延误和返工。 不可避免地,业务团队还将希望在交付后进行“简单的更改”,而无需再次经历整个昂贵,耗时的瀑布式过程,这通常导致无休止的变更管理会议和生产修复程序的周期。
干净的瀑布过程可能是神话。 我已经有很长的职业生涯,并与数百家公司进行了咨询,但是我从未见过理论上的瀑布式工作可以像现实生活中那样。 典型的瀑布释放周期可能需要数月或数年。
持续交付是一种开发方法,它承认随着项目的进行而发现了范围,并鼓励在短周期内逐步改进软件,以确保可以随时发布软件而不会引起问题。
通过连续交付,更改可以在几个小时内安全地寄出。
与瀑布方法相反,我已经看到连续交付过程在数十个组织中运行顺畅-但是我从来没有见过没有质量测试套件(包括单元测试和功能测试,并且经常包括集成测试。
希望现在您拥有在持续交付基础上入门所需的一切。
如您所见,每种测试都有重要的作用。 单元测试可为开发人员提供快速反馈,集成测试可涵盖组件集成的所有极端情况,而功能测试可确保一切都适合最终用户。
您如何在代码中使用自动化测试,以及它如何影响您的信心和生产力? 在评论中让我知道。
From: https://www.sitepoint.com/javascript-testing-unit-functional-integration/