Perl工具链峰会需要更多赞助商。如果您的公司取决于Perl,拜托支持这一非常重要的活动.

名称

Mojolicious::Guides::Testing-Web应用程序测试变得简单

概述

本文档介绍如何使用测试::Mojo.测试::Mojo可以将其视为一个模块,它提供了以Perl-ish方式测试web应用程序所需的所有工具和测试断言。

While期间测试::Mojo可以用于测试任何web应用程序,它具有用于进行测试的快捷方式Mojolicious公司web应用程序简单且无痛苦。

请参阅测试::Mojo文档中完整引用了本文档中介绍的许多思想和语法。

简单web应用程序的测试文件可能如下所示:

使用Mojo::Base-strict;使用测试::Mojo;使用测试::更多;#启动一个名为“Celestial”的Mojolicious应用程序my$t=测试::Mojo->new(“天国”);#发布JSON文档$t->post_ok('/nootifications'=>json=>{event=>‘满月’})->状态_is(201)->json_is('/message'=>'通知已创建');#执行GET请求并查看响应$t->get_ok(“/日出”)->状态_ is(200)->内容类(qr/am$/);$t->get_ok(“/日落”)->状态_ is(200)->content_like(qr/pm$/);#发布URL编码的表单$t->post_ok('/insurance'=>form=>{name=>'Jimmy',金额=>'3.000.000'})->状态_is(200);#使用Test::More's like()检查响应比如$t->tx->res->dom->at('div#thanks')->text,qr/thankyou/,'thanks'';done_testing();

在本文的其余部分中,我们将探讨这些概念以及与测试::Mojo.

概念

Essentials每Mojolicious公司开发人员应该知道。

测试::Mojo一目了然

这个测试::更多与Perl捆绑的模块包括几个基本测试断言,例如好 啊,,不是,喜欢,不像,cmp_ok(确认)等。如果断言的表达式返回真值,则断言“通过”。如果断言通过或失败(分别),断言方法将输出“ok”或“not ok”。

测试::Mojo提供了围绕web应用程序请求/响应事务(传输、响应标头、响应正文等)和WebSocket通信组织的其他测试断言。

值得注意的一件有趣的事情是:测试::Mojo对象断言总是测试对象本身,允许我们“链接”测试断言方法。因此,与其这样对相关测试语句进行分组,不如:

$t->get_ok('/framgs');$t->status_is(200);$t->content_like(qr/bullrog/);$t->content_like(qr/amlytoad/);

方法链接允许我们连接属于一起的测试断言:

$t->get_ok('/framgs')->状态_ is(200)->内容类(qr/牛蛙/)->类内容(qr/催眠蟾蜍/);

这会带来更多简洁的相干的测试经验:简洁是因为我们没有为每个测试重复调用,连贯是因为属于同一请求的断言在语法上绑定在同一方法链中。

有时,分解测试以对响应执行更复杂的断言是有意义的。测试::Mojo公开整个事务对象,以便您可以从响应中获取所需的所有数据:

$t->put_ok('/bees'=>json=>{type=>'worker',name=>'Karl'})->状态_ is(202)->json_has('/id');#从响应中提取idmy$newbee=$t->tx->res->json('/id');#使用先前响应中的数据发出新请求$t->get_ok(“/bees/$newbee”)->状态_ is(200)->json_is('/name'=>'Karl');

这个测试::Mojo对象是有状态的。只要我们还没有通过调用*_好的方法,上一个事务的请求和响应对象在测试::Mojo对象:

#第一笔交易$t->get_ok('/frogs?q=bullfrog'=>{内容类型'=>'application/json'})->状态_ is(200)->类json('/0/species'=>qr/catebeianus/i);#仍是第一笔交易$t->content_type_is(“应用程序/json”);#第二笔交易$t->get_ok('/framgs?q=banjo'=>{内容类型'=>'text/html'})->状态_ is(200)->类内容(qr/interioris/i);#还是第二笔交易$t->content_type_is('text/html');

这种庄严也使测试::Mojo要处理会话,请遵循重定向,并在重定向期间检查过去的响应。

这个测试::Mojo对象

这个测试::Mojo对象管理Mojolicious应用程序生命周期(如果提供了Mojolicous应用程序类)并公开内置Mojo::用户代理对象。创建裸露测试::Mojo对象:

my$t=测试::Mojo->new;

此对象初始化Mojo::用户代理对象,并为访问web应用程序提供各种测试断言方法。例如,使用这个对象,我们可以测试任何正在运行的web应用程序:

