Macaca 面向多端的自动化测试解决方案

背景


对于基于 UI 的功能测试的需求其实一直存在,理由其实很简单,不想一直让人去做重复机械的事情,而且可靠性完全是靠人力的堆积产生。然而目前部门的功能测试工作依然主要是依靠人工来完成,从我们公司的实践来看我觉得有几个方面的影响因素:

客户端APP已经实现模块化开发,而且外卖平台移动端的开发迭代流程正在进行改造,目标是从固定每三周一个迭代改造为每周一个发布窗口,版本迭代的提速,设备的碎片化,都给测试工作带来巨大的挑战。
由于版本迭代周期越来越短,而且UI变动比较频繁,因此测试编写测试代码的积极性不是很高,同时由于测试代码的可重复利用性差,导致测试脚本的编写成本和维护成本偏高 。
部分测试人员的编码能力不是很强。由于大部分测试人员可能并没有过多的开发经验,所以在编写测试代码时并不能很顺畅的完成自己想要的效果,这样也会导致测试代码项目的推广阻力会比较大。

如何在有限的时间内,追求尽可能高的产品质量?录放平台是我们推出的解决方案。它支持本地化UI脚本录制,集中式脚本管理,分布式脚本执行。业务测试只要开启我们的服务,就可以在业务测试的过程中,自动生成对Android、iOS和Web页面的自动化脚本,而自动化脚本在批量设备上的回放,可以极大提高关键路径的覆盖率,提升兼容性测试的效率,从而可以把业务测试从冗长重复的步骤中解放出来,把精力放到边界,异常等可以给我们产品带来更多提升的地方。

通过不断地寻找,不断地对比,最终我们将目标聚焦在阿里巴巴开源解决方案Macaca上。

简介


Macaca是一套完整的自动化测试解决方案,它的三个特性对我们极具吸引力:

1、周边工具支持(Reliable、app-inspector、UI-Recorder等)
2、它是一个轻量化的开源项目
3、社区活跃,中文文档丰富
4、支持JS、Python、Java编写自动化脚本
5、API比较统一

技术栈


在落地Macaca之前,需要先部署下列技术栈:
1、Node.js用于部署Macaca
2、Docker用于容器化Macaca的部署环境
3、Gitlab用于存储代码和测试用例
4、Slack用于团队的沟通协调
5、Python用于部署本地Agent

使用流程


业务测试人员通过在本地录制好测试脚本,然后上传到脚本管理平台,这些测试脚本将会根据业务模块和版本分类管理。使用者在自己的电脑上安装Agent,然后连接测试设备,Agent会将本机的ip、port和设备信息上传注册到录放平台。

新建一个task执行脚本回放操作,可以指定在哪些机器上回放也可以推送到STF手机管理平台批量回放,测试用例运行之后,会有两种情况发生:如果成功,则可以直接查看生成报告;否则会通过Slack或邮件通知开发人员测试失败,重新修改代码。

另外Macaca也提供了相应的分布式持续集成框架Reliable来进行任务管理。

Reliable


下图是Reliable的界面,通过Reliable用户可以查看测试用例和测试结果;并且Reliable天生与Macaca无缝衔接。

Inspector


Macaca中还提供了Inspector工具供用户直观、方便查找到想要选中的元素。图中右侧一栏提供的是XPS、ID、Name数据,用户通过Inspector工具寻找目标界面的元素。

Debug


我们选择Visual Studio Code作为常用的IDE因为它能够轻量地、方便地支持使用者Debug,用户可以根据自己喜好选择相应地调试工具。

UI Recorder


下图是简单的登录测试用例:输入用户名和密码,然后点击登录按钮。UI-Recorder脚本录制工具可以快速的通过录制得到脚本,方便新手入门。

测试报告


最终的测试结果需要与饿了么的质量平台对接(Macaca产生的测试报告、测试结果数据在导入饿了么质量平台前需要进行数据转换),形成完整的测试流程。

