初学者是怎么做的?
小明一个刚入行安卓的小萌新,刚刚在测试小姐姐那里交过学费(挨过骂)了解到软件开发过程中是需要区分正式、测试环境的。但是他稍加思考就能想到测试、正式环境的区别仅仅是host不一样而已,其他的比如接口名、参数名、返回的json格式均一模一样。于是他马上找到了解决方案,平时都用测试环境的,到上线的时候再换回正式环境不就可以了?在一次开发中需要请求三个不同接口,说干就干,于是小明就写出了以下代码,准备在上线时全局搜索www.test.com改成www.release.com,提交以后开开心心下班撩妹去了。
//请求接口1
NetWorkUtil.request("http://www.test.com?action=a1")
//请求接口2
NetWorkUtil.request("http://www.test.com?action=a2")
//请求接口3
NetWorkUtil.request("http://www.test.com?action=a3")
三天以后,产品经理跑过来说要加一个需求,新增了好几个接口,小明表示自己表现的机会到了,一股脑儿全部包下来了。可是做着做着发现有点不对劲啊,每次请求接口的时候都需要复制http://www.test.com这个域名,可是产品锦鲤追的紧啊,没办法先就这么办吧,实现了需求上线以后再说。
一周过去了,上线时间到了。测试小姐姐又跑过来问小明这个正式包的数据怎么不对,还是测试环境的?小明赶紧道歉,想起来了上线前需要修改域名这个事情,自己居然忘记了。心里一万只草泥马飞过,小明赶紧的匆匆忙忙地改完项目里所有接口请求的地方,这才松一口气,给测试小姐姐买了个奶茶打了个新包,这个版本终于成功上线了,还真是不容易啊。
一天以后,小明被项目经理叫到了办公室,把小明狠狠批了一顿。原来是小明有个地方忘记改了,线上用户的操作被记录到了测试数据库了。项目经理为了解决这个问题,将最近一天测试环境的该数据全部导入到了正式环境才解决,当然还收到了不少的投诉。不过还好这个数据不是核心数据,不是那么重要,不然小明的机票估摸着差不多就到手了。
小明痛定思痛,坚决要杜绝这种低级错误。于是他把需要改域名这个事情已经记录到备忘录里每天提醒了,除此之外,聪明的他还想到了一个办法,就是用一个全局的变量对域名进行保存,在上线前只需要切换一次就行了,类似于这种:
companion object {
const val HOST = "http://www.test.com"
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
setContentView(R.layout.activity_main)
//请求接口1
NetWorkUtil.request("$HOST?action=a1")
//请求接口2
NetWorkUtil.request("$HOST?action=a2")
//请求接口3 NetWorkUtil.request("$HOST?action=a3")
}
小明终于没有犯线上的低级错误了,但是后面业务需求越来越繁杂,服务端使用的域名也越来越多,并且很多第三方的api比如推送、bugly监测等也都需要切换id。每次上线需要修改一堆的域名和id。小明每天心态都跟高考一样,紧张又害怕,生怕自己再出问题,被强制送机票。而且就算在测试阶段,测试小姐姐偶尔也会让他打一个release包测试,虽然心里千万只草泥马不愿意,但是也没办法,先改成线上的域名吧,打完以后再改回来呗!于是小小明逐渐地熟悉了这一套切换方式,直到他看到了那一篇技术博客,小明他。。。哭了。
熟练开发者是怎么做的
小明看到的文章正是一篇关于测试和正式环境切换的技术文章,该文通俗易懂,还提供了完整的方案,小明看完觉得这不就是为自己准备的么,于是按照文章里的方式尝试了起来。文章中说到可以根据当前app是debug还是release来切换host,大概实现如下,首先在Application的onCreate()中获取到当前是否是debug模式,并且用静态变量进行记录,接下来需要区分测试、正式环境的时候就根据这个flag来判断即可。
class MyApplication: Application() {
companion object {
var IS_DEBUG = true
}
override fun onCreate() {
super.onCreate()
IS_DEBUG = (applicationInfo.flags != 0 && ApplicationInfo.FLAG_DEBUGGABLE != 0)
}
}
小明收到了该文章的启发,于是在项目所有需要区分测试、正式环境的地方都对上面的flag进行了判断,其代码大致如下:
companion object {
val HOST1 = if(MyApplication.IS_DEBUG)"http://www.test1.com" else "http://www.release1.com"
val HOST2 = if(MyApplication.IS_DEBUG)"http://www.test2.com" else "http://www.release2.com"
val HOST3 = if(MyApplication.IS_DEBUG)"http://www.test3.com" else "http://www.release3.com"
}
到了这里终于小明终于可以松口气不用设置备忘录,每次上线不用为了改域名问题而提心吊胆了,域名会智能地根据当前是debug包还是release包还自动赋值。但是后面加在这里的域名和第三方api越来越多,于是小明还在此基础上举一反三,进行了一波优化。小明了解到在系统打包的时候,如果在build.gradle文件中的buildTypes里添加debug和release的相应配置,系统在build/generated/source/buildConfig目录下会自动生成BuildConfig类,系统自动生成的类大概如下:
/**
* Automatically generated file. DO NOT MODIFY
*/
package xx.xx.xx;
public final class BuildConfig {
public static final boolean DEBUG = false;
public static final String APPLICATION_ID = "xx.xx.xx";
public static final String BUILD_TYPE = "debug";
public static final String FLAVOR = "";
public static final int VERSION_CODE = 1;
public static final String VERSION_NAME = "1.0";
}
这里面的字段是可以添加的,比如在build.gradle中设置好需要区分测试、正式环境的host,可以先按如下规则定义好
buildTypes {
debug {
buildConfigField "String", "URL", ""www.test.com""
}
release {
buildConfigField "String", "URL", ""www.release.com""
}
}
则在编译的时候,系统会自动在BuildConfig中加入以下代码
public static final boolean DEBUG = true;
public static final String URL= "www.test.com";//如果是release包中会自动生成www.release.com
我们可以看到,在实际开发的时候根本不需要去设置当前是哪个域名,而是系统会自动来判断,从而在实际的业务需求开发时我们只需要使用BuildConfig.URL即可,小明将所有的域名以及第三方sdk需要的appkey都放到了buildTypes里,于是小明的代码可以改成这样子了:
//请求接口1
NetWorkUtil.request("${BuildConfig.URL}?action=a1")
//请求接口2
NetWorkUtil.request("${BuildConfig.URL}?action=a2")
//请求接口3
NetWorkUtil.request("${BuildConfig.URL}?action=a3")
当然这里URL最好封装到Common层中,这里就不多说,不是本文的重点。经过这一波的修改终于不用在每次上线时都修改URL了,而是系统会自动选择好URL,我们直接使用就可以了。
大型项目是怎么做的
按照上面的方式,一些小型的项目基本就没问题了。但是用在大型项目里问题就比较明显了,我这里随便列举几个吧。
** 1.大项目的测试环境有可能不止一个,那么具体用哪个还得做一个手动切换入口**
** 2.大厂的测试人员经常测试出指定response数据下的bug,开发人员拿到bug以后要连测试的代理,恢复现场才能复现,影响测试人员测试其他的bug,而且不方便查看抓包信息**
** 3.在日常的开发测试时安装的是debug包,所以在上线以后,想看一下线上的效果又需要卸载重新安装release包,等下个版本开发时又卸载线上的安装debg版,这一波操作非常的麻烦**
那么,有没有办法可以解决以上的问题呢,答案当然是有的,这就是笔者今天要给大家重点介绍的host映射法。
什么是IP地址
在此之前,首先给大家简单介绍一下IP地址。大家肯定都知道,每台在因特网的电脑都会有一个唯一的IP地址,IP地址相当于是你电脑的名字,其他电脑就通过IP地址来访问你的地址,就相当于大家通过喊你的名字来将你和其他人进行区分一样。
什么是域名
接下来再给大家介绍一下域名,域名就相当于别人给你起的外号,别人嫌弃你的本名叫起来不顺口,就喜欢叫你外号。做过服务端开发的同学肯定知道,就算没有域名,只凭ip地址一样可以访问到服务器。但是如果让你们访问淘宝京东的时候,每次输入xx.xx.xx.xx这种ip地址大家肯定不会喜欢的,也没人能记得住,但是taobao.com就不一样了,朗朗上口,过目不忘。
什么是DNS解析
最后再给大家介绍一下DNS解析,这个名词听上去很高大上,但是其实理解起来特别简单。DNS解析就是将域名和IP给匹配上的过程,不然谁知道你的外号是给谁起的,肯定会有公证人来统计这一一对应的信息。在我们访问网址的时候,表现上是访问的域名,但其实中间会经过一层dns解析,最终变成了访问ip地址。
![](https://upload-images.jianshu.io/upload_images/22796403-d1f55c73746001ec.png?imageMogr2/auto-orient/strip|imageView2/2/w/650/format/webp)
如上图,例如小明想要访问种子网站1,于是小明在浏览器里输入了www.zzwz1.com,接下来浏览器会拿着小明输入的域名访问DNS解析中心,询问小明的网站ip是多少,询问到了以后才能真正地访问种子网站1,而不是错误地访问到种子网站2。
知道了上面那些概念以后,有一个想法油然而生,那我是不是可以通过修改解析方式来区分测试环境和正式环境呢?如果可以的话,我们项目里就让它一直是www.release.com好了,在我需要测试的时候,我把这个域名解析到测试机器上,这样不就访问到了测试环境了么?
![](https://upload-images.jianshu.io/upload_images/22796403-a97ab55cbb195d15.png?imageMogr2/auto-orient/strip|imageView2/2/w/786/format/webp)
如何手动修改hosts
事实证明这样做是可以的,而且是合法正规渠道就可以做到了。以windows为例,在c:windowsSystem32driversetc下面有一个hosts文件,该文件就是Windows官方提供用来修改DNS解析用的,凡是在该文件里定义的解析方式将不再走入正规的解析流程。
![](https://upload-images.jianshu.io/upload_images/22796403-55d31a4f3049e76e.png?imageMogr2/auto-orient/strip|imageView2/2/w/737/format/webp)
我们打开该文件,Window也有一些对该文件的介绍,大概就是说我们可以通过写入IP地址然后空格域名的方式指定该域名解析的IP地址,从而覆盖其真正的主机。有过一些后端基础的同学可能知道,为了方便我们经常将127.0.0.1为了方便从而替换成localhost,为的就是在访问本机网址的时候可以通过localhost的方式,写起来更简单一些。
![](https://upload-images.jianshu.io/upload_images/22796403-c417e8e5b5c3d081.png?imageMogr2/auto-orient/strip|imageView2/2/w/575/format/webp)
所以我们需要做的就是在测试的时候修改这个hosts文件,比如测试环境下服务端的ip地址为yy.yy.yy.yy我们只需要在hosts文件里加上以下一句话即可
yy.yy.yy.yy release.com
这样一旦我们app访问release.com的时候,本机就会将该域名指向IP为yy.yy.yy.yy的电脑。
服务端需要做什么呢?
服务端首先需要将测试环境和正式环境分别部署在不同的电脑上,其次正式环境和测试环境的域名都保证是同一个,这两条在稍微正规一些的项目应该都是没问题的。接下来服务端只需要告知客户端所有测试服务端的ip地址,客户端同学将所有的ip都保存到hosts里面,在使用的时候相应地放开某些限制就可以了,做法如下图:
![](https://upload-images.jianshu.io/upload_images/22796403-c10ea6cec6892ea4.png?imageMogr2/auto-orient/strip|imageView2/2/w/337/format/webp)
在需要使用测试环境1时就放开相应地限制,让该域名走向自己想要地服务端。如果想要切换回线上,也不需要修改任何客户端代码,甚至不需要重新打包,而是修改一下hosts将所有测试环境地限制全部取消即可,是不是非常地方便呢!
host太多怎么办
在复杂项目中,经常会出现多个域名的情况,这样每次切换环境需要改的host条数太多,还真是有点麻烦,这里可以通过一些专业切换host的工具来解决,比如switchhosts工具就非常好用,可以自定好平时工作中常用的几套host组合,保存到switchhosts中,可以实现一键切换
![](https://upload-images.jianshu.io/upload_images/22796403-275b215ce3d1765a.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
手机是否可以切换
以上切换host是在电脑上实现的,所以还需要测试机和电脑连同一个网,并且使用charles、fiddler等代理工具对手机网络进行代理。如果自己只需要修改而不需要抓包的话,同样可以下载手机上的代理app,类似app有很多,比如知名的HostGo就很好用。
![](https://upload-images.jianshu.io/upload_images/22796403-f3a1bfb814f89eef.png?imageMogr2/auto-orient/strip|imageView2/2/w/580/format/webp)
切换hosts方式的弊端
1.对服务端要求更高
2.开发时不小心hosts忘记切换的话会把数据传入到正式环境中,或者反之
3.和服务端沟通成本较高
三种方式对比
操作复杂性:第一种方式由于需要复制粘贴进行替换,所以还是有一些工作量的,第二种要看当前项目是否有多个测试环境,如果有的话则也需要测试人员去手动切换,如果没有的话就不需要任何操作,第三种方式由于切换的时候需要修改host,也是需要操作的。
容错性:第一种方式有可能会替换错误,所以容错性差,其他两种基本都不会有问题
规范性:规范性是从代码质量的方面来看,第一种方式是一直修改老代码,明显是低分,第二种方式存在需要切换不同测试环境的可能性,需要在debug模式下加一些只用来测试的和功能无关的代码。
沟通成本:第二种方式在和测试联调的时候,可能会更费劲一些,比如具体是哪个接口,还要把response数据发来本地maplocal才能复现问题。而切换host的方式,也需要和测试人员沟通当前复现的测试环境是哪个,并且需要和服务端沟通获取到测试环境的IP地址。如果测试人员测试出了接口bug,只需要把其host文件发来替换本地的host文件就可以复现问题了。
拓展性:拓展性是从功能的强大程度来对比,全局替换的方式,每新增一个host就需要多替换一个,到后面就越来越重,第二种的话由于在新增测试环境方面表现欠佳,每次新增一个测试环境就需要修改客户端代码来提供切换入口,所以也不能给高分。
是否需要重新安装:在测试的时候的apk包放到线上时,由于接口需要替换,所以肯定需要重新打包,但是使用切换host的方式,如果测试的时候就是用的release包,那么可以直接上线,就算是debug包,也一样把hosts文件里的dns限制注释掉就可以访问线上数据的。
![](https://upload-images.jianshu.io/upload_images/22796403-97a0a244c4475ec0.png?imageMogr2/auto-orient/strip|imageView2/2/w/724/format/webp)
总结
本文笔者以工作中必用的测试、正式环境切换为题,介绍了不同经历的同学分别是怎么实现的,最后重点介绍了笔者推荐的切换Host的方式。大家项目里用第二种还是第三种根据项目情况而定,如果是小项目的话用第二种就足以,但是第三种大家也还是要能看懂,除此之外,DNS也是大厂在面试的时候喜欢问的一个点。最后多谢同学们耐心地看完了文章,对里面内容有疑义或者有不懂的地方欢迎一起探讨,共同进步!
作者:苹果味的少年
链接:https://juejin.im/post/5eabae5ce51d454dc93ddf06