$t->get_ok('https://www.google.com/')->状态_ is(200)->类内容(qr/search/i);

如果要在不触发测试断言的情况下发出web请求,可以直接访问用户代理:

我的$tx=$t->ua->post('https://duckduckgo.com/html'=>形式=>{q=>“催眠蟾蜍”});$tx->result->dom->find('a.result__a')->each(sub{say$_->text});

请参见Mojo::用户代理以获取完整的API和返回值。

测试Mojolicios应用程序

如果您传递的是Mojolicious公司应用程序类(例如,“MyApp”)测试::Mojo建造师,测试::Mojo将实例化类并启动它,并使其侦听一个随机(未使用的)端口号。使用测试Mojolicious应用程序测试::Mojo永远不会与运行的应用程序冲突,包括您正在测试的应用程序。

这个Mojo::用户代理中的对象测试::Mojo将知道应用程序在哪里运行并向其发出请求。测试完成后Mojolicious公司应用程序将被删除。

#侦听本地主机:32114(一些未使用的TCP端口)my$t=测试::Mojo->new(“青蛙”);

要测试Mojolicious::精简应用程序,将应用程序脚本的文件路径传递给构造函数。

#加载相对于“t”目录的应用程序脚本使用Mojo::File qw(curfile);my$t=Test::Mojo->new(curfile->dirname->sibling('myapp.pl'));

对象初始化Mojo::用户代理对象,加载Mojolicious应用程序,绑定并侦听空闲TCP端口(例如32114),并启动应用程序事件循环。测试::Mojo对象($t(美元))超出范围时,应用程序将停止。

测试对象方法断言中的相对URL(获取(_O),发布(_O)等)将发送到由启动的Mojolicious应用程序测试::Mojo:

#改写为“http://localhost:32114/frogs(本地主机:32114/青蛙)"$t->get_ok('/framgs');

测试::Mojo内置了许多方便的快捷方式来进行测试Mojolicious公司Mojolicious::精简应用程序令人愉快。

一个例子

让我们使用mojo生成应用程序MyApp. The魔力实用程序将创建一个工作应用程序和一个包含工作测试文件的目录:

$mojo生成应用程序MyApp[mkdir]/my_app/script[写入]/my_app/script/my_aapp[chmod]/my_app/script/my_ap 744...[mkdir]/我的应用/t[写入]/my_app/t/basic.t...

让我们运行测试(我们将创建日志目录以静默应用程序输出):

$cd我的应用程序$mkdir日志$证明-lv tt/basic.t。。确定1-获取/正常2-200正常ok 3-内容相似1..3好 啊所有测试均成功。文件=1,测试=3,0壁钟秒(0.03 usr 0.01 sys+0.33 cusr 0.07 csys=0.44 CPU)结果:通过

样板测试文件如下所示:

使用Mojo::Base-strict;使用测试::更多;使用测试::Mojo;my$t=测试::Mojo->new(“MyApp”);$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);done_testing();

这里我们可以看到我们的应用程序类名我的应用程序传递给测试::Mojo构造函数。发动机罩下,测试::Mojo创建新的Mojo::服务器实例,加载我的应用程序(我们刚刚创建的),并运行应用程序。我们使用相对URL编写测试,因为测试::Mojo负责将请求发送到正在运行的测试应用程序(因为其端口可能会在运行之间发生更改)。

使用配置数据进行测试

我们可以使用环境变量(例如MOJO_模式)和通过配置值。一个很好的功能测试::Mojo是它直接从构造函数传递配置值的能力。

让我们修改我们的应用程序并添加一个“功能标志”,以便在启用天气设置配置值:

#从“my_app.conf”返回的散列加载配置my$config=$self->插件('config');#至控制器的正常路线$r->get('/')->to('example#welcome');#新增:只有在配置中设置了“enable_weather”时,该路线才存在if($config->{enableweather}){$r->get(“/天气”=>sub($c){$c->render(text=>“很热!🔥");});}

为了测试这个新特性,我们甚至不需要创建配置文件,只需通过测试::Mojo的构造函数:

my$t=Test::Mojo->new(MyApp=>{enable_weather=>1});$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);$t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);

当我们运行这些测试时,测试::Mojo将此配置数据传递给应用程序,这将导致它创建一个特殊的/天气我们可以在测试中访问的路线。除非启用天气如果在配置文件中设置,则在应用程序运行时,此路由将不存在。这样的功能标记允许我们在一段时间内以小受众为目标进行功能的软发布。一旦该功能得到验证,我们就可以重构条件,使其成为一个完整的版本。

