51Testing软件测试论坛

标题: 【转】Android 单元测试实践 [打印本页]

作者: lsekfe    时间: 2016-9-21 10:21
标题: 【转】Android 单元测试实践
什么是单元测试

在计算机编程中,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。但是什么叫"程序单元"呢?是一个模块、还是一个类、还是一个方法(函数)呢?不同的人、不同的语言,都有不同的理解。一般的定义,尤其是是在OOP领域,是一个类的一个方法。在此,我们也这样理解:单元测试,是为了测试某一个类的某一个方法能否正常工作,而写的测试代码。

单元测试的三个步骤:

单元测试不是集成测试

这里需要强调一个观念,那就是单元测试只是测试一个方法单元,它不是测试一整个流程。举个例子来说,一个Login页面,上面有两个输入框和一个button。两个输入框分别用于输入用户名和密码。点击button以后,有一个UserManager会去执行performlogin操作,然后将结果返回,更新页面。那么我们给这个东西做单元测试的时候,不是测这一整个login流程。这种整个流程的测试:给两个输入框设置正确的用户名和密码,点击login button,最后页面得到更新。叫做集成测试,而不是单元测试。当然,集成测试也是有他的必要性的,然而这不是每个程序员应该花多少精力所在的地方。为什么是这样呢?因为集成测试设置起来很麻烦,运行起来很慢,在保证代码质量、改善代码设计方面更起不到任何作用,因此它的重要程度并不是那么高

Android中的单元测试

Android中的单元测试分为两种,Local Unit Tests 和 Instrumented Tests,前者运行在JVM,后者需要运行再Android设备

Local Unit Tests

Local Unit Tests运行在本地JVM,不需要安装APP,所以运行时间很快。也因此不能依赖Android的API,所以大多数时候需要用Mock的形式来做替换(后面会提到)


LocalUnitTests

配置

使用Gradle添加相应的库

  1. dependencies {
  2.     // Required -- JUnit 4 framework
  3.     testCompile 'junit:junit:4.12'
  4.     // Optional -- Mockito framework
  5.     testCompile 'org.mockito:mockito-core:1.10.19'
  6. }
复制代码
使用JUnit4

这是Java界用的最广泛,也是最基础的一个框架,其他的很多框架,包括我们后面会看到的Mockito,都是基于或兼容JUnit4的。 使用比较简单,最多的是其Assert类提供的assertXXX方法。

假设这样的一个类需要测试

使用JUnit4

这是Java界用的最广泛,也是最基础的一个框架,其他的很多框架,包括我们后面会看到的Mockito,都是基于或兼容JUnit4的。 使用比较简单,最多的是其Assert类提供的assertXXX方法。

