레이블이 테스트인 게시물을 표시합니다. 모든 게시물 표시
레이블이 테스트인 게시물을 표시합니다. 모든 게시물 표시

2017년 8월 18일 금요일

Testing UI for Multiple Apps

Testing UI for Multiple Apps


Dependencies and Prerequisites

This lesson teaches you to

  1. Set Up UI Automator
  2. Create a UI Automator Test Class
  3. Run UI Automator Tests on a Device or Emulator

You should also read

Try it out

다중앱을 커버하는 사용자 상호작용과 연관된 UI 테스트는 사용자 흐름이 다른 앱들과 시스템 UI와 얽혀있을때 앱이 정확하게 동작하는지 확인해준다. 그러한 흐름의 예는 텍스트를 입력하는 메시징 앱이 안드로이드 contact picker를 띄워서 사용자가 메시지를 보낼 수신자를 선택할 수 있게 하고 나서 사용자가 메시지 보내기 위해 원래 앱으로 제어를 리턴하도록 하는 것이다.
이 문서는  Android Testing Support Library 가 제공하는  UI automator test framework를 사용하여 어떻게 UI 테스트를 작성하는가를 다룬다. UI Automator API는 어떤 액티비티가 포커스를 가지고 있는지 상관없이 장치의 보이는 요소들과 상호작용할 수 있도록 해준다. 당신의 테스트는 컴포넌트에 보여지는 텍스트나 그것의 컨텐츠 디스크립터와 같은 편리한 디스크립터를 사용하여 UI 컴포넌트를 찾을 수 있다. UI Automator 테스트는 안드로이드 4.3(API level 18) 이상에서 동작하는 장치에서 실행할 수 있다. UI Automator 테스팅 프레임워크는 장치 기반의 API이고 Android Testing Support Library test runner와 함께 동작한다.

Set Up UI Automator


UI automator를 사용하여 당신의 UI test를 작성하기 전에 Getting Started with Testing 에 나와 있는데로 당신의 테스트 코드 위치와 프로젝트 디펜던시를 설정해야 한다.
당신의 안드로이드 앱 모듈에 있는 build.gradle 파일에 UI Automator library에 대한 의존성 참조를 셋팅해야 한다:

dependencies {
    ...
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
}
 
 
UI Automator 테스팅을 최적화 하기 위해서는 먼저 타겟 앱의 UI 컴포넌트들을 조사하고 그것들에 접근 가능한지 확인해야 한다. 이들 최적화 팁은 다음 두 섹션에서 설명한다.

Inspecting the UI on a device

테스트를 디자인하기 전에, 장치에 보이는 UI 컴포넌트들을 조사한다. 당신의 UI Automator 테스트가 이들 컴포넌트에 접근할 수 있는지 확인하기 위해 이들 컴포넌트들이 보이는 텍스트 라벨과 android:contentDescription 혹은 둘다를 가지고 있는지 체크한다.
장치의 foreground 상에 보이는 UI 컴포넌트들의 특성을 보고 레이아웃 계층을 조사하기 위한 편리한 비주얼 인터페이스를 제공하는 uiautomatorviewer 툴을 제공한다. 이 정보들은 UI Automator를 사용하여 더 매끄러운 테스트를 작성할 수 있도록 해준다. 예를 들어 당신은 특정한 보이는 속성과 매치하는 UI selector를 작성할 수 있다.

uiautomatorviewer 툴을 띄우기 위해:
To launch the uiautomatorviewer tool:
  1. 물리 장치에 타겟 앱을 띄운다.
  2. 개발 머신에 물리장치를 연결한다.
  3. 터미널 윈도우를 열어서 <android-sdk>/tools/ 디렉토리로 이동한다.
  4. 다음 명령을 이용하여 툴을 실행한다.:
    $ uiautomatorviewe
애플리케이션을 위한 UI 속성들을 보기 위해:
  1. uiautomatorviewer 인터페이스 상에서 , Device Screenshot 버튼을 클릭한다..
  2. uiautomatorviewer 툴에 의해 인식된 UI 컴포넌트들을 보여주는 왼족 패널 상의 snapshot 상에 마우스 커서를 가져다댄다. 속성들이 오른쪽 패널 하단에 리스팅되고 오른쪽 패널 상단에는 레이아웃 계층이 표시된다.
  3. 선택적으로 UI Automator에서 접근할 수 없는 UI 컴포넌트들을 보기 위해서는 Toggle NAF Nodes 버튼을 클릭한다. 이들 컴포넌트를 위해서는 제한된 정보만 사용할 수 있다.
