테스트는 왜 필요할까요? 다음의 몇가지를 들 수 있습니다.

  • 필요한 조건을 만족하는지 확인
  • 오류 발견
  • 앱의 사용성을 확인

자동테스트 & 수동테스트

수동테스트는 코딩 작업이 끝난 상태에서 apk를 만들고, 체크리스트로 작성한 테스트 케이스를 차례대로 실행하는 형태로 이뤄지는 경우가 많습니다.

 

수동 테스트의 특성

  • 최종 사용자와 완전히 똑같이 동작을 확인할 수 있다.
  • 사용성이나 쾌적함 등 정성적인 면에서 평가할 수 있다.

자동테스트는 '단위 테스트' , 'UI 테스트' 로 불리는 테스트 코드를 만들어 테스트 코드를 실행함으로써 앱이 바르게 동작하는지 확인합니다.

 

자동테스트의 특성

  • 반복 실행이 가능하다.
  • 테스트 실행 상황을 시각화할 수 있다.
  • 클래스와 메서드 등 사용자 조작으로 확인하기 어려운 단위의 동작을 확인할 수 있다.

이런 특성들이 있어 자동 테스트로 동작에 대체로 이상이 없다는 것을 확인한 다음, 수동 테스트를 통해 공개 전 최종 확인 및 사용자 경험을 최적화하는 식으로 구분해서 생각합니다. 

 

단위 테스트

클래스나 메서드처럼 아주 작은 단위를 검증하기 위해 실행되는 테스트입니다. 소스코드 자체의 타당성이나 품질을 확인할 수 있으며, 가장 작은 단위로 검증이나 실행이 이뤄지므로 실행 시간도 아주 짧습니다. 그러므로 일반적으로 통신이나 IO 처리와 같은 외부 리소스와의 통신을 무효로 하고, 단말이나 환경에 의존하지 않는 상태로 테스트할 필요가 있습니다.

UI 테스트

실제 동작 환경을 중요시하므로 일반적으로는 실제 기기나 에물레이터 상에서 실행됩니다. 

이름 그대로 단말의 표시를 바탕으로 확인해 갑니다. 또한 통신이나 IO 처리 등 외부 리소스를 이용해 테스트하는 경우도 있습니다.

 

JUnit

자바에서 사실상의 표준이 되는 단위 테스트 프레임워크이며, 버전 3과 버전 4의 큰 차이 중 하나는 테스트 코드에 어노테이션이 이용되는 점을 들 수 있습니다.

 

 

Mockito를 이용한 객체의 목화

단위 테스트에서는 IO 처리 등과 같은 외부의 영향을 받지 않게 테스트를 작성해야 합니다.

 

예를 들어, HTTP 통신을 하는 경우를 생각해 봅시다.
테스트할 때 HTTP 통신을 실제로 하는 테스트를 작성해 버리면 단말 설정이나 서버 사오항에 따라 테스트 결과가 달라집니다.  그런 상황에서는 테스트에 실패해도 코드에 문제가 있는지, 그렇지 않으면 설정 등 주변 환경에 문제가 있는지 판단할 수 없기에 신뢰할 수 있는 테스트라고 할 수 없습니다.

 

이런 사태를 피하고자 테스트할 때는 의존하는 처리를 위장할 필요가 생깁니다.

HTTP 통신의 예에서 위장이란 어떤 테스트에서는 성공 응답을 반드시 반환하고, 반대로 다른 테스트에서는 반드시 실패 응답을 반환하는게 되겠지요. 추상적으로 말하면 위장이란 '실제 처리는 하지 않고 원하는 값을 반환하거나 처리를 실행시키는 것' 이라고 할 수 있습니다. 위장한 객체를 잘 사용하면 언제 어떤 때라도 동작이 항상 일정한 테스트를 작성할 수 있게 됩니다.

 

이런 위장을 간편하게 구현할 때 사용하는 것이 '목 객체' 입니다. 

(목 라이브러리를 사용하지 않아도 테스트할 때 클래스를 상속하는 방식 등으로 위장을 실현할 수 있지만 구현할 게 너무 많아지므로 목 라이브러리를 사용하는 경우가 많습니다.)

'Android' 카테고리의 다른 글