上面总结了一下自己在调研并选择UI自动化框架中的一些思考,希望能给处于UI自动化调研初期的同学们一些帮助,其中很多选择是出于自身业务的需要,仅供参考,希望大家能结合自身业务的需要,找到适合自己的UI自动化框架。另外如果有对此框架感兴趣的同学欢迎一起学习交流。


全栈增长工程师,欢迎关注

Share Comments

配置Android项目 - 版本名称和代码

Version Name & Code

开发人员通常给android versionName&versionCode使用一些硬编码值。

42

这种方法有几个缺点:

  • 你永远不知道哪个提交代表一个特定的版本。
  • 每当你增加versionCode和更改versionName,你必须修改build.gradle文件。

如果你使用git作为源代码控制系统,它也可以帮助你生成android versionName&versionCode。常见的做法是使用git标签来指示新版本的发布。43

Version Name

对于versionName,我们可以使用git describe命令。

a.该命令查找从提交开始可访问的最近的标记。

b.如果标记指向提交,则只显示标记。

c.否则,它将标记名称与标记对象顶部的附加提交数以及最近提交的缩写对象名称作为后缀。

Example (a-b)

44

  1. 使用tag1.0标记特定提交
  2. 签出此提交
  3. 调用git describe -tags
  4. 输出:1.0

正如你看到的,如果你使用一些标签在一个HEAD提交上调用git describe,它会输出这个标签。

Example (a-c)

45

  1. 标记tag为1.0的提交
  2. 再添加两个提交
  3. 调用git describe -tags
  4. 输出:1.0-2-gdca226a

46

使用git提交哈希“1.0-2-gdca226a”,我们可以很容易地找出从哪个特定的提交构建。

47

Version Code

对于versionCode,我们可以使用标签的总数。因为每个git标签指示一些版本,下一版本的versionCode将总是大于previous。

48

在上面的示例中,我们有3个标签。这个值将用于我们的versionCode。

但是我们不会为每个中间版本创建一个git标签,因此对于dev build我们可以使用HEAD提交的时间戳。

49

在上面的示例中,HEAD提交的时间戳等于1484407970(自UNIX纪元1970年1月1日00:00:00 UTC以来的秒数)。这个值将用于我们的versionCode。如果你想将它转换为人类可读的日期使用currentmillis.com网站。在我们的情况下,它是Sat 1月14日2017 15:32:50 UTC。

Groovy way to use git

要使用git我建议使用一个称为grgit的库。创建具有以下内容的script-git-version.gradle文件:

50

将其应用于您的build.gradle文件:

51

要检查version name 和 code是否正确生成调用gradle任务./gradlew printVersion它给出类似的输出:

52

最后在build.gradle文件中使用gitVersionName,gitVersionCode和gitVersionCodeTime变量。

53

运行项目并验证应用版本。

54

这种方法的好处:

  • 不需要修改build.gradle文件 - versionCode和versionName是自动生成的。
  • 你可以很容易地找出从哪个提交生成。

注:你可以尝试其他的方式来标记版本名:包括分支名称,时间戳等。


全栈增长工程师,欢迎关注

Share Comments

配置Android项目 - 静态代码分析工具

静态代码分析工具

静态代码分析工具 - 分析代码而不执行它。通常用于发现错误或确保符合编码指南。有助于保持你的代码健康,并保持代码质量。

在Android上,最流行的代码分析工具是:

  • Lint
  • PMD
  • Findbugs

我通常将静态代码分析脚本和相关文件保存在单独的文件夹中。

Lint

lint工具检查你的Android项目源文件是否存在潜在错误,并针对正确性,安全性,性能,可用性,可访问性和国际化进行优化改进。

配置

添加lint到你的android项目创建script-lint.gradle文件。55

重要的lint选项:

  • lintConfig —lint规则集的路径(可以用来配置压制警告)。
  • htmlOutput —html报告生成的地方。