안드로이드에 의해 제공되는 UI 컴포넌트들의 일반적인 타입들에 대해 알고 싶다면 User Interface.를 보아라.

Ensuring your Activity is accessible

The UI Automator test framework performs better on apps that have implemented Android accessibility features. When you use UI elements of type View, or a subclass of View from the SDK or Support Library, you don't need to implement accessibility support, as these classes have already done that for you.
Some apps, however, use custom UI elements to provide a richer user experience. Such elements won't provide automatic accessibility support. If your app contains instances of a subclass of View that isn't from the SDK or Support Library, make sure that you add accessibility features to these elements by completing the following steps:
  1. Create a concrete class that extends ExploreByTouchHelper.
  2. Associate an instance of your new class with a specific custom UI element by calling setAccessibilityDelegate().
For additional guidance on adding accessibility features to custom view elements, see Building Accessible Custom Views. To learn more about general best practices for accessibility on Android, see Making Apps More Accessible.

Create a UI Automator Test Class


Your UI Automator test class should be written the same way as a JUnit 4 test class. To learn more about creating JUnit 4 test classes and using JUnit 4 assertions and annotations, see Create an Instrumented Unit Test Class.
Add the @RunWith(AndroidJUnit4.class) annotation at the beginning of your test class definition. You also need to specify the AndroidJUnitRunner class provided in the Android Testing Support Library as your default test runner. This step is described in more detail in Run UI Automator Tests on a Device or Emulator.
Implement the following programming model in your UI Automator test class:
  1. Get a UiDevice object to access the device you want to test, by calling the getInstance() method and passing it an Instrumentation object as the argument.
  2. Get a UiObject object to access a UI component that is displayed on the device (for example, the current view in the foreground), by calling the findObject() method.
  3. Simulate a specific user interaction to perform on that UI component, by calling a UiObject method; for example, call performMultiPointerGesture() to simulate a multi-touch gesture, and setText() to edit a text field. You can call on the APIs in steps 2 and 3 repeatedly as necessary to test more complex user interactions that involve multiple UI components or sequences of user actions.
  4. Check that the UI reflects the expected state or behavior, after these user interactions are performed.
These steps are covered in more detail in the sections below.

Accessing UI Components

The UiDevice object is the primary way you access and manipulate the state of the device. In your tests, you can call UiDevice methods to check for the state of various properties, such as current orientation or display size. Your test can use the UiDevice object to perform device-level actions, such as forcing the device into a specific rotation, pressing D-pad hardware buttons, and pressing the Home and Menu buttons.
It’s good practice to start your test from the Home screen of the device. From the Home screen (or some other starting location you’ve chosen in the device), you can call the methods provided by the UI Automator API to select and interact with specific UI elements.
The following code snippet shows how your test might get an instance of UiDevice and simulate a Home button press:
import org.junit.Before;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {

    private static final String BASIC_SAMPLE_PACKAGE
            = "com.example.android.testing.uiautomator.BasicSample";
    private static final int LAUNCH_TIMEOUT = 5000;
    private static final String STRING_TO_BE_TYPED = "UiAutomator";
    private UiDevice mDevice;

    @Before
    public void startMainActivityFromHomeScreen() {
        // Initialize UiDevice instance
        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

        // Start from the home screen
        mDevice.pressHome();

        // Wait for launcher
        final String launcherPackage = mDevice.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
                LAUNCH_TIMEOUT);

        // Launch the app
        Context context = InstrumentationRegistry.getContext();
        final Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
        // Clear out any previous instances
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);

        // Wait for the app to appear
        mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                LAUNCH_TIMEOUT);
    }
}
In the example, the @SdkSuppress(minSdkVersion = 18) statement helps to ensure that tests will only run on devices with Android 4.3 (API level 18) or higher, as required by the UI Automator framework.
Use the findObject() method to retrieve a UiObject which represents a view that matches a given selector criteria. You can reuse the UiObject instances that you have created in other parts of your app testing, as needed. Note that the UI Automator test framework searches the current display for a match every time your test uses a UiObject instance to click on a UI element or query a property.
The following snippet shows how your test might construct UiObject instances that represent a Cancel button and a OK button in an app.
UiObject cancelButton = mDevice.findObject(new UiSelector()
        .text("Cancel"))
        .className("android.widget.Button"));
