[안드로이드-그 한계를 넘어서] 안드로이드 테스트 개요

안드로이드 테스트 개요.

왜 안드로이드 테스트를 해야하나?

안드로이드 가이드. https://developer.android.com/studio/test/index.html

종류

스크린샷 2017-02-27 오후 7.19.57

뷰(Views) : 액티비티, 프래그먼트에 해당하는 부분으로 UI변화가 생기는 부분입니다. 중요한 점은 오직 뷰만이 프레젠터에 말을 걸 수 있습니다.

프레젠터(Presenters) : 무엇을 보여줄 것인지에 대한 비지니스 로직을 담는 부분입니다. 레파지토리에서 정보를 요청하거나 뷰에 정보를 전달합니다. 유닛테스트가 까다로워지지 않게 하려면 가능한 안드로이드 상세 코드를 프레젠터에 넣지 마세요!

레파지토리(Repositories) : 데이터가 로컬에서 가져와야 하는지 또는 네트워크에서 가져와야하는지 결정하는 과정에서 프레젠터와 소통합니다.

모델(Model) : 프레젠터로부터 뷰에 정보를 전달하기 위해 사용되는 모델로 전형적인 POJO입니다.

자 그럼 자동화 테스트 폴더는 어디 있는가?

스크린샷 2017-02-27 오후 7.23.18

자동화된 테스트 작성하기

모바일 앱 개발자들이 자주 빼먹는 한 가지를 꼽자면 아마 테스틍 리것, 하지만 고품질의 소프트웨어는 철저한 테스트를 통해서만 나온다. 이점을 감안하면 아마도 이 장은 이 책에서 가장 중요한 장이 될 것이다.

안드로이드 테스트 원칙

안드로이드에서는 테스트를 두 분률로 나눌 수 있다. 단위 테스트와 계측 테스트다. 물론 TDD이론에는 안드로이드에서 활용할 만한 다른 테스트 유형(예를 들어 통합테스트, 기능테스트, 시스템 테스트, 컴포넌트 테스트 등)도 존재하지만 여기서는 단위 테스트와 계측 테스트에만 집중해보자.

단위 테스트는 외부 의존성을 모두 제거한 채 주로 개밸 메서드나 클래스를 대상으로 상세 수준에서 수행한다. 그에 반해 계측 테스트는 컴포넌트(Activtiy, Serivce, BroadcastReceiver, ContentProvider)의 동작을 확인하는 데 초점을 맞춘다.

단위테스트의 목적은 메서드가 예상대로 동작하고 메서드에서 잘못된 값을 문제 없이 처리할 수 있는지 확인하는 데 있다. 자바 프로그래밍 언어에서는 단위 테스트를 위해 JUnit이라는 프래임워크가 개발됐다. JUnit은 android.test 하위 패키지의 안드로이드 관련 테스트 프레임워크와 더불어 JUnit 패키지를 통해 공식 안드로이드 API에도 포함돼 있다. 단위 테스트를 수행하려면 애플리케이션 코드에서 메서드를 호출하고 그 결과를 예상 출력값과 비교하면 된다. 이를 “결과를 단정한다.”라고 부르며, 이런 용도로 활용할 수 있는 Assert라는 유틸리티 클래스가 제공된다. 각 테스트에서 이를 활용해 값을 단정하면 테스트 프레임워크에서는 코드가 테스트를 통과했는지 알려준다.

각 테스트를 테스트 케이스라고 부르며, 테스트 케이스 세트를 테스트 스워트라고 부른다. 각 테스트 케이스느느 모든 의존성을 생성하고 초기화하는 설정부터 시작한다. 테스트 케이스가 종료되면, teardown을 수행해 설정 과정에서 생성한 리소스를 해제한다.

종종 애플리케이션 내 메서드에서 시스템이나 다른 컴포넌트에 의존하는 경우가 있으므로 테스트를 실행할 때는 메서드를 고립화할 수 있어야 한다. 이를 위해서는 의존 객체의 동작을 시뮬레이션하는 목(mock) 객체를 사용한다. 안드로이드 API에서는 테스트에 사용할 수 있는 여러 목 클래스를 제공한다. 목 객체는 주로 각 테스트 케이스의 설정 과정에서 생성하며 teardown과정에서 해제된다.