将script-lint.gradle导入到build.gradle文件。

56

测试

重新构建你的项目,然后使用./gradlew lint命令运行lint。如果它发现一些问题,你会看到类似下面的输出。

57

当你打开lint.html报告文件时,你将看到问题列表描述,和如何解决它们的建议。

58

如果你想忽略此问题,请将以下规则添加到rules-lint.xml文件中。

59

注意:还有其他方法可以压制lint警告。有关lint的更多信息,请访问官方网站。

Findbugs

静态代码分析工具,用于分析Java字节码并检测各种各样的问题。

配置

要添加findbug到你的android项目需要创建script-findbugs.gradle文件。

60

重要的findbugs选项:

  • excludeFilter —findbugs规则集文件所在的路径,你可以在其中压制问题。
  • classes — 生成的类的路径(如果你有多个flavor,路径由flavor名称组成,在当前情况下为“dev”)。
  • source —源代码的路径
  • html.destination —html报告生成的路径

将script-findbugs.gradle导入到build.gradle文件。

61

测试

为了测试,我们将创建以下方法。

62

重新构建你的项目,然后运行findbugs ./gradlew findbugs命令。如果它发现一些问题,你会看到类似下面的输出。

63

当你打开findbugs.html报告文件,你将看到问题列表与说明和如何解决它们的建议。

64

如果你想忽略此问题,请将以下规则添加到rules-findbugs.xml文件中。

65

注意:还有其他方法去压制findbugs警告。有关findbugs的更多信息,请访问官方网站。

PMD

PMD是一个源代码分析器。它发现常见的编程缺陷,如未使用的变量,空catch块,不必要的对象创建等等。

配置

要添加pmd到你的android项目那么需要创建script-pmd.gradle文件。

66

重要的pmd选项:

  • ruleSetFiles —pmd规则集文件的路径,你可以在其中压制问题并定义要跟踪的问题。
  • source —源代码的路径
  • html.destination —html报告生成的路径

将脚本script-pmd.gradle导入到build.gradle文件。

67

测试

为了测试,我们将创建以下方法。

68

重新构建你的项目,然后使用./gradlew pmd命令运行pmd。如果它发现一些问题,你会看到类似下面的输出。

69

当你打开pmd.html报告文件,你将看到问题列表与说明和如何解决它们的建议。

70

如果你想忽略此问题,请将以下规则添加到rules-pmd.xml文件中。

71

注意:还有其他方法压制pmd警告。有关pmd的更多信息,请访问官方网站。


全栈增长工程师,欢迎关注

Share Comments

配置Android项目 - 一些重要的事情

gitignore

当你在Android Studio中创建一个新的Android项目时,它已经生成了gitignore文件,但通常它不包含所有必要的规则。

为了快速生成和下载gitignore文件,我建议您使用gitignore.io网站。只需输入必要的关键字,如 — Android,Intellij并点击生成按钮。

1

在模板项目中查看gitignore文件。

tools folder

如果你有一些第三方脚本,规则集或其他与您的项目相关的文件不要只是简单的把它们放在根目录 —它会造成混乱。(特别是对于那些使用Project视图,而不是Android视图)

尝试创建一个文件夹(例如tools),并将所有这些文件放入此文件夹。

2

通常我在那里放一些自定义的gradle脚本文件,proguard和静态代码分析工具的规则,如pmd,findbugs,lint。

在模板项目中查看 tools文件夹。

flavors

Flavours用于创建具有不同设置的构建。在大多数情况下,我会立即设置两种flavors — dev和prod:

  • applicationId
  • versionCode / versionName
  • server endpoints
  • google services keys

3

在模板项目中查看 productFlavors

keystore

keystore是一个二进制文件,其中包含一个或多个用于签署应用程序的私钥。

当从IDE运行或调试项目时,Android Studio会使用Android SDK工具生成的调试证书自动为您的APK签名。