UiObject okButton = mDevice.findObject(new UiSelector()
        .text("OK"))
        .className("android.widget.Button"));
// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
    okButton.click();
}

Specifying a selector

If you want to access a specific UI component in an app, use the UiSelector class. This class represents a query for specific elements in the currently displayed UI.
If more than one matching element is found, the first matching element in the layout hierarchy is returned as the target UiObject. When constructing a UiSelector, you can chain together multiple properties to refine your search. If no matching UI element is found, a UiAutomatorObjectNotFoundException is thrown.
You can use the childSelector() method to nest multiple UiSelector instances. For example, the following code example shows how your test might specify a search to find the first ListView in the currently displayed UI, then search within that ListView to find a UI element with the text property Apps.
UiObject appItem = new UiObject(new UiSelector()
        .className("android.widget.ListView")
        .instance(0)
        .childSelector(new UiSelector()
        .text("Apps")));
As a best practice, when specifying a selector, you should use a Resource ID (if one is assigned to a UI element) instead of a text element or content-descriptor. Not all elements have a text element (for example, icons in a toolbar). Text selectors are brittle and can lead to test failures if there are minor changes to the UI. They may also not scale across different languages; your text selectors may not match translated strings.
It can be useful to specify the object state in your selector criteria. For example, if you want to select a list of all checked elements so that you can uncheck them, call the checked() method with the argument set to true.

Performing Actions

Once your test has obtained a UiObject object, you can call the methods in the UiObject class to perform user interactions on the UI component represented by that object. You can specify such actions as:
The UI Automator testing framework allows you to send an Intent or launch an Activity without using shell commands, by getting a Context object through getContext().
The following snippet shows how your test can use an Intent to launch the app under test. This approach is useful when you are only interested in testing the calculator app, and don't care about the launcher.
public void setUp() {
    ...

    // Launch a simple calculator app
    Context context = getInstrumentation().getContext();
    Intent intent = context.getPackageManager()
            .getLaunchIntentForPackage(CALC_PACKAGE);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            // Clear out any previous instances
    context.startActivity(intent);
    mDevice.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}

Performing actions on collections

Use the UiCollection class if you want to simulate user interactions on a collection of items (for example, songs in a music album or a list of emails in an Inbox). To create a UiCollection object, specify a UiSelector that searches for a UI container or a wrapper of other child UI elements, such as a layout view that contains child UI elements.
The following code snippet shows how your test might construct a UiCollection to represent a video album that is displayed within a FrameLayout:
UiCollection videos = new UiCollection(new UiSelector()
        .className("android.widget.FrameLayout"));
// Retrieve the number of videos in this collection:
int count = videos.getChildCount(new UiSelector()
        .className("android.widget.LinearLayout"));
// Find a specific video and simulate a user-click on it
UiObject video = videos.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "Cute Baby Laughing");
video.click();
// Simulate selecting a checkbox that is associated with the video
UiObject checkBox = video.getChild(new UiSelector()
        .className("android.widget.Checkbox"));
if(!checkBox.isSelected()) checkbox.click();

Performing actions on scrollable views

Use the UiScrollable class to simulate vertical or horizontal scrolling across a display. This technique is helpful when a UI element is positioned off-screen and you need to scroll to bring it into view.
The following code snippet shows how to simulate scrolling down the Settings menu and clicking on an About tablet option:
UiScrollable settingsItem = new UiScrollable(new UiSelector()
        .className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "About tablet");
about.click();

Verifying Results

The InstrumentationTestCase extends TestCase, so you can use standard JUnit Assert methods to test that UI components in the app return the expected results.
The following snippet shows how your test can locate several buttons in a calculator app, click on them in order, then verify that the correct result is displayed.
private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
    // Enter an equation: 2 + 3 = ?
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("two")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("plus")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("three")).click();
    mDevice.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("equals")).click();

    // Verify the result = 5
    UiObject result = mDevice.findObject(By.res(CALC_PACKAGE, "result"));
    assertEquals("5", result.getText());
}

Run UI Automator Tests on a Device or Emulator