테스트의 기본 패키지명은 애플리케이션의 패키지명과 동일한 이름에 .test를 첨부한 이름이 된다. 이 설정은 모두 gradle.build에서 설정할 수 있다.

테스트대상

자동화된 테스트를 작성할 때는 코드만을 테스트한다. 시스템 기능이나 서비를 확인하는 자동화된 테스트트 작성할 이유가 없다.

기본 단위 테스트

컴포넌트 생명주기에 의존하지 않는 클래스를 테스트할 떄는 AndroidTestCase클래스를 사용해 가장 단순한 형태의 테스트를 수행하면 된다. 이 방식은 다른 컴포넌트나 안드로이드 프레임워크와 상관없이 객체를 생성할 수 있을 때 효과적이다.

public class Util{
  public static int byteArrayToInt(byte[] bytes)
            throws IllegalArgumentException {
        if(bytes == null ||bytes.length != 4) {
            throw new IllegalArgumentException();
        }
        return bytes[3] & 0xFF |
                (bytes[2] & 0xFF) << 8 |
                (bytes[1] & 0xFF) << 16 |
                (bytes[0] & 0xFF) << 24;
    }
}

이러한 유틸 클래스가 존재하여 테스트를 하고자한다면? 위의 클래스는 4바이트를 정수로 변환하는 메서드가 들어있다. 이 클래스를 테스트할 떄는 메서드에서 단순 입력값을 받고 메서드가 스태틱 메서드이므로 별도의 설정이나 정리 작업이 필요 없다. Util클래스에서는 테스트가 필요한 메서드가 하나뿐이다. 기기에서 테스트를 수행하면 쉽게 테스트를 통과하는 것을 확인할 수 있다.

 public void testBytesToIntConversion() {
        int result = Util.byteArrayToInt(new byte[] {(byte) 127,
                (byte) -1, (byte) -1, (byte) -1});
        assertEquals(Integer.MAX_VALUE, result);
        result = Util.byteArrayToInt(new byte[] {(byte) 0,
                (byte) 0, (byte) 0, (byte) 0});
        assertEquals(0, result);
        result = Util.byteArrayToInt(new byte[]{(byte) -128,
                (byte) 0, (byte) 0, (byte) 0});
        assertEquals(Integer.MIN_VALUE, result);
    }

이 코드에서는 유효한 입력값을 넘겨줄 경우 메서드가 정확히 답을 반환하는 것을 환인했지만, 유효하지 않은 입력값을 넘겨줄 떄는 어떤 일이 일어나는지 테스트하지 않는다. 이를 테스트하려면 그 외 의 유형에서 검사해야한다.

  public void testBytesToIntWithNull() {
        try {
            int result = Util.byteArrayToInt(null);
        } catch (IllegalArgumentException e) {
            return;
        }
        fail();
    }

    public void testBytesToIntWithTooShortInput() {
        try {
            int result = Util.byteArrayToInt(new byte[] {1,2,3});
        } catch (IllegalArgumentException e) {
            return;
        }
        fail();
    }

    public void testBytesToIntWithTooLongInput() {
        try {
            int result = Util.byteArrayToInt(new byte[] {1,2,3,4,5,6,7,8,9});
        } catch (IllegalArgumentException e) {
            return;
        }
        fail();
    }

유효하지 않은 입력값을 넘겨줄 때는 IllegalArgumentException 예외가 일어나거나 테스트가 실패해야 한다.

 public class Util {
    public static int byteArrayToInt(byte[] bytes)
            throws IllegalArgumentException {
        if(bytes == null ||bytes.length != 4) {
            throw new IllegalArgumentException();
        }
        return bytes[3] & 0xFF |
                (bytes[2] & 0xFF) << 8 |
                (bytes[1] & 0xFF) << 16 |
                (bytes[0] & 0xFF) << 24;
    }
}

이런식으로 작성해서 발생하는 에러를 줄이도록 한다. 이러한 것이 단위 테스트.