这个例子展示了开始测试Mojolicious应用程序是多么容易,以及如何从测试文件中设置特定的应用程序配置指令。

测试应用程序助手

假设我们在应用程序中注册一个助手以生成HTTP Basic Authorization头:

使用Mojo::Util qw(b64_encode);app->helper(basic_auth=>sub($c,@values){return{Authorization=>“Basic”.b64_encode联接(“:”=>@values),“”};});

我们如何测试这样的应用程序助手?测试::Mojo有权访问应用程序对象,这允许我们从测试文件调用帮助程序:

my$t=测试::Mojo->new(“MyApp”);is_deeply$t->app->basic_auth(bif=>“bif's Passwerdd”),{Authorization=>'基本YmlmOkJpZidzIFBhc3N3ZXJkZA=='},'正确的标头值';

应用程序的任何方面(助手、插件、路由等)都可以从测试::Mojo通过应用程序对象。这使我们能够深入测试Mojolicious公司-基于应用程序。

资产

本节描述了由提供的基本测试断言测试::Mojo。HTTP请求有四大类断言:

  • HTTP请求

  • HTTP响应状态

  • HTTP响应标头

  • HTTP响应内容/正文

WebSocket测试断言包含在“测试WebSocket web服务”.

HTTP请求断言

测试::Mojo有一个Mojo::用户代理对象,该对象允许它发出HTTP请求并检查HTTP传输错误。HTTP请求断言包括获取(_O),发布(_O),等等。这些断言不会测试请求是否得到了处理成功地只有web应用程序以符合HTTP的方式处理请求。

您还可以使用自定义动词(除GET(获取),邮政,PUT(输出)等)。请参见“自定义交易记录”如下所示。

使用HTTP请求断言

要将URL编码的表单发布到/呼叫应用程序的端点,我们只需使用形式内容类型快捷方式:

$t->post_ok('/calls'=>form=>{to=>'+43.55.5555555'});

这将创建以下HTTP请求:

POST/调用HTTP/1.1内容物长度:20内容类型:application/x-www-form-urlencoded到=%2B43.55.555.555

这个*_好的HTTP请求断言方法接受与其对应的参数相同的参数Mojo::用户代理方法(回调参数除外)。这允许我们为真实的测试情况设置标头并构建查询字符串:

$t->get_ok('/internal/personmental'=>{Authorization=>'Token secret-password'}=>form=>{q=>'Professor Plum'});

其生成以下请求:

GET/内部/人员?q=教授+梅花HTTP/1.1内容长度:0授权:Token secret-password

这个形式内容生成器(请参见Mojo::UserAgent::Transactor)将为生成查询字符串GET(获取)请求和应用程序/x-www-form-urlencoded多部分/表单数据用于POST请求。

而这些*_好的断言使HTTP请求我们期望,他们很少告诉我们有多好应用程序处理了请求。我们正在测试的应用程序可能返回了任何内容类型、主体或HTTP状态代码(200、302、400、404、500等),但我们不会知道。

测试::Mojo提供断言来测试HTTP响应的几乎所有方面,包括HTTP响应状态代码、内容类型标头和其他任意HTTP标头信息。

HTTP响应状态代码

虽然从技术上讲不是HTTP头,但状态行是HTTP响应中的第一行,后面是响应头。测试响应状态代码在基于REST和其他web应用程序中很常见,这些应用程序使用HTTP状态代码来广泛指示服务器返回的响应类型。

测试状态代码很简单,只需添加状态_ is断言:

$t->post_ok('/doorbell'=>form=>{action=>'ring-once'})->状态_is(200);

随着状态,这将满足大多数需求。要进行更详细的状态代码测试,您可以直接访问响应内部:

$t->post_ok('/doorbell'=>form=>{action=>'ring-once'});是$t->tx->res->message,‘Moved Permanently’,‘try next door’;

HTTP响应标头

测试::Mojo允许我们检查和断言HTTP响应头。这个内容类型标头通常经过测试,并有自己的断言:

$t->get_ok('/map-of-the-world.pdf')->content_type_is('应用程序/pdf');

这相当于更详细的:

$t->get_ok('/map-of-the-world.pdf')->header_is(“内容类型”=>“应用程序/pdf”);

我们可以使用方法链测试单个响应中的多个标头:

$t->get_ok('/map-of-the-world.pdf')->content_type_is('应用程序/pdf')->header_isnt(“压缩”=>“gzip”)->header_unliverse(“服务器”=>qr/IIS/i);

HTTP响应内容断言

测试::Mojo还公开了一组丰富的断言,用于测试响应主体,无论该主体是HTML、纯文本还是JSON。这个内容_*方法将响应正文视为纯文本(由响应的字符集定义):

$t->get_ok('/scary-things/spiders.json')->content_is('{“蜘蛛”:“棕色隐士”}');

虽然这是一个JSON文档,内容(_I)将其视为文本文档。对于我们正在寻找特定字符串而与文档结构无关的情况,这可能很有用。例如,我们可以对HTML文档执行相同的操作:

$t->get_ok('/scary-things/spiders.html')->content_like(qr{<title>所有蜘蛛</title>});

但是因为测试::Mojo可以访问所有Mojo::用户代理这样,我们可以使用断言来检查JSON文档以及基于DOM的文档(HTML、XML),这些断言允许我们检查元素的存在以及检查文本节点的内容。

JSON响应断言

测试::MojoMojo::用户代理可以访问JSON解析器,该解析器允许我们使用JSON指针语法测试JSON响应是否在文档中的某个位置包含值:

$t->get_ok('/alives/friendy.json')->json_has('/beens/jeremiah/age');

这个断言告诉我们友情.json文档包含位于/众生/耶利米/年龄JSON指针位置。我们还可以检查JSON指针位置的值:

$t->get_ok('/alives/friendy.json')->json_has('/engins/jeremiah/age')->json_is('/beens/jeremiah/age'=>42)->类json('/beens/jeremiah/species'=>qr/牛蛙/i);

JSON指针语法使测试JSON响应简单易读。

DOM响应断言

我们还可以使用Mojo::DOM用户代理中的解析器。以下是测试::Mojo文档:

$t->text_is('div.foo[x=y]'=>'你好!');$t->text_is('html head title'=>'Hello!','right title');

这个Mojo::DOM解析器使用中描述的CSS选择器语法Mojo::DOM::CSS允许我们测试HTML和XML文档中的值,而无需求助于通常冗长且不灵活的DOM遍历方法。

高级主题

本节描述了一些复杂(但常见)的测试情况测试::Mojo擅长简化。

重定向

这个Mojo::用户代理中的对象测试::Mojo可以在内部处理HTTP重定向到所需的任何级别。假设我们有一个重定向的web服务/1/2,/2重定向到/3,/3重定向到/4、和/4重定向到/5:

获取/1

返回:

找到302个位置:/2

和:

获得/2

返回:

找到302个位置:/3

以此类推,直到/5:

获得/5

它返回我们想要的数据:

200正常{“消息”:“这是五个”}

我们可以告诉用户代理测试::Mojo如何处理重定向。每个测试都会请求获取/1,但我们会更改用户代理在每次测试中应遵循的重定向数:

my$t=测试::Mojo->new;$t->get_ok('/1')->header_is(位置=>'/2');$t->ua->max_redirects(1);$t->get_ok('/1')->header_is(位置=>“/3”);$t->ua->max_redirects(2);$t->get_ok('/1')->header_is(位置=>“/4”);#查看上一跳是$t->tx->previor->res->headers->location,'/3','previor-redirect';$t->ua->max_redirects(3);$t->get_ok('/1')->header_is(位置=>'/5');$t->ua->max_redirects(4);$t->get_ok('/1')->json_is('/message'=>'这是五个');

当我们设定最大重定向数,它在测试对象的生命周期内保持不变,直到我们更改它。

测试::Mojo的HTTP重定向处理消除了进行许多重定向(有时是未知数量的重定向)的需要,以保持测试的精确性和易懂性(ahem)。

Cookie和会话管理

我们可以使用测试::Mojo测试在cookie中保持会话状态的应用程序。默认情况下Mojo::用户代理中的对象测试::Mojo将通过自动保存和发送cookie为我们管理会话,就像常见的web浏览器一样:

使用Mojo::Base-strict;使用测试::更多;使用测试::Mojo;my$t=测试::Mojo->new(“MyApp”);#无授权cookie$t->get_ok('/')->状态(401)->content_is('请登录');#应用程序设置授权cookie$t->post_ok('/login'=>form=>{password=>'let me in'})->状态_ is(200)->content_is('您已登录');#从上一个事务发送cookie$t->get_ok('/')->状态_ is(200)->content_like(qr/您在\d+/登录);#清除Cookie$t->reset_session;#再次没有授权cookie$t->get_ok('/')->状态(401)->content_is('请登录');

我们还可以通过事务的响应检查cookie响应中的特殊值(Mojo::消息::响应)对象:

$t->get_ok('/');像$t->tx->res->cookie(“marty”),qr/smarty=裤子/,“找到cookie”;

自定义交易记录

假设我们有一个响应新HTTP动词的应用程序要使用它,我们还必须传递一个秘密cookie值。这不是问题。我们可以通过创建Mojo::事务对象,设置cookie(请参见Mojo::消息::请求),然后将事务对象传递给请求(_O):

#使用自定义“RING”动词my$tx=$t->ua->build_tx(环=>“/门铃”);#设置一个特殊的cookie$tx->req->cookie({name=>'Secret',value=>“不要告诉任何人”});#提出请求$t->request_ok($tx)->状态_ is(200)->json_is('/status'=>'ding-dong');

测试WebSocket web服务

虽然WebSocket连接上的消息流可能是相当动态的,但它通常是完全可预测的,这使得这非常令人愉快测试::Mojo要使用的WebSocket API:

使用Mojo::Base-strict;使用测试::更多;使用测试::Mojo;#测试echo web服务my$t=测试::Mojo->new(“EchoService”);$t->websocket_ok('/echo')->send_ok(“你好,莫霍!”)->消息ok->message_is('echo:你好,Mojo!')->finish_ok;#测试JSON web服务$t->websocket_ok('/echo.json')->send_k({json=>{test=>[1,2,3]}})->消息ok->json_message_is('/test'=>[1,2,3])->finish_ok;done_testing();

由于其固有的异步特性,测试WebSocket通信可能很棘手。这个测试::MojoWebSocket断言通过事件循环原语序列化消息。这使我们能够将WebSocket消息视为使用了与HTTP相同的请求-响应通信模式。

为了说明这一点,我们来看看这些测试。在第一个测试中,我们使用网络套接字(_O)断言以确保我们可以连接到应用程序的WebSocket路由/回声它正在向我们“说话”WebSocket协议。下一个发送(_O)断言再次测试连接(例如,在连接关闭的情况下)并尝试发送消息你好,莫霍!。下一个断言,消息_ok,块(使用Mojo::IOLoop应用程序中的singleton)并等待服务器的响应。然后将响应与“echo:你好,Mojo!”在中消息_is断言,最后我们关闭并用完成(_O).

第二个测试与第一个测试类似,但现在我们在/回声.json。在发送(_O)我们利用的断言Mojo::用户代理的JSON内容生成器(请参阅Mojo::UserAgent::Transactor)将散列和数组引用封送到JSON文档中,然后将它们作为WebSocket消息发送。我们等待(阻止)来自服务器的响应消息_ok。然后,因为我们希望返回JSON文档,所以可以利用json消息ok它解析WebSocket响应主体并返回我们可以通过其访问的对象Mojo::JSON::指针语法。然后我们关闭(并测试)我们的WebSocket连接。

测试WebSocket服务器没有比使用测试::Mojo.

延伸测试::Mojo

如果您看到您正在编写大量不可链接的测试断言,那么您可能会从编写自己的测试断言中受益。假设我们想测试位置重定向后的标头。我们将创建一个新类角色::Tiny实现名为位置_is:

包测试::Mojo::Role::Location;使用Mojo::Base-role,-signatures;sub-location_is($self,$value,$desc=“位置:$value”){返回$self->test('is',$self->tx->res->headers->location,$value,$desc);}1;

当我们使用角色生成新的测试断言时,我们希望使用与其他测试断言匹配的方法签名*_是中的方法测试::Mojo,所以这里我们接受测试对象、要比较的值和可选描述。

我们指定默认描述值($desc(美元)),然后我们使用测试中的“测试”:Mojo将位置标头与预期标头值进行比较,并最终传播测试::Mojo对象用于方法链接。

有了这个新包,我们就可以组成一个使用角色的新测试对象了:

my$t=测试::Mojo->with_roles('+Location')->new('MyApp');$t->post_ok('/redirect/mojo'=>json=>{message=>'mojo,我来了!'})->状态(302)->location_is('http://mojolicious.org')->或(子{diag“我想念tempire”});

在本节中,我们介绍了如何将自定义测试断言添加到测试::Mojo以及如何使用这些角色来简化测试。

更多

您可以继续Mojolicious::指南现在或者看看Mojolicious维基,其中包含许多不同作者编写的更多文档和示例。

支持

如果您有任何文档可能还没有回答的问题,请毫不犹豫地在论坛,在IRC公司,或矩阵.