You can run UI Automator tests from Android Studio or from the command-line. Make sure to specify AndroidJUnitRunner as the default instrumentation runner in your project.
To run your UI Automator test, follow the steps for running instrumented tests described in Getting Started with Testing.

2016년 7월 27일 수요일

What is Hamcrest?

현재 JUnit4.4 에, 배포된 JAR 를 살펴보면 무언가 다른 점을 알아차릴 것이다.: org.juit* 패키지에 더하여 재밌는 이름의 org.hamcrest.* 패키지가 두드러져 보인다. 최근에 Hamcrest에 의존성이 강해지는프로젝트를 보았을 것이다. 그리고 나는 그것이 무엇인지 궁금해 할 것이라고 확신한다. 이제 그것에 대해 알아보자.
Introduction
현재 hamcrest 에 대한 구글 서치는 Hamcrest에 대해 언급하는 Google Code 페이지로 당신을 인도한다:
거기서는 matcher object  라이브러리를 제공한다(이는 또한 constraints 나 predicates 로도 알려져 있다)  이 'match' 룰은 선언적으로 정의될 수 있도록 하며 다른 프레임워크에서도 사용될 수 있다.
당신이 이미 Matcher가 무엇인지 알고 있다면 이는 매우 유용한 설명이다. 그러나 초심자에게는 더 많은 설명이 필요하다. 그래서 Hamcrest가 무엇을 한단 말인가?

그것을 생각해낼 좋은 방법은 정규 표현식이 텍스트에 대한 것이라면 Hamcrest는 객체에대한 것이라는 것이다. Hamcrest는 객체에 대한 pattern-matching 을 수행하는 DSL을 효과적으로 정의하는 여러 메소드를 제공한다. 무슨 용도로 사용할 수 있을까? 정규표현식과 같이 가능성은 무궁무진하다. 최근에 몇몇 참신한 사용으로 나타났다. 그러나 대부분의 주요 사용분야는 테스팅 영역이다. 그러한 이유로 Junit 에 의존적이다.
A Brief History
Hamcrest는 JMock 라이브러리로 부터 발전하였다. 거기서 실행상황을 대신하는 Mock object에게 발생할 것으로 예상되는 것들에 대한 제약사항들을 명세하는데 사용되었다. 더욱 유창한 assertion syntax 가 당신이 주요 constraint calls 을 하나의 assertThat 메소드로 함께 엮어주도록 하는 constraints로부터 발생했다. 이후 Jmock의 저자, Joe Waines는 constraint API를 Hamrest라 불리는 라이브러리로 리팩토링 하였다. 그리고 사람들은 다른 다양한 것들을 위해 테스팅 프레임워크 외부에서 그것을 사용하는데 흥미를 갖게 되었다. 또한 몇몇 무리에서는 "predicates"로 부르는 constraints를 "matcher"로 부르기 시작했다. 의존성을 요청하지 않는것으로 악명 높았고 개념적으로 작게 유지되었던 다른 테스팅 프레임워크와 심지어 Junit마저도 재빠르게 움직였다.
이들 모두 오래되고 친근한 org.jnit.Assert.* static mehods들은 여전히 Junit 4.4+ 에 유지하고 있다. 특정한 측면에서 그것들은 유용하지만 대체로 유행에 뒤쳐져 있다. Junit의 assertThat 메소드는 당신이 특별한 assert 메소드를 사용하여 assertion을 생성할 수 있도록 Matcher를 전달한다. 왜 이 assertThat이 유용할까? 몇가지의 이유가 있다.그것은 assertion을 명세하는 제너릭한 방식을 제공할 뿐만 아니라 프레임워크가 당신이 시도하는 assert 가 무엇인지 명확하게 알게해 준다. 이것은 프레임워크가 당신을 위한 실패 메시지를 생성하게 한다.
Fail message… win?
failer 메시지를 위한 스트링을 파라미터로받는 assertX 메소드의 오버라이딩 버전을 사용하는 것은 좋은 아이디어였다. 예를 들어:
assertTrue(blackbeard.getOccupations().contains("pirate") ||
  blackbeard.getOccupations().contains(
"captain"),
 
"Expected Blackbeard to be a pirate or a captain");
만약 Blackbeard의 어린시절 버전을 나타내는 객체를 전달하면 우리는 다음과 같은 실패 메시지를 기대하게될 것이다:
java.lang.AssertionError: Expected Blackbeard to be a pirate or a captain
이들 메시지를 제공하는 것은 좋은 아이디어였다. 그러나 반복적이고, 지루하고, 쓸모없는 것들을 피할 수는 없다. assertThat과 함게 Hamcrest matcher를 사용하면 당신은 의미있는 실패 메시지를 생성할 만큼 스마트한 Junit을 만날 수 있다: Hamcrest matcher를 사용하여 당신의 assertion을 사용하는 것은 프레임워크가 충분한 문백 정보를 가지고 당신을 도와주는 의미있는 실패메시지를 생성하게 해준다. 그리고 당신의 실패한 assertions을 이해할 추가적인 여분의 스트링을 유지할 필요가 없다 :
assertThat(blackbeard.getOccupations(), anyOf(hasItem("pirate"),hasItem("captain")));