使用本地调试keystore时有几个问题:

  • 到期日365天
  • 从多台计算机安装应用程序需要先卸载
  • google服务需要密钥库SHA-1指纹

这就是为什么我通常生成调试密钥库并提交到版本控制系统。

4

在模板项目中查看 signingConfigs

proguard

Android proguard用来做三件事:

  • 压缩未使用的代码 — 帮助你不超出64k限制
  • 优化代码和apk
  • 混淆代码 — 使你的APK难以做逆向工程

问题是混淆和代码优化显着增加了编译时间,使调试更困难。

这就是为什么最好对发布和调试版本使用不同的proguard规则:

  • rules-proguard.pro
  • rules-proguard-debug.pro

5

用于调试构建的Proguard规则必须具有以下行以强制proguard忽略警告,跳过代码混淆和优化:

6

对于发布版本,设置proguard规则将会更加困难,因为几乎每个库都有自己的特定规则。幸运的是,有一个开源代码库 —  android-proguard-snippets,它包含所有主要库的proguard规则。

7

在模板项目中查看 rules-proguard.prorules-proguard-debug.pro

strict mode

Android StrictMode可帮助您检测不同类型的问题:

  • 可关闭对象没关闭
  • 在主线程中读写文件或者访问网络
  • uri 暴露

每当检测到这样的问题,它可以显示适当的日志或应用程序崩溃,具体取决于你的配置。

我建议你只在调试的时候打开它并且使用detectAll方法来检测所有类型的问题。

8

这里是当你忘记关闭SQLiteCursor的日志的例子:

9

在模板代码中查看StrictMode


全栈增长工程师,欢迎关注

Share Comments

Model-View-Presenter:Android指南

原文地址:https://medium.com/@cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf#.nqgbpr2bj

网上有很多关于MVP架构的文章和示例,并且有很多不同的实现。但开发者社区仍不断努力,想以尽可能最好的方式将此模式应用在Android上。

如果你决定采用这种模式,你正在做一个架构选择,你必须知道你的代码库将改变,以及你新的功能也要用新的方法来开发。另外你需要面对常见的Android问题如Activity生命周期,然后你还应该问问自己下面这些问题:

  • 我应该保存presenter的状态吗?
  • 我应该将presenter做持久化处理吗?
  • presenter需要有生命周期吗?

在本文中,我将提供一系列准则或最佳做法,以便:

  • 解决采用这个架构遇到的最常见问题(至少是一些我遇到过的)
  • 发挥这个架构的最大优势

首先,让我们先解释一下这个模式:

1

  • Model:它是负责管理数据的接口。模型的职责包括使用API,缓存数据,管理数据库等。该模型还可以是与负责这些职责的其他模块通信的接口。例如,如果你使用Repository模式,则模型可以是Repository。如果你使用的是Clean架构,那么Model可以是一个Interactor。
  • Presenter:presenter是model和view的中间人。你的所有业务逻辑都应该放在这里面。presenter负责查询model和更新view,对更新模型的用户交互作出反应。
  • View:它只负责以presenter定义的方式来显示数据。view可以被Activities、 Fragments、任何Android widget或者其他一些像显示ProgressBar、更新TextView、填充RecyclerView等等可执行操作的视图。

下面是以我的观点列出的一些指南,你可能不会全部赞同,不过我会试着解释为什么这么做。

1. 让View变得被动和无知

Android中最大的一个问题就是view(Activities、Fragments等)不是那么容易被测试因为Android框架很复杂。为了解决这个问题,你需要实现Passive View模式。这种实现方式通过利用一个controller来减少view的业务行为,在我们的例子中,这个controller是presenter。这种方式显著的提高的代码的可测试性。

例如,如果你有一个username/password的表单和一个提交按钮,你不需要在view中写验证逻辑而是将它写在presenter中。你的view只管接受用户名和密码的输入然后将他们传递给presenter即可。

2. 使presenter与框架无关