@JvmOverloads  (0) 2020.12.15
[Android] 키패드에 따른 화면변경 설정하기  (0) 2020.04.20
[Android] Gradle  (0) 2020.04.10
[Android] ContentProvider, Service, BroadcastReceiver  (0) 2020.04.03
[Android] Activity, View, Layout  (0) 2020.04.03

Gradle

Gradle은 안드로이드뿐 아니라 다양한 빌드에 사용할 수 있는 빌드 시스템입니다.

Gradle로 안드로이드 애플리케이션 파일(apk 파일) 빌드는 물론이고, 빌드 스크립트로 라이브러리 등의 의존관계나 설정을 관리할 수 있다. 또한 소스코드를 체크하는 등 다양한 일을 할 수 있다. 그뿐만 아니라 Gradle의 빌드 스크립트는 그루비라는 스크립트 언어로 빌드를 커스터마이징할 수 있다.

 

Gradle의 장점

다른 환경에서 똑같이 실행할 수 있다.

GradleWrapper라는 매커니즘이 있어 사용시 설정도니 버전으로 자동으로 셋업할 수 있다.

그러므로 프로젝트 팀 간이나 서버에서 같은 버전의 Gradle로 빌드를 실행할 수 있다.

의존관계를 기술할 수 있다.

Gradle의 빌드 스크립트 안에서는 한 줄만 써도 라이브러리 등을 추가할 수 있다.


Android Gradle Plugin의 특징 : 앱에 특화된 설정

앱의 버전을 기술하거나 빌드에 이용할 툴의 버전을 지정할 수 있다.

그 밖에도 빌드 변형(Biuld Variant) 기능으로 제품 특성에 따른 앱을 만들 수 있다.

예를 들어, 무료 버전과 유료 버전으로 앱을 만들거나 디버그 버전과 릴리스 버전에 따라 서로 다르게 동작하게 할 때 이용할 수 있다. 매번 코드를 변경할 필요가 없으므로 효율적으로 개발할 수 있다.

 

안드로이드의 Build 파일

프로젝트의 build.gradle (/build.gradle)

Gradle 파일에서 중괄호로 에워싸인 부분을 가리켜 '블록'이라고 부른다.

우선 buildscript 블록을 보면 빌드 시 Android gradle 플러그인을 이용하기 위한 설정이 들어간다.

  • repository 블록에서 Android Gradle Plugin은 jcenter의 리포지토리에 있으므로 jcenter를 리포지토리로서 지정한다
  • dependencies 블록에서 실제로 Android Gradle Plugin을 의존관계로서 기술한다. 
  • 또한 모든 프로젝트에서 jcenter의 리포지토리를 볼 수 있게 하고자 allprojects 블록에서 jcenter를 리포지토리로서 추가한다.
의존성(dependency)
코드에서 두 모듈 간의 연결.
객체 지향 언어에서는 두 클래스 간의 관계라고도 말함.
일반적으로 둘 중 하나가 다른 하나를 어떤 용도를 위해 사용함.

모듈의 build.gradle (/app/build.gradle)

빌드에 필요한 앱의 설정 등을 기술하는 빌드 스크립트이다.

프로젝트의 build.gradle에서는 Android Gradle Plugin은 의존관계에는 들어 있었지만 실제로 적용은 하지 않았는데

apply plugin: 'com.android.application'

위와 같이 기술함으로써 Gradle Plugin 으로서 Android Gradle Plugin을 이용할 수 있다.

 

android 블록은 빌드 시 이용할 설정이 기술 돼 있다. 

defaultConfig 블록에는 앱의 설정에 관한 사항이 기술돼 있다.

dependencies 블록은 의존하는 라이브러리를 기술하고 있다.

빌드에 필요한 앱의 설정 등을 기술하는 빌드 스크립트. 이 파일은 수정할 일이 아주 많다.

// 1번 Android Gradle 플러그인 적용
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"

// 2번 Android Gradle 플러그인에서 이용되는 Android 블록
android {
    compileSdkVersion 28
    buildToolsVersion "29.0.1"
    
    // 3번 앱 자체의 설정
    defaultConfig {
        applicationId "com.avon.lv"
        minSdkVersion 20
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled = true
    }
}

