重构和测试我们的发电机克隆
erlang(20), 分布式系统(3)最近时间有点稀缺。因此,在我们涵盖下一个条目中的物理,逻辑和向量时钟之前,这里是测试和重构。
捡起来我们离开的地方在此帖子中,我们将在进一步实施之前,我们将在进行一些重构以清理CodeBase。我们的目标是四个:
- 简化了
kvs:store / 1
功能不威胁的功能, - 添加unittests via娇娇那
- 将客户端界面和服务器实现分为两个文件,
- 将基于消息传递的服务器返回为erlangGen_Server。
我们走了。
打扫干净店铺
这kvs:store / 1
功能已经令人尴尬地长。最初这可能是一个简单的解释简单的合理,但那时已经过去了。
该函数包含与消除消息类型的模式匹配,以及如何实现如何响应每种类型的消息。快速简化是将每个响应移动到自己的函数中。
模式匹配部分简化为:
店铺(店铺=#kvs_store.{数据=数据那pending_reads.=读那pending_writes.=写道})- >收到{发件人那取回那客户那钥匙}- >message_retrieve.(店铺那发件人那客户那钥匙);%% 等等..
每个响应函数的地方1好像:
message_retrieve.(店铺那发件人那客户那钥匙)- >发件人!!{自己(),检索到那客户那钥匙那普罗斯运选者:get_value.(钥匙那数据)},店铺(店铺)。
这项工作将代码减少为更容易消耗的块,但在我们重写服务器时,它也将成为乐器Gen_Server
之后。
这此工作的变更可在此处提供如果您想看到完整的更改。
添加测试
没有测试的重构在没有道路标志的情况下驾驶。更改了大量功能后,检查现有功能是否保持完整。在哪里时间密集是一种委婉语人类做得很大,不可避免地搞砸了。
Erlang提供了许多测试设施2,最简单的是娇娇,我们将使用哪些测试我们的项目。Eunit与[PhpUnit]或[JUnit]相当,除了(也许)需要较少的样板代码。
我们最初娇娇
测试将是相当简单的,但我们可以随着我们在行下添加进一步的功能时增强它们。
-模块(kvs_tests.)。-include_lib.(“eUnit /包括/ eunit.hrl”)。%% @doc测试响应当空kvs进程组时not_running_test.()- >kvs.:停止(),{错误那not_running.}=kvs.:得到(B.),{错误那not_running.}=kvs.:放(C那100.)
%% @doc测试最基本的功能Basic_test.()- >开始=kvs.:开始(3.),{好的那不明确的}=kvs.:得到(一种),{好的那更新}=kvs.:放(一种那5.),{好的那5.}=kvs.:得到(一种),停了下来=kvs.:停止()。
%% @doc测试执行许多并发读取many_reads_test.()- >开始=kvs.:开始(3.),{好的那更新}=kvs.:放(B.那100.),读=乐趣()- >{好的那100.}=kvs.:得到(B.)结尾那清单:Foreach.(乐趣(_)- >产卵(读)结尾那清单:SEQ.(0.那100.)),停了下来=kvs.:停止()。
%% @doc测试执行许多并发写字many_writes_test.()- >开始=kvs.:开始(3.),写=乐趣()- >{好的那更新}=kvs.:放(一种那101.)结尾那清单:Foreach.(乐趣(_)- >产卵(写)结尾那清单:SEQ.(0.那100.)),停了下来=kvs.:停止()。
运行测试是简单的,可以通过提供的命令行脚本来完成娇娇
,或通过erlang shell。
eShell v5.7.2(中止^ g)1>C(kvs.)。{好的,kvs}2>C(kvs_tests.)。{好的,kvs_tests}3>kvs_tests.:测试()。kvs_tests:not_running_test ... *失败*::错误:Badarg在功能kvs:get / 2在kvs_tests的调用中:not_running_test / 0=======================================================失败:1。跳过:0。通过:3。错误
好吧,看起来我需要对重构进行一些调整,但现在我们知道测试实际上是运行的。
接口和实施的分离
我们将迅速迁移,因为它不是特别教学,但兴趣点是:
- 分裂当前
kvs.erl.
进入kvs.erl.
和kvs_server.erl.
那 - 维护现有的客户端界面,
- 因为我们有测试我们可以安全地进行更改,回归测试,
- 没有添加任何新功能,
- 我们继续使用
kvs.
过程组;不必对应于文件。
重构在近距离结束后,是时候重新运行测试了。
5>C(kvs.)。{好的,kvs}6>C(kvs_server)。{好的,kvs_server}8>C(kvs_tests.)。{好的,kvs_tests}9.kvs_tests.:测试()。kvs_tests:not_running_test ... *失败*::错误:function_clause在功能列表中:' - 过滤器/ 2-LC $ ^ 0 / 1-0 - '/ 2叫做'-filter / 2-lc $ ^ 0 / 1-0 - '({error,{no_such_group,kvs}},#fun ) 在kvs的呼叫中:停止/ 0在kvs_tests的调用中:not_running_test / 0=======================================================失败:1。跳过:0。通过:3。错误10.
啊,啊kvs_tests:not_running_test.
还有一个问题,但其他一切都在通过。我们在重构之前保留了怪癖和功能:成功重构的标志。
从消息传递到Gen_Server
此重构的最终阶段是从我们的消息传递的实现转换到一个使用的实现OTP Gen_Server.。Gen_Server
代表通用服务器这正是它听起来的样子。Gen_Server
S在功能上等同于我们迄今为止所使用的基于消息的方法,但它们有一些重要的优势:
- 他们消除锅炉板代码,
- 它们与其他erlang OTP公用事业公司一起玩得很好,如主管,层次结构树等,
- 他们对其他Erlang程序员熟悉,因此了解程序的整体结构更快。
大多数实质性的erlang项目要么从至少一个开始或结束Gen_Server
。
当我们通过此重写时,请注意,我们将保留客户端接口常量,以便假设用户从任何更改中避免,以便我们可以继续运行我们现有的测试。经过一点扰流板,经过大量的动荡,组合的大小kvs.erl.
和kvs_server.erl.
将从200到189行代码中减少。
现在让我们看一下我们的第一个更改:更新客户端界面的实现,用于启动KVS服务器kvs.erl.
。
%%旧启动功能开始(N)- >PG2.:创造(kvs.),清单:Foreach.(乐趣(_)- >店铺=#kvs_store.{数据=[],pending_reads.=[],pending_writes.=[]},PG2.:加入(kvs.那产卵(kvs_server那店铺那[店铺]))结尾那清单:SEQ.(0.那N)),开始。%%新的启动功能开始(N)- >清单:Foreach.(乐趣(_)- >kvs_server:start_link.()。结尾那清单:SEQ.(0.那N)),开始。
我们对此进行了类似的变化kvs:停止
。
%%旧停止功能停止()- >清单:Foreach.(乐趣(PID)- >PG2.:离开(kvs.那PID),PID!!停止结尾那PG2.:get_members.(kvs.)),停了下来。%%新停止功能停止()- >清单:Foreach.(乐趣(PID)- >Gen_Server:称呼(PID那停止)结尾那PG2.:get_members.(kvs.)),停了下来。
接下来,我们需要更新客户端界面的实现以进行更新和检索值。值得注意的两件事是:
- 我们在接收构造中获取发送和块
Gen_Server /呼叫
那 - 我们不再需要明确传递发件人的流程ID,因为我们也可以免费获得。
%%旧获得实施得到(钥匙那超时)- >PID=PG2.:get_closest_pid.(kvs.),PID!!{自己(),得到那钥匙},收到{PID那得到了那价值}- >{好的那价值};{错误那错误}- >{错误那错误}后超时- >{错误那超时}结尾。%%新进展得到(钥匙那超时)- >PID=PG2.:get_closest_pid.(kvs.),Gen_Server:称呼(PID那{得到那钥匙},超时)。
并且,更新值的相同更改。
%%旧设置实施放(钥匙那瓦那超时)- >PID=PG2.:get_closest_pid.(kvs.),PID!!{自己(),放那钥匙那瓦},收到{PID那已收到那{放那钥匙那瓦}}- >{好的那更新};{错误那错误}- >{错误那错误}后超时- >{错误那超时}结尾。%%新设置实现放(钥匙那瓦那超时)- >PID=PG2.:get_closest_pid.(kvs.),Gen_Server:称呼(PID那{放那钥匙那瓦},超时)。
现在我们已经完成了更改kvs.erl.
是时候搬到了kvs_server.erl.
。看看转换成一个所需的所有变更是相当沉闷的Gen_Server
(有一切的变化这可能是有效的)。相反,我们将采取高级方法并注意一些有趣的细节。
在旧的实现中,我们依赖于商店/ 1
函数要将传入消息调发到正确的功能,但是Gen_Server
对我们执行派遣,而是要求我们实施handle_call / 3.
功能。
%%从旧的实施中摘录店铺(店铺)- >收到{发件人那得到那钥匙}- >message_get.(店铺那发件人那钥匙);%%其他一些模式匹配停止- >好的结尾。重量%摘录新实施%% @doc处理显式停止请求handle_call.(停止那发件人那状态)- >{停止那停了下来那停了下来那状态};
handle_call.({得到那钥匙},SNDR.那状态=#kvs_store.{pending_reads.=读})- >%% 执行…
我们正在使用handle_call.
,但有一个类似的功能命名handle_cast.
这也用于新的kvs_server.erl.
执行。两者之间的差异是handle_call.
是来自客户的角度的同步操作,handle_cast.
是来自客户端的角度的异步操作。
注意handle_call.
仅需要来自客户端的角度的同步操作,并且可以使服务器将客户端请求存储在其状态,接收其他传入请求,然后返回对第一个请求的响应(此实现使其广泛使用图案)。
还有许多其他方面Gen_Server
我们可以潜入,但我们可能会在这次调查中陷入困境。相反,从我们的角度来看,目标是重组代码,以便我们可以随着序列的进展进行外科算法变化。
掌握。现在我们已经洗牌了我们所有的代码,我们已经出于针对OTP工作的业务,现在终于开始与之合作。从现在在我们进一步努力实现核心实施后,我们将返回这一工作,并利用其他一些erlang的善良(特别是监督员),但随时才能准备好达到一些算法。
接下来,我们将以逻辑时钟,向量时钟和正确排序更新。
请注意,函数的命名为
信息_???
完全是任意的。↩这erlang test_server也许是有史以来最深刻的灵活性和最深刻的令人困惑的测试软件。我试图在几次荣耀荣耀,但仍然发现它充其量充满挑战。↩