为了提高代码的可测试性,那么就要确保presenter不能依赖Android类文件。presenter用纯java代码实现的两个理由:首先你要将具体的实现抽象到presenter中,这样的话你就可以写不依赖于设备的测试代码了(甚至都不需要Robolectric),可以快速的在你的本地JVM中运行而不需要模拟器。

如果我需要用到Context呢?

那么就不要用它。在这种情况下,你应该问一下自己为什么需要context呢。我猜你可能想要存储数据或者获取资源。但是你不需要在presenter中做这些:你可以在view中获取资源,在model中存储数据。这里只是两个简单的例子,不过我敢打赌大多数情况下都是因为类的职责不明确导致的。

顺便说一下,依赖倒置原则可以帮助你在这种情况下解耦。

3. 写一个contract类来描述View和Presenter之间的交互

当你准备开始写一个新功能时,第一步最好先写一个contract类。contract描述了view和presenter之间的交互,它帮助你以更干净的方式设计交互。

我喜欢用Google在 Android Architecture repository中建议的解决方案:这个contract接口类中包含两个接口一个是view另一个是presenter。

让我们举个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface SearchRepositoriesContract {
interface View {
void addResults(List<Repository> repos);
void clearResults();
void showContentLoading();
void hideContentLoading();
void showListLoading();
void hideListLoading();
void showContentError();
void hideContentError();
void showListError();
void showEmptyResultsView();
void hideEmptyResultsView();
}
interface Presenter extends BasePresenter<View> {
void load();
void loadMore();
void queryChanged(String query);
void repositoryClick(Repository repo);
}
}

看到这个方法的名字,你应该就明白这个例子中的contract是干什么的了吧。

如果你还不知道,那一定是你的问题哈哈。

在这个例子中你可以看到view中定义的方法非常简单而且不包含任何逻辑。

The View contract

正如我之前说过的,view接口是要被Activity或者Fragment实现的。presenter必须依赖于view接口而不是直接依赖于Activity:通过这种方式,你可以将presenter从视图实现解耦,遵循SOLID原则的D:“依赖抽象,不要依赖具体实现)。

我们不需要更改presenter中的一行代码就可以替换具体的视图。因此我们可以非常容易的通过创建一个mock view来进行单元测试。

The presenter contract

等等,我们真的需要一个Presenter接口吗?

事实上不需要,但我认为还是要的。

关于这个话题有两种不同的思想流派。

一些人认为应该写一个Presenter接口因为你要将具体的presenter和view解耦。

然而另外一些开发者认为你在抽象的东西已经是一个抽象的了所以不需要再写一个接口了。另外不管怎么样,有了一个接口后可以帮你方便的写mock presenter,不过如果你采用了Mockito这样的工具类那么你就不需要接口了。

我个人还是喜欢写这么一个Presenter接口的,下面是两个简单的理由:

  • 我不是去为presenter写一个接口而是写一个Contract类来描述view和presenter之间的交互。
  • 写这么个接口并不费什么力。

我已经这么写超过一年了甚至更长,至今没有发现什么问题。

4. 定义一个名称方便区分责任

presenter通常有两种类型的方法:

  • Actions(e.g: load()):presenter的一些行为操作。
  • User events(e.g:queryChanged(…)):用户触发的操作比如在搜索框中键入字符或者是点击列表中的某个选项。

你定义的action越多那么view中的逻辑也就越多。

当用户滚动到列表的结尾时将调用loadMore()方法,然后presenter加载另外一页的结果。这意味着当用户滚动到结尾时,view知道必须加载新页面。我可以命名方法onScrolledToEnd()让具体的presenter处理具体做什么。

我想说的是,在“contract设计”阶段,你必须定义好每个用户事件,相应的action是什么,逻辑应该属于谁。

5. 不要在Presenter接口中创建Activity-lifecycle-style回调