그닥 개선이 이루진 것처럼 보이지 않을 수 있다. 그러나 더 나쁘게 읽히지는 않는다. 우리는 여분의 스트링 파라미터를 버릴 수 있다. 이것이 실패했을 때 우리는 매우 간단한 실패 메시지를 무료로 만날 것이다:

java.lang.AssertionError:
  Expected: (a collection containing
"pirate" or a collection containing "captain")
    got:  [] >
Note: I'm using JUnit 4.6 - 당신에게 작동하는 이 문구를 만나기 위해 몇몇 static imports가 필요하다.
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.matchers.JUnitMatchers.*;

나는 이것을 동작시킬 수 없었지만, 이 DSL 을 Junit에서 잘읽을 수 있도록 여전히 할일이 있다; 다음과 같이:
assertThat(blackbeard.getOccupations(), either(hasItem("pirate")).or(hasItem("captain")));
Other Cool Stuff
Getting Reused
TestNG, JMock그리고 다른 테스팅 프레임워크가 Hamcrest가 제공한 것으로부터 확실히 이득을 얻을 것이다. 그러나 우리는 이것이 다른 많은 곳에서 사용되는 것을 보게될 것이다. matcher가 어떻게 잘 동작하는지 보는 것에 관심을 갖게 될 것이고 그것은 비교적 생성하기 쉽다는 견해가 유지될 것이다. 바라건데 프레임워크는 프로젝트에 유용한 matcher를 제공할 것이다.
Chaining
matcher가 method chaining idiom을 이용하여 DSL로 구현되었기 때문에 조합하거나 확장하는 것이 매우 쉽다.
Matchers For Regular Expressions
이 블로그 포스트를 가져오면서 이전에 포스트를 발견했다: the Hamcrest Text Patterns project. 이것의 목표는 matcher를 사용해 Hamcrest 스타일의 정규 표현식을 작성해서 더 가독성 높은 정규 표현식 코드를 생상하도록 하는 것이다. 예를 들어 프로젝트에서 발견한 다음 테스트를 보아라:
PatternMatcher emailAddressMatcher = new PatternMatcher(sequence(
  capture(
"user", oneOrMore(anyCharacter())),
 
"@",
  capture(
"host", oneOrMore(anyCharacter()))
));

PatternMatcher mailToURLMatcher =
new PatternMatcher(sequence(
  capture(
"scheme", text("mailto")),
 
":",
  capture(
"email", emailAddressMatcher)
));

assertThat(
"mailto:npryce@users.sf.net", matchesPattern(mailToURLMatcher));
각 capture(user, host, scheme, email) 에 대해 친숙한 이름을 제공함으로써 어떤 것이 매칭하고 매칭하지 않는지에 대한 프로그래밍측면에서 의미있는 메시지를 제공할 수 있다.
Many more uses
Matchers를 적용하는 더 많은 방식을 보기 위해 UsesOfHamcrest 를 체크해라.
컬렉션을 처리하기, 로깅에 사용하기 위한 필터 생성하기 등
Conclusion
당신이 Hamcrest를 더 자세히 살펴보기 원한다. 왜냐면 그만한 가치가 있고 우리를 더 가독성이 높은 코드를 작성하도록 가이드해 줄것이다.
I hope you choose to take a closer look at Hamcrest because it brings a big bang-for-the-buck and has the potential to guide us to more human-friendly, readable code.
Further Reading