许多公司的Codebases存在遗留代码。通常在内部,存在在开发过程中导致障碍的反模式。

我最近将我的第一个分析事件添加了我刚刚完成的一些功能工作的一部分。我偶然发现了一些意想不到的问题是不正确的模式的后果。在本文中,我将向您展示我如何使用抽象克服它们并提高可测试性。

空调

我们有一个调用的特征或流量特定类sessionmetrics.。它包含所有必要的部分,用于发送与我们的用户会话相关的分析事件。其大多数方法将数据作为输入将数据带到,然后创建并发送特定于其分析库的事件。它还定义了我们发送的常见值。

Object SessionMetrics:BaseMetrics(){const val convo_status =“会话状态”有趣FireSelectedMsg(/ *批次Params * /){//创建事件firelevent(事件)}}

sessionmetrics.以上从父类扩展粪便仪。父父父级是非常简单的,是一种直接调用我们的分析库的包装器。它还包含了一种用于创建特定于其分析库的事件的便利方法。

以下是一个例子粪便仪好像。

开放类粪便{fun firelevent(事件:事件){val instance = symenticstool.getInstance()}}

问题

作为我们工作的一部分,我们需要从ViewModel添加新的分析事件。天真地,我们符合上面的模式。代码按预期运行,但我们很快就会迎接一个破碎的单位测试。我们的静态构造正直接呼叫我们的分析库,因此我们的视图模型测试失败了。

线路val instance = symenticstool.getInstance()从上面的例子导致问题。我们的Analyticstool绝不是在JVM中运行,结果损坏了我们的单元测试。我们能做什么?我们不能这样留下它。

我们的目标是什么?

至少,我们的变化不应破坏测试。最佳,我们希望通过测试除了不打破现有测试之外,我们将验证我们的变更。让我们的目标高,看看我们可以想出什么!

从界面开始

让我们开始创建一个接口,该接口将定义我们要发送到我们的Analytics客户端的值。

interface sessionmetrics {fun fireselectedmsg(InboxSizeProperty:int?,targetUserID:String ?,匹配用户:User ?,Inignnways:Int?)}

我们正在呼叫我们的界面sessionmetrics.它会有我们的事件方法fireeselectedmsg()这是我们分析事件的所有所需数据。

创建一个实现

class symunticslibsessionmetrics:sessionmetrics,basemetrics(){override funeselectedmsg(inboxsizeproperty:int?,targetUserID:string ?, matchedUser:User?,InitialNways:int?){//创建我们的Analytics库特定事件//从我们在Data Firedvent中获取特定事件//(AnalyticsLibevent)}}

然后我们创建了我们的具体实现,并命名为它AnalyticsLibSessionMetrics.名称应反映此特定实现的哪些分析库正在使用。在这种情况下它是分析器。它有助于我们sessionmetrics.除了实用程序之外,上面的接口粪便仪如果您使用Firebase例如它将是FirebasessionMetrics.

我们现在可以将新创建​​的类传递给我们的视图:

MessageThreadViewModel(AnalyticsLibSessionMetrics())

使用假修复测试

让我们通过创建代表我们声明的界面的假设来获得测试并再次运行。

class fakessionmetrics:sessionmetrics {override fun fireeselectedmsg(InboxsizeProperty:int?,targetUserID:String ?, matchedUser:User?,InitialNways:int?){// do nother}}

通过我们的假声明,我们可以从测试中创建我们的视图模型:

val fakessionmetrics = fakessionmetrics()MessageThreadviewModel(FakessionMetrics)

现在我们的测试再次正常工作,因为我们并不试图拨打我们的分析库。感觉像我们可以做得更好!让我们验证实际调用的事件。

验证使用假

让我们将一个实例变量添加到我们的fakessionmetrics.

class fakessionmetrics:sessionmetrics {var selectsmegeventfired = false覆盖有趣的fireselectedmsg(//大量参数){selectsmegeventfired = true}}

实例变量选择米格ventfired.当调用我们的假事件方法时,设置为true。然后我们可以从测试中验证我们的fireeselectedmsg()根据我们所需调用方法。

assertthat(fakessionmetrics.selectedmsgeventfired).istrue()

有些东西仍然不对

我们的参数fireeselectedmsg()方法是我们传递给我们的分析客户端的价值。我们对数据进行了一些处理,以推导出我们发送给我们的分析客户端的某些值。代码库中的当前模式将在ViewModel中放置该处理逻辑。我目前的设计在我们新创造的sessionmetrics.它没有真正属于任何一种。

它怎么不属于?

我们的ViewModel真的不应该负责处理数据以发送到我们的分析层。它会增加ViewModel的责任,并使我们更难验证此处理中的逻辑。

如果我们在我们的助手的具体实施中将此处理逻辑放入sessionmetrics.,我们无法测试它。我们将拥有我们的原始问题,因为我们正在直接致电我们的分析库。处理逻辑是否与我们的具体实施有关?

数据作为类型

让我们准确查看我们将发送到我们的分析客户端。

val hasread:boolean,val targetuserid:string?,val yeyindays:int,val inboxsize:int

接下来,我们可以创建一个数据类来表示这些值。

数据类SelectedMessageEvent(val hasread:boolean,val targetuserid:string?,val yegeindays:int,val inboxsize:int)

我们的sessionmetrics.界面现在可以改变。

interface sessionmetrics {fun fireselectedmsg(selectedMessageEvent:SelectedMessageEvent)}

这简化了界面并从我们的处理逻辑中解耦了我们的Analytics库。我们现在是我们对此活动期望的Analytics库的准确性。

处理逻辑

我们可以将我们的处理逻辑与我们的数据类联系起来,因为它们逻辑地一起使用。我们可以附上静态方法从()到我们的数据类(如:

数据类SelectedMessageEvent(val hasread:boolean,val targetuserid:string?,val yegeindays:int,val inboxsize:int){companion对象{from from(inboxsizeproperty:int?,targetUserid:string ?, matchedUser:UsiteNways:Inte?):SelectedMessageEvent {//处理逻辑}}}

我们从观众的调用看起来像:

val事件= selectedMessageEvent.from(Insize,UserID,MatchedUser,Nways)SessionMetrics.firselectedMsg(事件)

这一切都是什么意思

我们的观点现在减少了责任。它创造了一个事件,但它不知道如何。它将事件发送到它不知道具体实现的接口。这一切都导致了改进的测试。我们现在可以在测试时间被激励,即我们的分析层被调用。

分析层现在正在使用我们的自定义数据类,其中派生值不需要处理。我们现在明确地了解了这本层需要什么。如果在未来,我们希望替换我们的分析库或添加其他实施,它只需要使用我们的新数据类。

用于创建事件的处理逻辑现已被隔离并且可以进行测试。我们可以提供广泛的输入,并验证事件数据类是在我们期望时生成的。

我们的测试现在有效,我们可以验证我们的视图中的正确行为,我们可以验证我们的事件数据类是否已正确创建。

我希望你喜欢这篇文章,请随时与我联系推特

有兴趣为Okcupid工作吗?188bet金宝搏官网我们正在招聘!!