我使用这个标题的意思是presenter不应该有像onCreate(…),onStart(),onResume()等方法原因如下:

  • 如果这么做了的话presenter将会和Activity产生耦合。如果我想用一个Fragment替换Activity怎么办?我什么时候应该调用presenter.onCreate(state)方法?在fragment的onCreate(…)、onCreateView(…)还是onViewCreated(…)中?如果我使用自定义view怎么办?
  • presenter不应该有这么复杂的生命周期。事实上,主要的Android组件都是以这种方式设计的,但并不意味着你必须也这么做。如果你有机会可以简化的话那就简化它吧。

6. Presenter和view有1对1的关系

如果没有view的话presenter就没有意义了。presenter随着view一起被创建也随着view一起被销毁。一个presenter管理一个view。

你可以通过多种方式处理presenter中view的依赖。一种方式是在presenter接口中提供像attach(View view)和detach()的方法就像之前例子中展示的那样。不过这样做有一个问题就是你需要注意view是否为null,每次presenter用到它的时候都要检查一下是否为null。这点确实有点烦……

我说了presenter和view是一对一的关系。我们可以利用这一点,实际上具体的presenter可以将view实例作为构造函数的参数传入。顺便说一句,你可能需要一个方法来订阅presenter的一些事件。所以我建议定义一个方法start()(或类似的方法)来运行Presenter中的业务。

关于detach()呢?

如果你有一个叫start()的方法,那么你可能至少还需要一个来释放依赖的方法。既然我们定义订阅presenter一些事件的方法叫start(),那么另一个方法就叫stop()吧。

1
2
3
4
public interface BasePresenter<V> {
void attach(V view);
void detach();
}
1
2
3
4
public interface BasePresesnter {
void start();
void stop();
}

7. 不要在presenter中保存状态

我的想着是要用Bundle来保存。但考虑到上面的第二条准则就不能这么做了。你不能将数据序列化到Bundle中,因为这样的话presenter就与Android类耦合了。

我说presenter应该是无状态的,但其实也不然。在我之前描述的例子中,presenter应该至少具有页码/偏移量之类的状态。

8. 不要持久化presenter

我不喜欢这种方式主要是因为我认为presenter不是我们应该持久化的,要清楚它不是一个数据类。

一些建议提供了一种在配置发生改变的时候通过恢复fragments或者 Loaders的方式记住presenter的状态。我不认为这是最好的解决方案。通过这种方式presenter可以在方向发生变化恢复,但是当Android杀死了进程并销毁Activity,后者将与新的presenter一起重新创建。因此,该解决方案仅解决了一半的问题。

9. 为Model提供缓存以恢复视图状态

在我看来,解决“恢复状态”问题需要一些应用架构的知识。基本上,作者建议使用类似Repository或任何旨在管理数据的接口来缓存网络结果,范围限定于应用程序而不是Activity。

这个接口只是一个更聪明的Model。后者应至少提供磁盘缓存策略和可能的内存缓存。这样的话,即使进程被杀,presenter也可以使用磁盘缓存恢复视图状态。

view应该只关心必要的请求参数以恢复状态。例如,在我们的示例中,我们只需要保存查询。

现在,你有两个选择:

  • 你在model层中抽象这个行为,当presenter调用repository.get(params)时,如果页面已经在缓存中,数据源只返回它,否则再调用API。
  • 在contract中的presenter添加一个方法来恢复视图状态。restore(params),loadFromCache(params)或reload(params)这些是描述相同动作的不同名称你可以随便选一个。

结论

以上是我对应用于Android的Model-View-Presenter架构的看法,希望通过不断的尝试可以找到最佳实践。


全栈增长工程师,欢迎关注

Share Comments

和无序说再见

不要给信息归档,用的时候搜索就行了

先来看看我们日常工作生活中经常会做的文件整理吧。为了让生活更有序,因此我们都学会了如何让资料文件归档。毫无疑问,这样做是有意义的。如果不能按照某种符合逻辑的方式存放自己收到的文件或者信函,那么当你查找需要用到的东西时,就得到处翻检。而往往我们都会通过文件柜的形式去完成这样的事情(特别是传统行业的从业者)。