dependencies {
	// 4번 libs 디렉터리에 있는 jar 파일을 읽어온다.
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 5번 의존관계
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

1번처럼 기술함으로써 Gradle 플러그인으로서 Android Gradle 플러그인을 이용할 수 있다.

2번은 Android 블록은 빌드시 이용할 설정이 기술돼 있다.

3번 defaultConfig블록에는 앱의 설정에 관한 사항이 기술돼 있다.

4번과 5번은 의존하는 라이브러리를 기술하고 있다.

4번의 fileTree에서 인수로 libs와 *.jar을 지정함으로써 libs 디렉터리에 있는 jar 확장자 파일을 가져와 의존관계로서 기술한다.

5번은 이용할 라이브러리를 Maven 리포지토리에서 가져와 이용한다. 그 밖에도 라이브러리를 늘리고 싶을 때는 이곳에 추가해서 이용할 수 있다.

프로젝트의 settings.gradle (/settings.gradle)

gradle에는 일반적으로 settings.gradle 파일이 있어야 한다.

include ':app', ':lib'

위와 같이 어떤 모듈이 있는지 기술한다.

 

위의 3개의 파일이 가장 자주 이용됩니다.

나머지도 알아두면 편리하므로 계속해서 살펴보겠습니다.

Gradle Wrapper 설정 (/gradle/wrapper/gradle-wrapper.properties)

Gradle에는 Gradle Wrapper라는 매커니즘이 있다. Gradle Wrapper는 앱을 빌드할 때 필요한 gradle이 설치돼 있지 않아도 Gradle Binary를 다운로드해서 설정합니다. 이로써 서버 상에 gradle이 설치돼 있지 ㅇ낳아도 실행할 수 있고, 팀으로 개발할 때 모두 같은 환경에서 빌드할 수 있습니다.

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

distributionUrl 에서 다운로드할 gradle의 URL설정 등을 할 수 있지만 기본값으로 설정돼 있으니 기본적으로 편집할 필요는 없습니다.

 

proguard 설정 파일 (/app/proguard-rules.pro)

프로가드는 빌드 시 코드를 난독화하거나 최적화하는 도구입니다. 또한 빌드 시 사용하지 않는 메서드를 지워줍니다.

코드를 난독화하면 디컴파일해 apk로부터 소스코드를 쉽게 읽어내는 행위를 막을 수 있습니다.

안드로이드에서는 한 앱에서 메서드 수가 65,536개를 넘으면 Multidex라는 특수한 기능을 이용해야 하지만 그것도 프로가드를 이용하면 대개는 관련 오류를 피할 수 있습니다. 기본값은 주석뿐이고 아무것도 쓰여 있지 않지만 이곳에 프로가드로 난독화하지 않을 클래스 등을 지정할 수 있습니다.

 

그 밖에도 라이브러리를 설치할 때 '프로가드 설정에 추가하세요' 와 같은 내용이 있을 경우에 이 파일에 설정할 필요가 있습니다. 또한 디버그할 때는 잘 작동했는데릴리스 빌드일 때 빌드할 수 없거나 메서드를 찾을 수 없는 등의 충돌이 있을 때는 프로가드 설정을 의심해보세요.

 

Gradle 표준 프로퍼티 파일 (/gradle.properties)

gradle.properties에 기술하면 build.gradle에서 참조할 수 있게 됩니다.

 

gradle 표준 프로퍼티 파일(/gradle.properties)

testMessage=this is test!

build.gradle 파일 (/app/build.gradle)

println testMessage

위 와 같이 기술하고 앱을 빌드해보겠습니다.

 

앱을 빌드할 때 아래와 같이 표시되는 것을 확인할 수 있습니다.

 

SDK 위치를 지정하는 프로퍼티 파일 (/local.properties)

이 파일에 SDK 위치가 적혀있지만 이 설정보다도 안드로이드 스튜디오의 설정을 우선합니다.

그래서 SDK 디렉터리를 변경하고 싶을 때는 안드로이드 스튜디오의 설정에서

[Appearance & Behavior] -> [System Settings] -> [Android SDK]를 열어 변경합니다.

이 파일은 개발자마다 다를 수 있어 Git 등의 VCS(Version Control System)에는 포함하지 않도록 합시다.

 

 

MVP

MVP(Model View Presenter)는 사용자 인터페이스를 구축할 때 이용하는 설계 기법이다.

Model

데이터와 비즈니스 로직이 들어있고, UI에 관한 로직은 가지지 않는다.

데이터베이스나 API 접근에 관한 처리는 여기에 들어간다.

Presenter

모델과 뷰 사이에서 서로 통신한다.

뷰에서 발생한 이벤트가 프레젠터에 알려지면 프레젠터는 그 이벤트에 대응하는 처리.

뷰와 모델 사이에는 항상 프레젠터가 들어간다. 모델이나 뷰의 실체인 인스턴스를 프레젠터로부터 직접 참조하게하지 않고, 인터페이스 등을 이용해 접근할 수 있게 한다.

이렇게 하면 테스트 시에 목 객체(Mock Object)로 대체할 수 있어 테스트하기가 쉬워진다.

MVP 설계의 장점

Model, View, Presenter로 역할을 명확히 나누므로 어느 처리 내용이 어디에 있는지 명확해지고 코드의 관리 효율이 높아진다. MVP패턴으로 설계하면 필연적으로 역할을 나눠야 하기에 액티비티에 구현을 채워넣을 수 없게 된다. 

결과적으로 처리를 나눌 수 있어 액티비티를 작게 만들 수 있다. 또한 뷰와 모델 사이에 프레젠터가 들어가므로 뷰와 모델의 의존관계가 없어진다.

MVP 설계의 단점

프레젠터는 인터페이스를 통해 뷰와 모델에 접근하므로 그것들의 위치를 인터페이스로서 정의할 필요가 있다.

이 부분이 길어지기 쉽다. 또한 모델에서 가져온 데이터를 뷰에 표시하는 것을 개발자가 직접 구현해야 한다.

안드로이드에는 기본적으로 MVP 패턴을 지원하는 프레임워크가 없어서 어떻게 UI 로직을 프레젠터로 분리하는가 하는 설계상의 난도가 높다는 것도 단점으로 들 수 있다.


MVVM

Android Gradle Plugin을 통해 데이터 바인딩(Databinding)이 지원된다.

Databinding은 사용자 인터페이스와 데이터를 연결하는(바인딩하는) 메커니즘이다.

데이터 바인딩을 활용한 설계 기법으로 MVVM(Model View ViewModel)이 있다.

 

모델에는 MVP의 모델처럼 데이터와 비즈니스 로직이 들어간다.

 

뷰는 데이터를 표시한다. MVP와 달리 ViewModel이 모델에서 가져온 데이터를 반영해서 표시한다.

ViewModel 가진 값이 데이터 바인딩으로 자동적으로 뷰에 반영되므로 뷰 부분에서 반영하는 구현을 할 필요가 없어진다. 하지만 안드로이드에서는 애니메이션이나 액티비티 전환 등 ViewModel에서 구현하기 어려운 항목이 있다. 그런 부분은 뷰에서 구현할 필요가 있다.

 

기본적으로 ViewModel은 뷰의 상태와 UI에 관한 로직을 구현하고, 데이터 바인딩을 통해 ViewModel의 상태가 뷰에 반영된다. 또한 뷰 클릭 등의 이벤트를 ViewModel이 받고 모델과 데이터를 주고받아 데이터 바인딩으로 뷰의 상태를 갱신한다.

MVVM 설계의 장점

MVP 패턴처럼 역할을 분리할 수 있으므로 액티비티를 작게 만들 수 있다.

데이터 바인딩으로 MVP일 때 기술하는 모델에서 가져온 데이터를 뷰에 반영하는 로직도 작성할 필요가 없으므로 액티비티의 코드를 많이 줄일 수 있다. 프레젠터와 마찬가지로 뷰에 의존하는 코드가 없어 테스트하기 쉽다.

MVVM 설계의 단점

바인딩에 대한 처리는 자동으로 생성되므로 데이터 바인딩 처리는 블랙박스화돼 있다. 자동으로 생성된 코드는 일반적으로 가독성이 낮고 디버그하기 어렵다.

black은 컴포넌트 안을 보거나 변경할 수 없으므로 고안된 대로 재사용이 가능하다.
고안된 대로 쓸 수 있기 때문에 일관된 프로그래밍이 가능하다.

 

Preference

안드로이드 개발 레벨업 교과서

+ Recent posts