一年三次,OkCupid开发者可以把我188bet金宝搏官网们的日常职责放在一边,享受“黑客周”,这是一个完整的工作周,让我们可以更自由地探索和学习想要解决的问题。

今年春天,我花了一个星期的时间,终于深入研究了浓缩咖啡的测试。我以前也用这个工具自动化过应用程序,但我一直在努力解决的一个问题是:网络嘲弄。

有很多指南,包括官方的浓缩咖啡这有助于你开始自动化你的应用程序,但如果你想写可靠的用户界面测试,你需要模仿你的网络层。如果不这样做,您的测试可能会不一致。让我们考虑在OkCupid中测试DoubleTake(这是主屏幕,您可以在其中向左/向右滑动用户卡来投票)。如果不嘲笑网络,我不知道:188bet金宝搏官网

  • 哪个用户将首先出现在卡片堆栈中。
  • 如果只有用户卡会出现(相对于广告或特别公告)。
  • 如果喜欢某个用户,即使我的测试没有预料到,也会导致相互匹配。

对于OkCupi188bet金宝搏官网d应用程序来说,模仿网络层似乎是一项特别具有挑战性的任务。我们现在不使用Dagger或其他依赖注入框架,而且我们的许多网络代码与应用程序类紧密耦合。不过,事实证明,这根本不是什么大问题!

我应该用什么工具?

当我第一次开始这项工作时,我试过WireMock。无线模拟是一个HTTP模拟工具,允许你在Android设备上运行一个HTTP服务器,你的应用程序可以与之通信,而不是与自己的服务器通信。我学会了WireMock188博金宝电子体育频道多亏了Sam Edwards在纽约Droidcon 2017,他深入解释了如何使用WireMock来模拟httpapi。如果您正在寻找WireMock的深入指南,我强烈建议您这样做。

然而,Sam也提到了另一个工具,叫做模拟Web服务器. MockWebServer是一个方形库,它实现了相同的目标——为您的HTTP响应运行一个模拟服务。MockWebServer实际上更轻,在尝试了这两种方法之后,我发现MockWebServer的设置要简单得多(添加WireMock作为gradle依赖项给了我一些必须解决的冲突)。既然MockWebServer具备了我所需要的所有功能,我决定继续前进。

地基

在我们继续实现MockWebServer之前,让我们讨论一下我们可以做的一些基础工作。MockWebServer将在本地主机上启动一个web服务器,这意味着我们的所有请求都将通过http://localhost:8080而不是http://myendpoint. 为测试应用程序交换端点可以通过两个步骤完成:

  1. 创建一个扩展普通应用程序类的TestApplication,并重写基本URL。
  2. 创建一个将使用您的测试应用程序的自定义JUnit运行程序。

创建测试应用程序

首先,让我们考虑一下我们有自己的应用程序类,它公开了我们的API url:

open class OkApp:Application(){open fun getApiUrl():String{return“http://apiurl" } }

在AndroidTest188博金宝电子体育频道目录中,创建一个扩展应用程序中使用的应用程序类的应用程序类:

类TestApplication:OkApp(){override fun getApiUrl():String{return“http://127.0.0.1:8080" } }

注意这里我刚刚重写了一个返回localhost API url的方法,而不是应用程序使用的方法。这是否意味着我们的应用程序的所有请求都将命中本地主机?不完全是,浓缩咖啡仍然使用我们的普通应用程序类。为了解决这个问题,我们需要一个定制的跑步者。

定制JUnit Runner

创建自定义JUnit运行程序只需要一个快速更新:重写新建应用程序方法并使其指向您的测试应用程序类,而不是原始的:

类MockTestRunner:Android188博金宝电子体育频道JUnitRunner(){重写fun onCreate(参数:Bundle?){ StrictMode.setThreadPolicy设置线程策略(StrictMode.ThreadPolicy.Builder().permitAll().build())超级.onCreate(参数)}重写fun newApplication(cl:ClassLoader?,类名:String?,上下文:上下文?):应用程序{返回超级新应用程序(第1条,测试申请:class.java.name类,上下文)}

然后,我们需要进入我们的应用程序的构建.gradle文件并将其配置为指向此运行程序:

188博金宝电子体育频道android{defaultConfig{testInstrumentationRunner'com.my.package包.MockTestRunner'}}

现在我们已经有了基础设置,并且我们的应用程序指向localhost,让我们在那里运行一些东西。

MockWebServer安装程序

我们可以从包含MockWebServer依赖项的复制/粘贴步骤开始:

188博金宝电子体育频道“androidTestImplementation”com.squareup.okhttp3公司:mockwebserver:${版本.okhttpVersion}"

接下来,我们需要为每个测试启动模拟web服务器。我们可以在测试类中的setup and teardown方法中配置:

@RunWith(188博金宝电子体育频道AndroidJUnit4::class)类MainActivityTest{//。。。private var mockWebServer=mockWebServer()@Before fun setup(){mockWebServer.start(8080)}@After fun teardown(){mockWebServer.shutdown() } // ... }

当我们启动web服务器时,我们给它一个运行的端口。这与我们在测试应用程序类中使用的端口相同;这一点很重要。如果您想在这里更安全一点,可以将端口移到build config字段中。

回应嘲讽

恭喜你成功了!我相信接下来的事情很多。让我们看看我们现在的处境:

  • 我们已经创建了一个TestApplication类,它允许我们重写普通应用程序将使用的任何内容。
  • 我们已经学习了如何实现我们自己的定制JUnit运行程序来使用这个TestApplication类。
  • 我们这样做的目的是,当我们的应用程序运行连接测试时,它将与localhost通信,而不是与您公司的服务器通信。

所有伟大的事情!不幸的是,我们还没有嘲笑任何回应。如果按原样运行测试,将看到大量404错误。我们来谈谈如何解决这个问题。

文件读取样板

概括地说,我们这里有两种方法:以编程方式从模型对象中生成模拟响应,或者从JSON文件中读取模拟响应(如果您对其中一种优于另一种的原因有什么想法,请告诉我)在Twitter上因为我真的不知道这里什么是最好的)。我选择了使用文件,我将简单地谈谈为实现这一点而添加的样板代码。

要将所有文件保存在以下位置:

app/src/debug/assets/network\u文件/端点_成功.json

我选择了给文件夹打电话网络文件但是你可以随便叫它什么。既然我们有了JSON文件,我们就需要一种读取它们的方法。我创建了以下内容资产总额.kt文件并将其存储在我的androidTest目录中:188博金宝电子体育频道

对象AssetReaderUtil{fun asset(context:context,assetPath:String):String{try{val inputStream=context.assets.open文件(“network\u files/$assetPath”)return inputStreamToString(inputStream,“UTF-8”)}catch(e:IOException){throw RuntimeException(e)}private fun inputStreamToString(inputStream:inputStream,charsetName:String):String{val builder=StringBuilder()val reader=InputStreamReader(inputStream,charsetName)reader.readLines文件().forEach{生成器.append(it)}返回生成器.toString() } }

现在我们有了这个,让我们用这些mock来看看。

调度员

MockWebServer使用一种叫做调度员处理模拟的服务器响应。它包括一个重写方法,它告诉我们请求是什么,并允许我们返回特定于该请求的模拟响应。

以下是我的调度员的工作方式:

  • 它接受一个上下文,该上下文用于读取我们在上一步中创建的文件。
  • 它包含一个映射属性,该属性将端点映射到其模拟响应的文件名。
  • 如果dispatch方法找到请求端点的文件名,它将返回模拟响应。
  • 如果没有找到模拟文件,我们将返回404。

以下是所有这些的代码:

类SuccessDispatcher(private val context:context=InstrumentationRegistry.getInstrumentation工具().context):Dispatcher(){private val responseFilesByPath:Map=mapOf(APIPaths.ENDPOINT\u一至MockFiles.ONE\u SUCCESS\u文件, APIPaths.ENDPOINT\u二至MockFiles.TWO\u SUCCESS\u文件)重写fun dispatch(请求:RecordedRequest?):MockResponse{val errorResponse=MockResponse().setResponseCode(404)val pathWithoutQueryParams=Uri.parse文件(请求?)?。路径)。路径?:return errorResponse val responseFile=responseFilesByPath[pathWithoutQueryParams]return if(responseFile!=null){val responseBody=asset(context,responseFile)MockResponse().setResponseCode(200).setBody(responseBody)}否则{errorResponse}}

我给这个调度员起了名字成功调度员因为它只返回成功响应。如果您想覆盖响应,我建议您远离端点到文件名的静态映射,而是公开一个方法,该方法允许您为每个端点定义应该模拟的内容。

开始测试前模拟

最后需要注意的是,您需要确保启动MockWebServer之前运行应用程序。您的应用程序初始化时可能会发出网络请求,因此如果您先启动应用程序(这是浓缩咖啡的默认设置),则可能无法按预期工作。您可以调整ActivityTestRule以在安装方法中启动,如下所示:

@RunWith(188博金宝电子体育频道AndroidJUnit4::class)类MainActivityTest{@JvmField@Rule var activityTestRule=activityTestRule(MainActivity::类.java,true,false)private var mockWebServer=mockWebServer()@在fun setup()之前{mockWebServer.start(BuildConfig.PORT端口) mockWebServer.dispatcher文件=成功调度程序()activityTestRule.launchActivity活动(null)}@之后有趣的撕裂(){mockWebServer.shutdown() } // ... }

样品

现在我们已经完成了所有这些,我们可以编写这样一个自动化测试,它为我自己使用所有模拟数据,而不是与真实用户交互:

数据传输自动化

如果你想看到一个超级基本的应用程序,使用这些工具的行动,请随意分叉此示例项目.

我希望这是有帮助的!如果您有任何问题,或其他独特的方式写MockWebServer调度,找到我的网站推特.

想和我一起去爱神丘比特吗?188bet金宝搏官网我们在招聘!