188be188博金宝电子体育频道;t金宝搏官网OkCupid通过赝品和工厂提高了Android的可测试性

遗留代码存在于许多公司的代码库中。在开发过程中,反模式通常会造成障碍。

我最近添加了我的第一个分析活动,作为我刚刚完成的功能工作的一部分。我偶然发现了一些意想不到的问题,它们是错误模式的结果。在本文中,我将向您展示如何使用抽象和改进的可测试性来克服这些问题。

的终结

我们有一个特性或特定于流程的类叫做SessionMetrics.它包含发送与用户会话相关的分析事件的所有必要部分。它的大多数方法将数据作为输入,然后创建并发送特定于我们的分析库的事件。它还定义了我们随事件发送的公共值。

object SessionMetrics: BaseMetrics() {const val CONVO_STATUS = "会话状态" fun fireSelectedMsg(/* lots of params */){//创建事件fireEvent(event)}}

SessionMetrics上面扩展了一个父类BaseMetrics.父类非常小,是直接调用我们分析库的包装器。它还包含一个方便的方法,用于创建特定于我们的分析库的事件。

下面是一个例子BaseMetrics的样子。

open class BaseMetrics {fun fireEvent(event: event) {val instance = AnalyticsTool.getInstance()}}

这个问题

作为我们工作的一部分,我们需要从ViewModel中添加一个新的分析事件。我们很天真地遵循了上面的模式。代码按照预期运行,但我们很快就遇到了一个坏掉的单元测试。我们的静态构造直接调用了我们的分析库,结果ViewModel测试失败了。

这条线val instance = AnalyticsTool.getInstance()导致了这个问题。我们的AnalyticsTool从未打算在JVM中运行,因此破坏了我们的单元测试。我们能做什么?我们不能就这样离开。

我们的目标是什么?

至少,我们的更改不应该破坏测试。在最好的情况下,除了不破坏现有的测试之外,我们还希望通过测试来验证我们的更改。让我们志存高远,看看我们能想出什么来!

从接口开始

让我们从创建一个接口开始,该接口将定义我们想要发送给我们的分析客户端的值。

interface SessionMetrics {fun fireSelectedMsg(inboxSizeProperty: Int?, targetUserId:字符串?, matchedUser:用户?, initialNWays: Int?)}

我们正在调用我们的接口SessionMetrics它会有我们的事件方法fireSelectedMsg ()这包含了我们分析活动所需的所有数据。

创建一个实现

class AnalyticsLibSessionMetrics: SessionMetrics, BaseMetrics() {override fun fireSelectedMsg(inboxSizeProperty: Int?, targetUserId:字符串?, matchedUser:用户?, initialNWays: Int?){//创建我们的分析库特定事件//从我们传入的数据fireEvent(analyticsLibEvent)}}

然后像上面一样创建具体的实现并命名它AnalyticsLibSessionMetrics名称应该反映这个特定的实现正在使用的分析库。在这种情况下,它是AnalyticsLib.它实现了我们的SessionMetrics接口上面除了实用程序BaseMetrics如果你使用的是FirebaseFirebaseSessionMetrics

现在,我们可以像这样将新创建的类传递给ViewModel:

MessageThreadViewModel (AnalyticsLibSessionMetrics ())

使用假的修改测试

让我们通过创建一个表示已声明接口的假接口来启动测试并再次运行。

class FakeSessionMetrics: SessionMetrics {override fun fireSelectedMsg(inboxSizeProperty: Int?, targetUserId:字符串?, matchedUser:用户?, initialNWays: Int?){//什么都不做}}

用我们的假声明,我们可以从测试中创建我们的ViewModel,像这样:

val fakeSessionMetrics = fakeSessionMetrics () MessageThreadViewModel(fakeSessionMetrics)

现在我们的测试又开始工作了,因为我们并没有调用我们的分析库。感觉我们可以做得更好!让我们验证一下事件是否真的被调用了。

使用假的

让我们添加一个实例变量FakeSessionMetrics

FakeSessionMetrics: SessionMetrics {var selectedMsgEventFired = false override fun fireSelectedMsg(//许多参数){selectedMsgEventFired = true}}

实例变量selectedMsgEventFired在调用假事件方法时设置为true。然后我们可以从测试中验证我们的fireSelectedMsg ()方法的调用。

为了(fakeSessionMetrics.selectedMsgEventFired) .isTrue ()

有些地方还是不对

参数fireSelectedMsg ()方法不是我们传递给分析客户的值。我们对数据进行一些处理,以获得一些值,然后将这些值发送给我们的分析客户端。代码库中的当前模式将把这个处理逻辑放在ViewModel中。我现在的设计中就有这个SessionMetrics它似乎不属于这两家公司。

为什么它不属于任何一个?

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

如果我们把这个处理逻辑放到helper的具体实现中SessionMetrics,我们就无法进行测试了。我们会遇到测试失败的问题,因为我们直接调用了分析库。处理逻辑与我们的具体实现有什么关系吗?

数据作为类型

让我们看看我们将向我们的分析客户端发送什么。

val hasRead: Boolean, val targetUserId: String?, val indays: Int, val inboxSize: Int

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

数据类SelectedMessageEvent(val hasRead: Boolean, val targetUserId: String?, val indays: Int, val inboxSize: Int)

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

interface SessionMetrics {fun fireSelectedMsg(selectedMessageEvent: selectedMessageEvent)}

这简化了接口,并将分析库与处理逻辑解耦。我们现在对我们的分析库对这次活动的期待已经很明确了。

处理逻辑

我们可以将处理逻辑与数据类关联起来,因为它们在逻辑上是一起的。我们可以附加一个静态方法从()到我们的数据类:

数据类SelectedMessageEvent(val hasRead: Boolean, val targetUserId: String?, val indays: Int, val inboxSize: Int){ companion object { fun from( inboxSizeProperty: Int?, targetUserId: String?, matchedUser: User?, initialNWays: Int? ): SelectedMessageEvent { // processing logic } } }

我们的ViewModel调用如下所示:

val事件= SelectedMessageEvent.from(inSize, userId, matchedUser, nWays) sessionMetrics.fireSelectedMsg(事件)

这一切意味着什么

我们的ViewModel现在减少了责任。它创建了一个事件,尽管它不知道如何创建。它将事件发送到它不知道具体实现的接口。这一切都导致了改进的测试。我们现在可以在测试时断言我们的分析层被调用了。

分析层现在使用我们的自定义数据类和不需要处理的派生值。现在我们清楚地知道这一层需要什么。如果将来我们想要替换我们的分析库或添加另一个实现,它只需要消费我们的新数据类。

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

我们的测试现在可以工作了,我们可以在ViewModel中验证正确的行为,并且我们可以验证我们的事件数据类被正确创建。

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

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

最初发表在https://tech.188bet金宝搏官网okcupid.com2020年7月27日。

188bet金宝搏官网OkCupid科技博客

阅读来自OkCupid工程团队的故事,每天连188bet金宝搏官网接着数百万人

写的

10多年来,Brian一直对Android应用程序的开发充满热情。188博金宝电子体育频道他喜欢在会议和聚会上谈论Android。188博金宝电子体育频道

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网

写的

10多年来,Brian一直对Android应用程序的开发充满热情。188博金宝电子体育频道他喜欢在会议和聚会上谈论Android。188博金宝电子体育频道

188bet金宝搏官网OkCupid科技博客

188bet金宝搏官网OkCupid的工程团队负责每天为数百万人配对。在OkCupid科技博客上阅读他们的故事188bet金宝搏官网