假设这样的一个类需要测试

  1. public class Calculator {

  2.     public int add(int one, int another) {
  3.         return one + another;
  4.     }

  5.     public int multiply(int one, int another) {
  6.         return one * another;
  7.     }
复制代码

如果不使用单元测试框架,我们可能需要这样来验证这段代码的正确性:

  1. public class CalculatorTest {
  2.     public static void main(String[] args) {
  3.         Calculator calculator = new Calculator();
  4.         int sum = calculator.add(1, 2);
  5.         if(sum == 3) {
  6.             System.out.println("add() works!")
  7.         } else {
  8.             System.out.println("add() does not works!")
  9.         }

  10.         int product = calculator.multiply(2, 4);
  11.         if (product == 8) {
  12.             System.out.println("multiply() works!")
  13.         } else {
  14.             System.out.println("multiply() does not works!")
  15.         }
  16.     }
  17. }
复制代码

然后我们再通过某种方式,比如命令行或IDE,运行这个CalculatorTest的main方法,在看着terminal的输出,才知道测试是通过还是失败。想一下,如果我们有很多的类,每个类都有很多方法,那么就要写一堆这样的代码,每个类对于一个含有main方法的test类,同时main方法里面会有一堆代码。这样既写起来痛苦,跑起来更痛苦,比如说,你怎么样一次性跑所有的测试类呢?所以,一个测试框架为我们做的最基本的事情,就是允许我们按照某种更简单的方式写测试代码,把每一个测试单元写在一个测试方法里面,然后它会自动找出所有的测试方法,并且根据你的需要,运行所有的测试方法,或者是运行单个测试方法,或者是运行部分测试方法等等。 对于上面的例子,如果使用Junit的话,我们可以按照如下的方式写测试代码:

  1. public class CalculatorTest {
  2.     Calculator mCalculator;

  3.     @Before
  4.     public void setup() {
  5.         mCalculator = new Calculator();
  6.     }

  7.     @Test
  8.     public void testAdd() throws Exception {
  9.         int sum = calculator.add(1, 2);
  10.         Assert.assertEquals(3, sum);
  11.     }

  12.     @Test
  13.     public void testMultiply() throws Exception {
  14.         int product = calculator.multiply(2, 4);
  15.         Assert.assertEquals(8, product);
  16.     }

  17. }
复制代码

上面的@Before修饰的方法,会在测试开始前调用,这里是新建了一个Calculator对象,所以之后的一些测试单元都可以直接使用这个实例,@Test修饰的方法,是用来需要测试的单元,例如testAdd方法是用来测试Calculator类的加法操作,测试单元内使用Assert类提供的assertXXX方法来验证结果。如果还有其他的测试方法,则以此类推。 另外还有一些可能需要用到的修饰符,如@After,@BeforeClass,@AfterClass等。

Mockito

Mockito的两个重要的功能是,验证Mock对象的方法的调用和可以指定mock对象的某些方法的行为。(对于不懂Mock概念的同学来说,第一次看到的确很可能很难理解)

为什么要使用Mockito?

这是项目中的一个例子:

  1. /**
  2. * @param <T> 用于过滤的实体类型
  3. */
  4. public interface BaseItemFilter<T> {
  5.     /**
  6.      * @param item
  7.      * [url=home.php?mod=space&uid=26358]@return[/url] true:不过滤;false:需要过滤
  8.      */
  9.     boolean accept(T item);

  10. }
复制代码

BaseItemFilter是用来判断某种指定类型的实体是否需要过滤的,类似java中的FileFilter,目的是为了用了过滤不符合要求的实体。

以下是我们的关键服务过滤器的实现:

  1. public class EssentialProcessFilter implements BaseItemFilter<RunningAppBean> {

  2.     /**
  3.      * 系统关键进程及用户主要的进程
  4.      */
  5.     private static HashSet<String> sCoreList = new HashSet<String>();

  6.     /**
  7.      * 加载系统核心进程列表
  8.      * @param context
  9.      */
  10.     public static void loadCoreList(Context context) {
  11.         if (sCoreList.isEmpty()) {
  12.             final Resources r = context.getResources();
  13.             String[] corePackages = r.getStringArray(R.array.default_core_list);
  14.             Collections.addAll(sCoreList, corePackages);
  15.         }
  16.     }

  17.     @Override
  18.     public boolean accept(RunningAppBean appModle) {
  19.         return appModle != null && !(isEssentialProcess(appModle.mPackageName) || isEssentialProcessMock(appModle.mPackageName, appModle.mIsSysApp));
  20.     }

  21.     /**
  22.      * 判断进程是否属于重要进程
  23.      * @param process
  24.      * @return
  25.      */
  26.     public static boolean isEssentialProcess(String process) {
  27.         return sCoreList.contains(process);
  28.     }

  29.     /**
  30.      * 系统关键进程关键词模糊匹配
  31.      * @param packageName
  32.      * @param isSystemApp
  33.      * @return
  34.      */
  35.     public static boolean isEssentialProcessMock(String packageName, boolean isSystemApp) {
  36.         return 省略...额外的一些判断;
  37.     }

  38. }
复制代码

可以看到,这里的关键服务的判断的判断规则可以分两部分,一个是从String.xml中预设的一段Arrays数组查找是否右符合的,这个需要在初始化或某个时机预先调用EssentialProcessFilter#loadCoreList(Context context)方法来加载,另外的一个判断是在EssentialProcessFilter#isEssentialProcessMock方法中定义,这个类中accept方法,定义了只要符合其中一种规则,那么我们就需要把它过滤。

这个时候我们来写单元测试,你一开始就会发现你没有办法新建一个Context对象来读取String.xml,即使你想尽任何方法新建一个ContextImpl实例,最后你还是会出错的,主要原因再在于Gradle运行Local Unit Test 所使用的android.jar里面所有API都是空实现,并抛出异常的。 现在想想,我们实际上并不需要真的读取String.xml,我们需要验证的是记录在我们的关键列表集合是否生效,既然这样,我们前面说过了,Mockito的两个重要的功能是,验证Mock对象的方法的调用和可以指定mock对象的某些方法的行为。我们是否可以Mock一个Context对象并且指定它读取String.xml的行为?答案是可以的,如下就是使用Mockito的一段测试代码

  1. public class TestListFilter2 {
  2.     @Mock
  3.     Context mContext;
  4.     @Mock
  5.     Resources mResources;

  6.     @Before
  7.     public void setup() {
  8.         MockitoAnnotations.initMocks(this);
  9.         Mockito.when(mContext.getResources()).thenReturn(mResources);
  10.         Mockito.when(mResources.getStringArray(R.array.default_core_list)).thenReturn(getEssentialProcessArray());
  11.         //模拟加载XML资源
  12.         EssentialProcessFilter.loadCoreList(mContext);
  13.     }

  14.     /**
  15.      * 测试关键服务的过滤器
  16.      */
  17.     @Test
  18.     public void testEssentialFilter() {
  19.         EssentialProcessFilter processFilter = new EssentialProcessFilter();
  20.         ListFilter<RunningAppBean> listFilter = Mockito.spy(ListFilter.class);
  21.         listFilter.addFilter(processFilter);
  22.         List<RunningAppBean> list = new ArrayList<RunningAppBean>();
  23.         list.addAll(getEssentialAppBean());
  24.         list.addAll(getNormalRunningApp());
  25.         List<RunningAppBean> result = Mockito.mock(ArrayList.class);
  26.         for (RunningAppBean runningAppBean : list) {
  27.             if (listFilter.accept(runningAppBean)) {
  28.                 result.add(runningAppBean);
  29.             }
  30.         }
  31.         Mockito.verify(listFilter, Mockito.times(list.size())).accept(Mockito.any(RunningAppBean.class));
  32.         Mockito.verify(result, Mockito.times(getNormalRunningApp().size())).add(Mockito.any(RunningAppBean.class));
  33.     }
  34.     /**
  35.     * 关键服务应用包名
  36.     */
  37.     public String[] getEssentialProcessArray() {
  38.       return new String[]{"android.process.acore", "android.process.media", "android.tts", "android.uid.phone", "android.uid.shared", "android.uid.system"};
  39.     }
  40.   }
复制代码

上面的代码,我们使用@Mock来Mock了Context和Resource对象,这需要我们在setup的时候使用MockitoAnnotations.initMocks(this)方法来使得这些注解生效,如果再不使用@Mock注解的时候,我们还可以使用Mockito.mock方法来Mock对象。这里我们指定了Context对象在调用getResources方法的时候返回一个同样是Mock的Resources对象,这里的Resources对象,指定了在调用getStringArray(R.array.default_core_list)方法的时候返回的字符串数组的数据是通过我们的getEssentialProcessArray方法获得的,而不是真的是加载String.xml资源。最后调用EssentialProcessFilter.loadCoreList(mContext)方法使得EssentialProcessFilter内记录的关键服务集合的数据源就是我们指定的。目前,我们使用的就是改变Mock对象的行为的功能。

在测试单元testEssentialFilter方法中,使用Mockito.spy(ListFilter.class)来Mock一个ListFilter对象(这是一个BaseItemFilter的实现,里面记录了BaseItemFilter的集合,用了记录一系列的过滤规则),这里使用spy方法Mock出来的对象除非指定它的行为,否者调用这个对象的默认实现,而使用mock方法Mock出来的对象,如果不指定对象的行为的话,所有非void方法都将返回默认值:int、long类型方法将返回0,boolean方法将返回false,对象方法将返回null等等,我们也同样可以使用@spy注解来Mock对象。这里的listFilter对象使用spy是为了使用默认的行为,确保accept方法的逻辑正确执行,而result对象使用mock方式来Mock,是因为我们不需要真的把过滤后的数据添加到集合中,而只需要验证这个Mock对象的add方法调用的多少次即可。

最后就是对Mock对象的行为的验证,分别验证了listFilter#accept方法和result#add方法的执行次数,其中Mockito#any系列方法用来指定无论传入任何参数值。

依赖注入,写出更容易测试的代码

这里举一个新的例子,在使用MVP模式开发一个登录页

这是我们的LoginPresenter,Presenter的职责是作为View和Model之间的桥梁,UserManager就是这里的Model层,它用来处理用户登录的业务逻辑

  1. public class LoginPresenter {
  2.     private final LoginView mLoginView;
  3.     private UserManager mUserManager = new UserManager();

  4.     public LoginPresenter(LoginView loginView) {
  5.           mLoginView = loginView;
  6.     }

  7.     public void login(String username, String password) {
  8.         //....
  9.         mLoginView.showLoginHint();
  10.         mUserManager.performLogin(username, password);
  11.         //...
  12.     }
复制代码

这段代码存在了一些问题

为了把依赖解耦,我们一般可以作如下改变

  1. public class LoginPresenter {
  2.     private final LoginView mLoginView;
  3.     private final UserManager mUserManager;

  4.     //将UserManager作为构造方法参数传进来
  5.     public LoginPresenter(LoginView loginView,UserManager userManager) {
  6.         this.mLoginView = loginView;
  7.         this.mUserManager = userManager;
  8.     }

  9.     public void login(String username, String password) {
  10.         //... some other code
  11.         mUserManager.performLogin(username, password);
  12.     }
  13. }
复制代码

这就是一种常见的依赖注入方式,这种方式的好处是,依赖关系非常明显。你必须在创建这个类的时候,就提供必要的dependency。这从某种程度上来说,也是在说明这个类所完成的功能。实现依赖注入很简单,比较有名的Dagger、Dragger2这些框架可以让这种实现变得更加简单,简洁,优雅,有兴趣可以自行了解。 作出这种修改之后,我们就可以很容易的Mock出UserManager对象来对LoginPresenter做单元测试

对异步任务进行测试

很多时候,我们耗时的业务逻辑都是再异步线程中处理的,这样就会对我们的测试造成了一些困难,因为很可能我们的异步任务执行完之前,我们的Test单元已经执行完了,为了能够顺利验证,其中一个思路是,首先需要一个回调接口来告诉我们处理完成,之后再我们知道回调后,继续执行测试代码,这里的一个简单实现就是使用CountDownLatch,也可以使用wait和notifyAll,下面以CountDownLatch为例子

这里是我们的回调接口

  1. public interface ScanListener<T> {
  2.     /**
  3.      * @param t 扫描结果
  4.      */
  5.     void onScannedCompleted(T t);
  6. }
复制代码

测试代码如下:

  1. public class TestRunningAppScanner {
  2.     CountDownLatch mSignal = null;
  3.     RunningAppScanner mRunningAppScanner;
  4.     @Mock
  5.     BaseScanner.ScanListener<List<RunningAppBean>> mRunningAppBeanScanListener;
  6.     @Captor
  7.     ArgumentCaptor<List<RunningAppBean>> mListArgumentCaptor;

  8.     @Before
  9.     public void setUp() {
  10.         mSignal = new CountDownLatch(1);
  11.         MockitoAnnotations.initMocks(this);
  12.         mRunningAppScanner = Mockito.spy(new RunningAppScanner(InstrumentationRegistry.getTargetContext()));
  13.     }

  14.     @After
  15.     public void tearDown() {
  16.         mSignal.countDown();
  17.     }

  18.     @Test
  19.     public void testRunningApp() throws InterruptedException {
  20.         Assert.assertFalse(mRunningAppScanner.isRunning());
  21.         Mockito.doAnswer(new Answer() {
  22.             @Override
  23.             public Object answer(InvocationOnMock invocation) throws Throwable {
  24.                 mSignal.countDown();
  25.                 return invocation.getArguments();
  26.             }
  27.         }).when(mRunningAppBeanScanListener).onScannedCompleted((List<RunningAppBean>) Mockito.any());
  28.         mRunningAppScanner.setScanListener(mRunningAppBeanScanListener);
  29.         mRunningAppScanner.startScanning();
  30.         Assert.assertTrue(mRunningAppScanner.isRunning());
  31.         mSignal.await();
  32.         Assert.assertFalse(mRunningAppScanner.isRunning());
  33.         Mockito.verify(mRunningAppBeanScanListener, Mockito.times(1)).onScannedCompleted(mListArgumentCaptor.capture());
  34.         //必定最少有1个进程再运行
  35.         Assert.assertTrue(mListArgumentCaptor.getValue().size() > 0);
  36.     }
  37.   }
复制代码

上面代码中Mock了一个ScanListener类型的回调接口,并在testRunningApp方法中,指定了它onScannedCompleted方法的行为,RunningAppScanner#startScanning方法是用来启动异步逻辑的,在开始异步逻辑之前设置了回调接口,在开始异步逻辑后,就使用了mSignal.await()方法来阻塞当前线程,直到onScannedCompleted方法的执行,会调用到在setup的时候新建的CountDownLatch对象的countDown(),表明异步任务的完成,以继续执行下面的测试代码

上面的测试代码实际上是属于Instrumented Tests,它的异步逻辑是去扫描当前设备运行的进程,需要运行再Android设备上,它的异步逻辑是实际运行的而且会扫描出真实结果。但有些时候,我们只是想验证状态和交互结果,而不需要它真的执行异步逻辑,我们可以使用其他的方式来进行测试,我们以一个登录流程为例子

这是登录流程的时序图


Login Sequence

回调的定义

  1. public interface LoginCallback {

  2.     void onSuccess();

  3.     void onFail(int code);
  4. }
复制代码

这是LoginPresenter

  1. public class LoginPresenter {
  2.     private final LoginView mLoginView;
  3.     private final UserManager mUserManager;

  4.     //将UserManager作为构造方法参数传进来
  5.     public LoginPresenter(LoginView loginView, UserManager userManager) {
  6.         this.mLoginView = loginView;
  7.         this.mUserManager = userManager;
  8.     }

  9.     public void login(String username, String password) {
  10.         mLoginView.showLoginHint();
  11.         mUserManager.performLogin(username, password, new UserManager.LoginCallback() {
  12.             @Override
  13.             public void onSuccess() {
  14.                 mLoginView.showSuccess();
  15.             }

  16.             @Override
  17.             public void onFail(int code) {
  18.                 mLoginView.showError();
  19.             }
  20.         });
  21.     }
  22. }
复制代码

我们现在需要对LoginPresenter进行测试,我们的测试点在于接收到不同的回调结果的时候,对View进行不同的展示,至于UserManager的测试则应该是另外的一个测试单元的 LoginPresenter的测试代码

  1. public class TestDemo {
  2.     @Mock
  3.     LoginView mLoginView;
  4.     @Mock
  5.     UserManager mUserManager;

  6.     LoginPresenter mLoginPresenter;

  7.     @Captor
  8.     ArgumentCaptor<LoginCallback> mCallbackArgumentCaptor;

  9.     @Before
  10.     public void before() {
  11.         MockitoAnnotations.initMocks(this);
  12.         mLoginPresenter = new LoginPresenter(mLoginView, mUserManager);
  13.     }

  14.     @Test
  15.     public void showError() {
  16.         mLoginPresenter.login("username", "password");
  17.         Mockito.verify(mUserManager, Mockito.times(1)).performLogin(Mockito.anyString(), Mockito.anyString(), mCallbackArgumentCaptor.capture());
  18.         mCallbackArgumentCaptor.getValue().onFail(0);
  19.         Mockito.verify(mLoginView, Mockito.times(1)).showError();
  20.     }

  21.     @Test
  22.     public void showSuccess() {
  23.         mLoginPresenter.login("username", "password");
  24.         Mockito.verify(mUserManager, Mockito.times(1)).performLogin(Mockito.anyString(), Mockito.anyString(), mCallbackArgumentCaptor.capture());
  25.         mCallbackArgumentCaptor.getValue().onSuccess();
  26.         Mockito.verify(mLoginView, Mockito.times(1)).showSuccess();
  27.     }
  28. }
复制代码

这里的关键是@Captor ArgumentCaptor<LoginCallback> mCallbackArgumentCaptor; ,ArgumentCaptor是作用是用来捕获所传入方法特定的参数,然后可以进一步对参数进行断言,而且是在方法调用之后使用。这里我们捕获到UserManager#performLogin的第三个LoginCallback的参数,然后直接使用它来做相应回调来验证不同情况下的反馈结果是否正确

Mockito的一些替代或扩增库

使用Mockito并不可以Mock对象的静态方法、private修饰的方法、static方法、构造函数等,使用JMockit或PowerMock是可以解决这样的问题,有时间的话可以去实践下

Local Unit Tests的优点Instrumented Tests

Instrumented Unit tests是需要运行再Android设备上的(物理/虚拟),通常我们使用Mock的方式不能很好解决对Android的API的依赖的这个问题,而使用这种测试方式可以依赖Android的API,使用Android提供的Instrumentation系统,将单元测试代码运行在模拟器或者是真机上,但很多情况来说,我们还是会需要和Mockito一起使用的。这中方案速度相对慢,因为每次运行一次单元测试,都需要将整个项目打包成apk,上传到模拟器或真机上,就跟运行了一次app似得,所以。

配置

通过Gralde添加相应的库

  1. dependencies {
  2.     androidTestCompile 'com.android.support:support-annotations:24.0.0'
  3.     androidTestCompile 'com.android.support.test:runner:0.5'
  4.     androidTestCompile 'com.android.support.test:rules:0.5'
  5.     // Optional -- Hamcrest library
  6.     androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
  7.     // Optional -- UI testing with Espresso
  8.     androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
  9.     // Optional -- UI testing with UI Automator
  10.     androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
  11. }
复制代码

另外还需要在你的App的模块的build.gralde文件添加如下设置:

  1. android {
  2.     defaultConfig {
  3.         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  4.     }
  5. }
复制代码
使用AndroidJUnitRunner

AndroidJUnitRunner是JUnit4运行在Android平台上的兼容版本,使用也是很简单的且方法类似,以上面测试异步任务为例子

  1. @RunWith(AndroidJUnit4.class)
  2. @MediumTest
  3. public class TestRunningAppScanner {
  4.     CountDownLatch mSignal = null;
  5.     RunningAppScanner mRunningAppScanner;
  6.     @Mock
  7.     BaseScanner.ScanListener<List<RunningAppBean>> mRunningAppBeanScanListener;


  8.     @Before
  9.     public void setUp() {
  10.         mSignal = new CountDownLatch(1);
  11.         MockitoAnnotations.initMocks(this);
  12.         mRunningAppScanner = Mockito.spy(new RunningAppScanner(InstrumentationRegistry.getTargetContext()));
  13.     }

  14.     @After
  15.     public void tearDown() {
  16.         mSignal.countDown();
  17.     }

  18.     @Test
  19.     public void testRunningApp() throws InterruptedException {
  20.         Assert.assertFalse(mRunningAppScanner.isRunning());
  21.         Mockito.doAnswer(new Answer() {
  22.             @Override
  23.             public Object answer(InvocationOnMock invocation) throws Throwable {
  24.                 mSignal.countDown();
  25.                 return invocation.getArguments();
  26.             }
  27.         }).when(mRunningAppBeanScanListener).onScannedCompleted((List<RunningAppBean>) Mockito.any());
  28.         mRunningAppScanner.setScanListener(mRunningAppBeanScanListener);
  29.         mRunningAppScanner.startScanning();
  30.         Assert.assertTrue(mRunningAppScanner.isRunning());
  31.         mSignal.await();
  32.         Assert.assertFalse(mRunningAppScanner.isRunning());
  33.         Mockito.verify(mRunningAppBeanScanListener, Mockito.times(1)).onScannedCompleted((List<RunningAppBean>) Mockito.any());
  34.     }
  35.   }
复制代码

需要在测试类的定义上加上@RunWith(AndroidJUnit4.class)标注,另外@MediumTest标注是用来指定测试规模,有三种类型可以指定,限制如下,因为这里的异步任务的目的是扫描所有正在运行的App,所以这里使用@MediumTest,其他的Assert#XXX、@Befor,@After使用一致,另外这里还搭配Mockito使用。因为需要真正用到系统的一些服务(AMS,PKMS)或资源,这里的InstrumentationRegistry可以为我们提供Instrumentation对象,测试App的Context对象,目标App的Context对象和通过命令行启动测试所提供的参数信息。这里的 InstrumentationRegistry.getTargetContext() 就是用来获取目标App的Context对象。

[td]
FeatureSmallTestMediumTestLargeTest
NetworkNolocalhost onlyYes
DatabaseNoYesYes
File system accessNoYesYes
Use external systemsNoDiscouragedYes
Multiple threadsNoYesYes
Sleep statementsNoYesYes
System propertiesNoYesYes
Execution Time (ms)200<1000<>1000
Espresso

Espresso提供大量的方法用来进行UI测试,这些API可以让你写的简洁和可靠的自动化UI测试,站在用户的角度测试,所有逻辑是黑盒

Espresso的三个重要功能:

使用流程







欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2