但是,使用这种文件柜的组织模式既没有考虑我们大脑的自身限制,也没有充分利用科学技术的新发展。

举例来说,公司在入职的时候给你发了一份关于费用报销及各项福利的说明文件。等你下次需要用到的时候就可以参考这些规定执行,以便填写正确的报销单据或者申请享受应有的福利。可是如果下次用到是几个月以后的事,你会怎么办?在此期间,你该怎么归档这份跨部门的文件呢?

在这个问题上,大部分人的做法可能会如出一辙。你极有可能会把这个说明文件装进一个文件夹,贴上写有“福利待遇”之类字样的便签然后放进专门存放公司文件的抽屉中,可是,你怎么记得这个抽屉就是存放公司福利政策文件的地方?

或许你也可能把相应的电子文档放到个人电脑的某个磁盘的目录下。即便如此,还是会有同样的问题:以后你能记得自己把这份文件放在这个目录下了吗?

也许你会未雨绸缪,以防忘记这份文件放在哪,而把它复制(复印)好多份放在不同的目录下(不同的抽屉中),例如:桌面也放一份。尽管需要执行多次文档归类的操作,而且也会多占用几个字节的磁盘空间(加剧森林的砍伐),不过这种策略短期来看还是行之有效的。

时至今日,“云”已经开始变得像自来水一样慢慢的走进了大众的生活,诸如电子邮件、电子日历、文件备份、资料共享、云盘之类的服务都属于云计算的范畴。人们不必要把信息存放在个人电脑的固定硬盘或者移动存储设备上,而是可以利用上面的诸多服务,在互联网上存放所有的信息。这意味着你可以在任何一台联网的计算机或者其他设备(比如装有浏览器的手机、PAD)上访问和使用自己的信息。你的信息存放在一个地方而你可以通过很多种方式获取它,这样的话,即便你的电脑磁盘损坏或者手机中毒导致文件不可用,你也不用担心自己的数据会丢失。

只要能联网,你就可以使用绝大多数的云服务。在这些海量云服务中,我个人比较喜欢的是Google的免费Gmail、Google Drive(有15G空间)、Google Doc、Google Photo(可以上传无限张压缩无损照片)。

回到上面的例子,你完全可以把这份文件通过Gmail发给自己或者上传到Google Drive。这样的话,在你需要的时候只需要在Gmail或者Google Drive中搜索一下即可。另外如果你使用的是mac,那么你就没有必要把一份文件到处复制保存了,你可以在需要的时候调出spotlight键入关键字就可以迅速找到本机中的相应文档了。

这就是搜索的美妙之处,它花费的时间大概还不及你把椅子转向抽屉的时间长。即使这份文件没有电子版也没关系,你可以扫描后存放在自己的电脑或者互联网上,等需要的时候再去搜索。

就在刚刚我要查看我的公积金账户,但是距离上一次使用已经过去快半年了,账号早已经忘记了,而且我也没有专门记录,于是我通过手机的邮箱客户端在搜索框中键入公积金三个字,在主题那个搜索结果列表中找到了当初公司发给我的公积金账户信息(太方便了!)。

借助搜索功能,你再也不必像过去那样,费劲心思想要把自己的信息资料存放的“井井有条”了。这就好比你再也不用一件一件地把所有衣服都挂好一样。相反,你只要把衣服扔到那个越来越高的大堆上就行。等你想穿那件印有ZERO TO ONE的T恤时,直接提出要求,那件衣服就像变魔术般的出现在了你的面前。这是多么轻松自由的感觉啊!(这也会减少磁盘空间的浪费你只需要128G的Mac和16G的iphone就行了甚至移动存储设备都可以被抛弃另外也可以把森林砍伐的风险降到最小)


全栈增长工程师,欢迎关注

Share Comments