SlideShare a Scribd company logo
1 of 39
Download to read offline
Robolectric을 활용한
안드로이드 테스팅
정상혁
0
테스트 코드란?
검증을 위한 코드
@Test
public void testSchemeFile() throws Exception {
String uri = "file://path/on/the/device/1.png";
Scheme result = Scheme.ofUri(uri);
Scheme expected = Scheme.FILE;
assertThat(result).isEqualTo(expected);
}
@Test
public void testSchemeUnknown() throws Exception {
String uri = "other://image.com/1.png";
Scheme result = Scheme.ofUri(uri);
Scheme expected = Scheme.UNKNOWN;
assertThat(result).isEqualTo(expected);
}
( Android UniversalImage Loader의 )BaseImageDownloaderTest
테스트 프레임워크
테스트 실행 : JUnit
안드로이드 SDK에서도 JUnit기반의 테스트 프레임워크 제공
테스트용 가짜 객체 만들기 : Mockito, JMock, PowerMock
안드로이드를 위한 테스트 프레임워크
Robolectric, Robotium, Spoon, Robospock
왜 테스트를 코딩하는가?
디버깅 편의성
설계개선
동작하는 예제, 명세
회귀 테스트
집중력
유의할 개념
JUnit으로 하는 테스트 != 유닛 테스트
Functional테스트 (혹은 시스템 테스트)도 JUnit으로 작성하는 경우도 많다.
테스트 코드 작성 != TDD
TDD는 테스트를 작성하는 하나의 방식
TDD는 Testfirst-> Test통과 -> Refactoring단계를 거침
안드로이드 테스트의
장벽
Mock을 쓰기 어려운 기본 프레임워크
구조
예: 상위클래스의 메소드를 호출
Activity.getViewById(int), getSystemService(String)
빈약한 기본 Mock클래스
android.test.mock 아래에 MockContext, MockApplication, MockResource 등
UnsupportedOperationException을 던지는 껍데기일 뿐
필요한 동작은 직접 override해서 구현해야 함.
static public class MockServiceContext extends MockContext { @Overrride
public getSystemService(String name){
……
}
}
Instrumentation Test의 높은 난이도
예: Activity를 테스트할때 ActivityTestCase, ActivityUnitTestCase,
ActivityInstrumentationTestCase2의 세 가지 클래스 중 어느것을 써야할까?
많은 예외
ActivityUnitTestCase에서 Dialog생성 등에 Event가 전달되면 BadToken
Exception이 발생
ActivityInstrumentationTestCase2에서 Dialog객체를 생성 후 dismiss() 메서
드를 호출하지 않으면 leak window Exception이 발생
UI 테스트 본연의 어려움
Layer의 역할상 UI 생성과 이벤트를 다루는 코드의 비중이 높음
웹어플리케이션 등 다른 플랫폼에서도 테스트하기 어려운 분야
깨어지기 쉬운 테스트
익명 클래스 등을 통해서 처리되는 이벤트는 Mock 객체로 바꾸고 추적하기가 어려움
느린 테스트 실행
한줄을 고쳐도 패키징 -> 설치 -> 실행 싸이클을 거친다
가장 치명적
Robolectric 활용
Robolecric은?
java.lang.RuntimeException: Stub!?
IDE안에서 안드로이드 코드를 돌리면 위와 같은 에러가 남.
Robolectric은 Android SDK가 제공하는 클래스에 가짜 동작을 심어서 JVM에서
Android 코드를 실행한다.
활발한 Github 프로젝트 :
174명의 기여자. Jake Wharton 등 유명 개발자도 참여
꾸준한 발전
Kitkat이슈 :
구글에서 1.x버전을 자체 fork한 소스가 Android 소스 저장소에 있음
https://github.com/robolectric/robolectric
http://robolectric.org/release-notes/
https://github.com/robolectric/robolectric/pull/881
https://android.googlesource.com/platform/external/robolectric/
왜 유용한가?
public class ViewParseUtils {
private static final String TAG = "ViewParseUtils";
public static Calendar parseDate(TextView view, DateFormat dateFormat) {
String dateString = view.getText().toString();
Calendar inputDate = Calendar.getInstance();
try {
inputDate.setTime(dateFormat.parse(dateString));
} catch (ParseException e) {
Log.i(TAG,"fail to parse : " + dateString);
inputDate.setTimeInMillis(System.currentTimeMillis());
// set today
}
return inputDate;
}
}
JVM에서 테스트가 어려운 Android 코드
Android SDK 의존 클래스가 존재 ( Log, View)
Mockito를 이용
@RunWith(MockitoJUnitRunner.class)
public class ViewParseUtilsMockTest {
@Mock TextView input;
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.KOREAN);
@Test
public void validDate(){
given(input.getText()).willReturn("2013-03-14");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertDate(parsed, 2013, 3, 14); // custom assert
}
@Test
public void strangeButValidDate(){
given(input.getText()).willReturn("2013-13-03");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertDate(parsed, 2014, 1, 3); // custom assert
}
@Test
@Ignore // AndroidLog 코드에 걸려서 test가 fail한다.
public void wrongDateFormat(){
given(input.getText()).willReturn("2013/5/3");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertToday(parsed); // custom assert
}
...
}
( )ViewParseUtilsMockTest.java
View의 기대동작을 Mock API로 지정해야함
'Log.i(..)'같은 Static 호출은 일반적인 Mocking불가능
PowerMock을 쓰면 가능하긴 함
Robolectric을 이용
@Test
public void validDate(){
input.setText("2013-03-14");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertDate(parsed, 2013, 3, 14);
}
@Test
public void strangeButValidDate(){
input.setText("2013-13-03");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertDate(parsed, 2014, 1, 3);
}
@Test
public void wrongDateFormat(){
input.setText("2013/5/3");
Calendar parsed = ViewParseUtils.parseDate(input, format);
assertToday(parsed);
}
( )ViewParseUtilsTest.java
View 객체를 별도의 Mock없이 그대로 사용가능
Log.i같은 static 메서드도 바로 기본 동작을 수행
Log를 System.out으로 출력하기
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SystemUtilsTest {
@Before
public void setUp() {
ShadowLog.stream = System.out;
}
android.util.Log를 호출하는 클래스라도 JVM에서 바로 실행
단말의 SDK 정보를 원하는 값으로
public static void setModel(String model) {
Robolectric.Reflection.setFinalStaticField(Build.class, "MODEL", model);
}
public static void setManufacturer(String manufacturer) {
Robolectric.Reflection.setFinalStaticField(Build.class, "MANUFACTURER", manufacturer);
}
public static void setOsVersion(String version){
Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "RELEASE", version);
}
public static void setSdkVersion(int version) {
Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", version);
}
Build.VERSION클래스 정보를 마음대로 설정 가능
Robolectric.Reflection.setFinalStaticField(..) 이용
Http호출을 하는 클라이언트에서 단말의 정보를 조합해서 userAgent를 생성하는 기
능을 테스트할때 유용했음
System서비스의 결과를 원하는 값으로
public static void setDeviceId(String deviceId) {
getTelManager().setDeviceId(deviceId);
}
public static void setNetworkOperatorName(String operatorName) {
getTelManager().setNetworkOperatorName(operatorName);
}
private static ShadowTelephonyManager getTelManager() {
Context context = Robolectric.application;
return Robolectric.shadowOf((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
}
HTTP API 파싱 테스트
실제 API 서버를 호출해서 통합테스트해도 좋다
예상되는 호출결과를 고정해서 테스트하기
예외 테스트에 특히 유용
비정상적인 응답 (예: 서버 점검 중일때 )
앞으로 변화가 예상되는 응답
별도의 파일로 분리해서 관리하면 편리함
편집 용이성
API 명세의 예제가 됨
통합 테스트 사례
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class NaverSearchTest {
@Test
public void shouldSearch(){
// given
RestTemplate apiClient = createRestClient();
Map<String, String> params = new HashMap<String, String>();
String url = "http://openapi.naver.com/search?key={key}&target={target}&query={query}&start={start}&display={display}";
params.put("key", "....");
params.put("target", "news");
params.put("query", "네이버 오픈세미나");
params.put("start", "1");
params.put("display", "15");
// when
Channel channel = apiClient.getForObject(url, Channel.class, params);
// then
@SuppressWarnings("unchecked")
List<Item> items = channel.getItems();
// assert대신 System.out으로 출력
}
( )SearchServiceTest.java
SpringAndroid RestTemlate을 이용한 네이버 검색 오픈 API
Robolectric이 없이 그냥 JUnit4만 쓴다면?
java.lang.UnsatisfiedLinkError: android.util.Log.isLoggable(Ljava/lang/String;I)Z
at android.util.Log.isLoggable(Native Method)
at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:85)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:472)
같이 쓸만한 라이브러리
비동기 호출을 테스트
가짜 API서버를 쉽게 만듬
Awaitility
Mock Http Server
Awaitility + Mock Http Server 활용 사례
@Test
public void responseShouldBeParsedWithUnknownProperties() throws Exception {
// Given
responseProvider
.expect(com.github.kristofa.test.http.Method.GET, "/")
.respondWith(200, "application/json",
"{"name":"hello", "unknown":"unknown", "property":"unknown property"}");
// When
Request<Person> request = new Jackson2Request<Person>(url,
Person.class, listener, errorListener);
requestQueue.add(request);
with().await("testWhenUnknownPropertiesExist").until(
wasListenerCalled(listener));
// Then
Person person = listener.getLastResponse();
assertThat(person.name, is("hello"));
}
( )Jackson2RequestIntegrationTest
Volley(네트워크 라이브러리)를 활용한 비동기 호출을 테스트
앞으로 속성이 추가되어도 기존 버전의 파싱메서드가 잘 동작하는지를 검증
DisplayMetricsDensity
@RunWith(RobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class PixelUtilsTest {
private Context context;
@Before
public void setUp() {
ShadowLog.stream = System.out;
this.context = Robolectric.application;
}
@Test
public void shouldGetDpFromPixel(){
Robolectric.setDisplayMetricsDensity(1.5f);
int dp = PixelUtils.getDpFromPixel(context, 50);
assertThat(dp, is(33));
}
( )PixelUtilsTest
클래스의 다양한 메서드를 활용할 수 있다org.robolectric.Robolectric
View 의존 테스트
@Test
public void shouldChangeScreenBrightness() {
TestActivity activity = createActivity(TestActivity.class);
float brightness = 0.5f;
ScreenUtils.setScreenBrightness(activity, brightness);
LayoutParams lp = activity.getWindow().getAttributes();
assertThat(lp.screenBrightness, is(brightness));
}
private <T extends Activity> T createActivity(Class<T> activityClass) {
ActivityController<T> controller = Robolectric.buildActivity(activityClass);
controller.create();
return controller.get();
}
( )ScreenUtilsTest
Activity는 ActivityController를 이용해서 생성
assertj-android
assertThat(layout).isVisible()
.isVertical()
.hasChildCount(4)
.hasShowDividers(SHOW_DIVIDERS_MIDDLE);
View객체에 대한 assert를 편하게 해주는 도우미 라이브러리
어떻게 활용할 것인가?
문자열, 날짜 처리, 프로토콜 파싱 영역에서 이득이 많다
java.lang, java.util, java.io 패키지가 다루는 영역에 우선 집중
특히 예외 상황
UI영역의 테스트에 너무 많은 기대를 걸지는 말자
Utility클래스부터 시작
버전 2.3부터는 실제 Sqlite 구현체를 이용하기 시작
DB관련 테스트도 시도해볼만함
테스트의 이득이 높은 영역을 분리해서 설계하라
재활용/기능 추가/버그 발견에도 좋은 구조가 될것이다.
코드 기여
Javadoc의 오타부터
https://github.com/robolectric/robolectric/pull/804
ShadowCookieManager의 javadoc에서 TelephonyManager-> CookieManager
오타 수정
ShadowCookieManager 재구현
https://github.com/robolectric/robolectric/pull/853
Robolectric 2.2까지는 단순히 HashMap에 key,value를 저장하는 수준
Expires같은 속성이 들어가면 실제 SDK와 다르게 동작함. (아래 코드는 fail)
cookieManager.setCookie(httpUrl, "name=value; Expires=Wed, 11 Jul 2035 08:12:26 GMT");
assertThat(cookieManager.getCookie(httpUrl)).isEqualTo("name=value");
실제 단말에서의 동작을 AndroidTestCase로 확인
CookieManager cookieManager;
public void setUp() {
Context context = getContext();
CookieSyncManager.createInstance(context);
cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
}
public void testRemoveExpiredCookie() {
cookieManager.setCookie(url, "name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT");
cookieManager.setCookie(url, "name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT");
cookieManager.removeExpiredCookie();
assertEquals("name=value", cookieManager.getCookie(url));
}
https://gist.github.com/benelog/7655764
유사한 테스트 케이스를 Robolectric으로 작성
이를 통과시키는 ShadowCookieManager를 구현하여 Pullrequest
Robolectric에 들어갈 코드를 Robolecric으로 검증했음
CookieManager cookieManager = Robolectric.newInstanceOf(CookieManager.class);;
@Test
public void shouldRemoveExpiredCookie() {
cookieManager.setCookie(url, "name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT");
cookieManager.setCookie(url, "name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT");
cookieManager.removeExpiredCookie();
assertThat(cookieManager.getCookie(url)).isEqualTo("name=value");
}
ShawdowProcess 구현
https://github.com/robolectric/robolectric/pull/861/
android.os.Process.myPid()에서 나오는 값을 가짜로 지정할 수 있도록
Android 소스 저장소의 Robolectric fork판에도 유사한 클래스가 있음
@Test
public void shouldBeTrueWhenThisContextIsForeground(){
int pid = 3;
ShadowProcess.setPid(pid);
createRunningAppProcessInfo(pid);
boolean foreground = ActivityUtils.isContextForeground(context);
assertThat(foreground, is(true));
}
코드 기여할 때 유의할 점
master의 최신 커밋으로 rebase
적절한 테스트 코드를 같이 커밋
LocalPC에서 모든 테스트를 돌려보고 커밋 (mvn test)
Travis CI 통과를 확인
참조
Indent에는 탭대신 공백 2칸
http://robolectric.org/contributor-guidelines/
정리
Android 테스트는 난관이 많다
특히 느린 실행속도
Robolectric이 도움이 된다
테스트하기 쉬운 영역부터
문자열, API 파싱. 유틸리티
궁극적으로는 설계개선을 고민
코드 기여도 어렵지 않다
기여자에게 관대하다
참고자료
(helloworld 블로그)
Gradle + Robolectric 설정 예제
Android에서 @Inject, @Test
https://github.com/kvandermast/my-robolectric-app
https://github.com/robolectric/deckard-gradle

More Related Content

What's hot

ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는Taegon Kim
 
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기NAVER Engineering
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs기동 이
 
Web Components 101 polymer & brick
Web Components 101 polymer & brickWeb Components 101 polymer & brick
Web Components 101 polymer & brickyongwoo Jeon
 
[115] clean fe development_윤지수
[115] clean fe development_윤지수[115] clean fe development_윤지수
[115] clean fe development_윤지수NAVER D2
 
Html5 web workers
Html5 web workersHtml5 web workers
Html5 web workersWoo Jin Kim
 
GKAC 2015 Apr. - Android Looper
GKAC 2015 Apr. - Android LooperGKAC 2015 Apr. - Android Looper
GKAC 2015 Apr. - Android LooperGDG Korea
 
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개beom kyun choi
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSCirculus
 
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modificationSecrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modificationHyuncheol Jeon
 
[11]Android DataBinding : 기초에서 고급까지
[11]Android DataBinding : 기초에서 고급까지[11]Android DataBinding : 기초에서 고급까지
[11]Android DataBinding : 기초에서 고급까지NAVER Engineering
 
React 튜토리얼 1차시
React 튜토리얼 1차시React 튜토리얼 1차시
React 튜토리얼 1차시태현 김
 
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기Kim Hunmin
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시태현 김
 
React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작Taegon Kim
 
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.병대 손
 
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기XpressEngine
 

What's hot (20)

ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는ReactJS | 서버와 클라이어트에서 동시에 사용하는
ReactJS | 서버와 클라이어트에서 동시에 사용하는
 
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기
[TECHCON 2019: MOBILE - Android]2.예제에서는 알려주지 않는 Model 이야기
 
Spring Boot 2
Spring Boot 2Spring Boot 2
Spring Boot 2
 
Nodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjsNodejs, PhantomJS, casperJs, YSlow, expressjs
Nodejs, PhantomJS, casperJs, YSlow, expressjs
 
Web Components 101 polymer & brick
Web Components 101 polymer & brickWeb Components 101 polymer & brick
Web Components 101 polymer & brick
 
[115] clean fe development_윤지수
[115] clean fe development_윤지수[115] clean fe development_윤지수
[115] clean fe development_윤지수
 
Html5 web workers
Html5 web workersHtml5 web workers
Html5 web workers
 
GKAC 2015 Apr. - Android Looper
GKAC 2015 Apr. - Android LooperGKAC 2015 Apr. - Android Looper
GKAC 2015 Apr. - Android Looper
 
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
옛날 웹 개발자가 잠깐 맛본 Vue.js 소개
 
Startup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JSStartup JavaScript 8 - NPM, Express.JS
Startup JavaScript 8 - NPM, Express.JS
 
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modificationSecrets of the JavaScript Ninja - Chapter 12. DOM modification
Secrets of the JavaScript Ninja - Chapter 12. DOM modification
 
Redux
ReduxRedux
Redux
 
[11]Android DataBinding : 기초에서 고급까지
[11]Android DataBinding : 기초에서 고급까지[11]Android DataBinding : 기초에서 고급까지
[11]Android DataBinding : 기초에서 고급까지
 
React 튜토리얼 1차시
React 튜토리얼 1차시React 튜토리얼 1차시
React 튜토리얼 1차시
 
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
웹 프론트엔드 개발자의 얕고 넓은 Rx 이야기
 
React 튜토리얼 2차시
React 튜토리얼 2차시React 튜토리얼 2차시
React 튜토리얼 2차시
 
React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작React Native를 사용한
 초간단 커뮤니티 앱 제작
React Native를 사용한
 초간단 커뮤니티 앱 제작
 
Spring Boot 1
Spring Boot 1Spring Boot 1
Spring Boot 1
 
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
React 애플리케이션 아키텍처 - 아무도 알려주지 않아서 혼자서 삽질했다.
 
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
XECon2015 :: [2-2] 박상현 - React로 개발하는 SPA 실무 이야기
 

Viewers also liked

[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functionsNAVER D2
 
[D2 오픈세미나]2.모바일웹디버깅
[D2 오픈세미나]2.모바일웹디버깅[D2 오픈세미나]2.모바일웹디버깅
[D2 오픈세미나]2.모바일웹디버깅NAVER D2
 
Papago/N2MT 개발이야기
Papago/N2MT 개발이야기Papago/N2MT 개발이야기
Papago/N2MT 개발이야기NAVER D2
 
데이터분석과통계2 - 최재걸님
데이터분석과통계2 - 최재걸님데이터분석과통계2 - 최재걸님
데이터분석과통계2 - 최재걸님NAVER D2
 
텀 프로젝트에서 제품 프로젝트로 - 성준영님
텀 프로젝트에서 제품 프로젝트로 - 성준영님텀 프로젝트에서 제품 프로젝트로 - 성준영님
텀 프로젝트에서 제품 프로젝트로 - 성준영님NAVER D2
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promiseNAVER D2
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitiveNAVER D2
 
[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선NAVER D2
 
[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridappNAVER D2
 
[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신NAVER D2
 
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님NAVER D2
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generatorNAVER D2
 
[D2 COMMUNITY] Open Container Seoul Meetup - Docker security
[D2 COMMUNITY] Open Container Seoul Meetup - Docker security[D2 COMMUNITY] Open Container Seoul Meetup - Docker security
[D2 COMMUNITY] Open Container Seoul Meetup - Docker securityNAVER D2
 
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshiftNAVER D2
 
blue-green deployment with docker containers
blue-green deployment with docker containersblue-green deployment with docker containers
blue-green deployment with docker containersAlfred UC
 
Container & kubernetes
Container & kubernetesContainer & kubernetes
Container & kubernetesTed Jung
 
Docker d2 박승환
Docker d2 박승환Docker d2 박승환
Docker d2 박승환Seunghwan Park
 
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...NAVER D2
 
[D2 COMMUNITY] Open Container Seoul Meetup - 마이크로 서비스 아키텍쳐와 Docker kubernetes
[D2 COMMUNITY] Open Container Seoul Meetup -  마이크로 서비스 아키텍쳐와 Docker kubernetes[D2 COMMUNITY] Open Container Seoul Meetup -  마이크로 서비스 아키텍쳐와 Docker kubernetes
[D2 COMMUNITY] Open Container Seoul Meetup - 마이크로 서비스 아키텍쳐와 Docker kubernetesNAVER D2
 
[KAIST - RUN] 프로그래밍 경진대회 문제
[KAIST - RUN] 프로그래밍 경진대회 문제[KAIST - RUN] 프로그래밍 경진대회 문제
[KAIST - RUN] 프로그래밍 경진대회 문제NAVER D2
 

Viewers also liked (20)

[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 2. functions
 
[D2 오픈세미나]2.모바일웹디버깅
[D2 오픈세미나]2.모바일웹디버깅[D2 오픈세미나]2.모바일웹디버깅
[D2 오픈세미나]2.모바일웹디버깅
 
Papago/N2MT 개발이야기
Papago/N2MT 개발이야기Papago/N2MT 개발이야기
Papago/N2MT 개발이야기
 
데이터분석과통계2 - 최재걸님
데이터분석과통계2 - 최재걸님데이터분석과통계2 - 최재걸님
데이터분석과통계2 - 최재걸님
 
텀 프로젝트에서 제품 프로젝트로 - 성준영님
텀 프로젝트에서 제품 프로젝트로 - 성준영님텀 프로젝트에서 제품 프로젝트로 - 성준영님
텀 프로젝트에서 제품 프로젝트로 - 성준영님
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 4. promise
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 1. primitive
 
[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선[D2 오픈세미나]1.무한스크롤성능개선
[D2 오픈세미나]1.무한스크롤성능개선
 
[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp
 
[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신
 
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님
JavaScript 비동기 프로그래밍 집중 탐구 - 조유성님
 
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator
[D2 COMMUNITY] ECMAScript 2015 S67 seminar - 3. generator
 
[D2 COMMUNITY] Open Container Seoul Meetup - Docker security
[D2 COMMUNITY] Open Container Seoul Meetup - Docker security[D2 COMMUNITY] Open Container Seoul Meetup - Docker security
[D2 COMMUNITY] Open Container Seoul Meetup - Docker security
 
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift
[D2 COMMUNITY] Open Container Seoul Meetup - Kubernetes를 이용한 서비스 구축과 openshift
 
blue-green deployment with docker containers
blue-green deployment with docker containersblue-green deployment with docker containers
blue-green deployment with docker containers
 
Container & kubernetes
Container & kubernetesContainer & kubernetes
Container & kubernetes
 
Docker d2 박승환
Docker d2 박승환Docker d2 박승환
Docker d2 박승환
 
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...
[D2 COMMUNITY] Open Container Seoul Meetup - Running a container platform in ...
 
[D2 COMMUNITY] Open Container Seoul Meetup - 마이크로 서비스 아키텍쳐와 Docker kubernetes
[D2 COMMUNITY] Open Container Seoul Meetup -  마이크로 서비스 아키텍쳐와 Docker kubernetes[D2 COMMUNITY] Open Container Seoul Meetup -  마이크로 서비스 아키텍쳐와 Docker kubernetes
[D2 COMMUNITY] Open Container Seoul Meetup - 마이크로 서비스 아키텍쳐와 Docker kubernetes
 
[KAIST - RUN] 프로그래밍 경진대회 문제
[KAIST - RUN] 프로그래밍 경진대회 문제[KAIST - RUN] 프로그래밍 경진대회 문제
[KAIST - RUN] 프로그래밍 경진대회 문제
 

Similar to [D2 오픈세미나]5.robolectric 안드로이드 테스팅

Naver api for android
Naver api for androidNaver api for android
Naver api for androidSangon Lee
 
[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개Jong Pil Won
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Testbeom kyun choi
 
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출GDG Korea
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509영석 조
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증dagri82
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)beom kyun choi
 
Android 기초강좌 애플리캐이션 구조
Android 기초강좌 애플리캐이션 구조Android 기초강좌 애플리캐이션 구조
Android 기초강좌 애플리캐이션 구조Sangon Lee
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블YongEun Choi
 
Clean Front-End Development
Clean Front-End DevelopmentClean Front-End Development
Clean Front-End Development지수 윤
 
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)Junbum Lee
 
Pinpoint spring_camp 2015
Pinpoint spring_camp 2015Pinpoint spring_camp 2015
Pinpoint spring_camp 2015Woonduk-Kang
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018KyungHo Jung
 
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Donghyeok Kang
 
데이터베이스패턴
데이터베이스패턴데이터베이스패턴
데이터베이스패턴Suan Lee
 
MyBatis 개요와 Java+MyBatis+MySQL 예제
MyBatis 개요와 Java+MyBatis+MySQL 예제MyBatis 개요와 Java+MyBatis+MySQL 예제
MyBatis 개요와 Java+MyBatis+MySQL 예제정완 전
 
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디Youngbin Han
 

Similar to [D2 오픈세미나]5.robolectric 안드로이드 테스팅 (20)

Naver api for android
Naver api for androidNaver api for android
Naver api for android
 
[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개
 
Nodejs express
Nodejs expressNodejs express
Nodejs express
 
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 TestOkjsp 13주년 발표자료: 생존 프로그래밍 Test
Okjsp 13주년 발표자료: 생존 프로그래밍 Test
 
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출
GKAC 2015 Apr. - Battery, 안드로이드를 위한 쉬운 웹 API 호출
 
Sonarqube 20160509
Sonarqube 20160509Sonarqube 20160509
Sonarqube 20160509
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증
 
Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)Ji 개발 리뷰 (신림프로그래머)
Ji 개발 리뷰 (신림프로그래머)
 
Android 기초강좌 애플리캐이션 구조
Android 기초강좌 애플리캐이션 구조Android 기초강좌 애플리캐이션 구조
Android 기초강좌 애플리캐이션 구조
 
JUnit & AssertJ
JUnit & AssertJJUnit & AssertJ
JUnit & AssertJ
 
Effective unit testing ch3. 테스트더블
Effective unit testing   ch3. 테스트더블Effective unit testing   ch3. 테스트더블
Effective unit testing ch3. 테스트더블
 
Clean Front-End Development
Clean Front-End DevelopmentClean Front-End Development
Clean Front-End Development
 
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)
투스쿱 장고: 장고폼의 기초와 폼 패턴(11-12장)
 
Tdd 4장
Tdd 4장Tdd 4장
Tdd 4장
 
Pinpoint spring_camp 2015
Pinpoint spring_camp 2015Pinpoint spring_camp 2015
Pinpoint spring_camp 2015
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018
 
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
Java Annotation과 MyBatis로 나만의 ORM Framework을 만들어보자
 
데이터베이스패턴
데이터베이스패턴데이터베이스패턴
데이터베이스패턴
 
MyBatis 개요와 Java+MyBatis+MySQL 예제
MyBatis 개요와 Java+MyBatis+MySQL 예제MyBatis 개요와 Java+MyBatis+MySQL 예제
MyBatis 개요와 Java+MyBatis+MySQL 예제
 
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디
2.Connect Sunshine to the Cloud - 시온고 안드로이드 스터디
 

More from NAVER D2

[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다NAVER D2
 
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...NAVER D2
 
[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기NAVER D2
 
[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발NAVER D2
 
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈NAVER D2
 
[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&ANAVER D2
 
[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기NAVER D2
 
[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep LearningNAVER D2
 
[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applicationsNAVER D2
 
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingOld version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingNAVER D2
 
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지NAVER D2
 
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기NAVER D2
 
[224]네이버 검색과 개인화
[224]네이버 검색과 개인화[224]네이버 검색과 개인화
[224]네이버 검색과 개인화NAVER D2
 
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)NAVER D2
 
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기NAVER D2
 
[213] Fashion Visual Search
[213] Fashion Visual Search[213] Fashion Visual Search
[213] Fashion Visual SearchNAVER D2
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화NAVER D2
 
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지NAVER D2
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터NAVER D2
 
[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?NAVER D2
 

More from NAVER D2 (20)

[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다[211] 인공지능이 인공지능 챗봇을 만든다
[211] 인공지능이 인공지능 챗봇을 만든다
 
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
[233] 대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing: Maglev Hashing Scheduler i...
 
[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기[215] Druid로 쉽고 빠르게 데이터 분석하기
[215] Druid로 쉽고 빠르게 데이터 분석하기
 
[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발[245]Papago Internals: 모델분석과 응용기술 개발
[245]Papago Internals: 모델분석과 응용기술 개발
 
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
[236] 스트림 저장소 최적화 이야기: 아파치 드루이드로부터 얻은 교훈
 
[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A[235]Wikipedia-scale Q&A
[235]Wikipedia-scale Q&A
 
[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기[244]로봇이 현실 세계에 대해 학습하도록 만들기
[244]로봇이 현실 세계에 대해 학습하도록 만들기
 
[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning[243] Deep Learning to help student’s Deep Learning
[243] Deep Learning to help student’s Deep Learning
 
[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications[234]Fast & Accurate Data Annotation Pipeline for AI applications
[234]Fast & Accurate Data Annotation Pipeline for AI applications
 
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load BalancingOld version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
Old version: [233]대형 컨테이너 클러스터에서의 고가용성 Network Load Balancing
 
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
[226]NAVER 광고 deep click prediction: 모델링부터 서빙까지
 
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
[225]NSML: 머신러닝 플랫폼 서비스하기 & 모델 튜닝 자동화하기
 
[224]네이버 검색과 개인화
[224]네이버 검색과 개인화[224]네이버 검색과 개인화
[224]네이버 검색과 개인화
 
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
[216]Search Reliability Engineering (부제: 지진에도 흔들리지 않는 네이버 검색시스템)
 
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
[214] Ai Serving Platform: 하루 수 억 건의 인퍼런스를 처리하기 위한 고군분투기
 
[213] Fashion Visual Search
[213] Fashion Visual Search[213] Fashion Visual Search
[213] Fashion Visual Search
 
[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화[232] TensorRT를 활용한 딥러닝 Inference 최적화
[232] TensorRT를 활용한 딥러닝 Inference 최적화
 
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
[242]컴퓨터 비전을 이용한 실내 지도 자동 업데이트 방법: 딥러닝을 통한 POI 변화 탐지
 
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
[212]C3, 데이터 처리에서 서빙까지 가능한 하둡 클러스터
 
[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?[223]기계독해 QA: 검색인가, NLP인가?
[223]기계독해 QA: 검색인가, NLP인가?
 

Recently uploaded

A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)Tae Young Lee
 
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionKim Daeun
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Kim Daeun
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Wonjun Hwang
 
Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Wonjun Hwang
 

Recently uploaded (6)

A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)A future that integrates LLMs and LAMs (Symposium)
A future that integrates LLMs and LAMs (Symposium)
 
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution DetectionMOODv2 : Masked Image Modeling for Out-of-Distribution Detection
MOODv2 : Masked Image Modeling for Out-of-Distribution Detection
 
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
Continual Active Learning for Efficient Adaptation of Machine LearningModels ...
 
캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차캐드앤그래픽스 2024년 5월호 목차
캐드앤그래픽스 2024년 5월호 목차
 
Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)Merge (Kitworks Team Study 이성수 발표자료 240426)
Merge (Kitworks Team Study 이성수 발표자료 240426)
 
Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)Console API (Kitworks Team Study 백혜인 발표자료)
Console API (Kitworks Team Study 백혜인 발표자료)
 

[D2 오픈세미나]5.robolectric 안드로이드 테스팅

  • 3. 검증을 위한 코드 @Test public void testSchemeFile() throws Exception { String uri = "file://path/on/the/device/1.png"; Scheme result = Scheme.ofUri(uri); Scheme expected = Scheme.FILE; assertThat(result).isEqualTo(expected); } @Test public void testSchemeUnknown() throws Exception { String uri = "other://image.com/1.png"; Scheme result = Scheme.ofUri(uri); Scheme expected = Scheme.UNKNOWN; assertThat(result).isEqualTo(expected); } ( Android UniversalImage Loader의 )BaseImageDownloaderTest
  • 4. 테스트 프레임워크 테스트 실행 : JUnit 안드로이드 SDK에서도 JUnit기반의 테스트 프레임워크 제공 테스트용 가짜 객체 만들기 : Mockito, JMock, PowerMock 안드로이드를 위한 테스트 프레임워크 Robolectric, Robotium, Spoon, Robospock
  • 5. 왜 테스트를 코딩하는가? 디버깅 편의성 설계개선 동작하는 예제, 명세 회귀 테스트 집중력
  • 6. 유의할 개념 JUnit으로 하는 테스트 != 유닛 테스트 Functional테스트 (혹은 시스템 테스트)도 JUnit으로 작성하는 경우도 많다. 테스트 코드 작성 != TDD TDD는 테스트를 작성하는 하나의 방식 TDD는 Testfirst-> Test통과 -> Refactoring단계를 거침
  • 8. Mock을 쓰기 어려운 기본 프레임워크 구조 예: 상위클래스의 메소드를 호출 Activity.getViewById(int), getSystemService(String)
  • 9. 빈약한 기본 Mock클래스 android.test.mock 아래에 MockContext, MockApplication, MockResource 등 UnsupportedOperationException을 던지는 껍데기일 뿐 필요한 동작은 직접 override해서 구현해야 함. static public class MockServiceContext extends MockContext { @Overrride public getSystemService(String name){ …… } }
  • 10. Instrumentation Test의 높은 난이도 예: Activity를 테스트할때 ActivityTestCase, ActivityUnitTestCase, ActivityInstrumentationTestCase2의 세 가지 클래스 중 어느것을 써야할까? 많은 예외 ActivityUnitTestCase에서 Dialog생성 등에 Event가 전달되면 BadToken Exception이 발생 ActivityInstrumentationTestCase2에서 Dialog객체를 생성 후 dismiss() 메서 드를 호출하지 않으면 leak window Exception이 발생
  • 11. UI 테스트 본연의 어려움 Layer의 역할상 UI 생성과 이벤트를 다루는 코드의 비중이 높음 웹어플리케이션 등 다른 플랫폼에서도 테스트하기 어려운 분야 깨어지기 쉬운 테스트 익명 클래스 등을 통해서 처리되는 이벤트는 Mock 객체로 바꾸고 추적하기가 어려움
  • 12. 느린 테스트 실행 한줄을 고쳐도 패키징 -> 설치 -> 실행 싸이클을 거친다 가장 치명적
  • 14. Robolecric은? java.lang.RuntimeException: Stub!? IDE안에서 안드로이드 코드를 돌리면 위와 같은 에러가 남. Robolectric은 Android SDK가 제공하는 클래스에 가짜 동작을 심어서 JVM에서 Android 코드를 실행한다.
  • 15. 활발한 Github 프로젝트 : 174명의 기여자. Jake Wharton 등 유명 개발자도 참여 꾸준한 발전 Kitkat이슈 : 구글에서 1.x버전을 자체 fork한 소스가 Android 소스 저장소에 있음 https://github.com/robolectric/robolectric http://robolectric.org/release-notes/ https://github.com/robolectric/robolectric/pull/881 https://android.googlesource.com/platform/external/robolectric/
  • 16. 왜 유용한가? public class ViewParseUtils { private static final String TAG = "ViewParseUtils"; public static Calendar parseDate(TextView view, DateFormat dateFormat) { String dateString = view.getText().toString(); Calendar inputDate = Calendar.getInstance(); try { inputDate.setTime(dateFormat.parse(dateString)); } catch (ParseException e) { Log.i(TAG,"fail to parse : " + dateString); inputDate.setTimeInMillis(System.currentTimeMillis()); // set today } return inputDate; } } JVM에서 테스트가 어려운 Android 코드 Android SDK 의존 클래스가 존재 ( Log, View)
  • 17. Mockito를 이용 @RunWith(MockitoJUnitRunner.class) public class ViewParseUtilsMockTest { @Mock TextView input; SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.KOREAN); @Test public void validDate(){ given(input.getText()).willReturn("2013-03-14"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertDate(parsed, 2013, 3, 14); // custom assert } @Test public void strangeButValidDate(){ given(input.getText()).willReturn("2013-13-03"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertDate(parsed, 2014, 1, 3); // custom assert } @Test @Ignore // AndroidLog 코드에 걸려서 test가 fail한다. public void wrongDateFormat(){ given(input.getText()).willReturn("2013/5/3"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertToday(parsed); // custom assert } ... } ( )ViewParseUtilsMockTest.java View의 기대동작을 Mock API로 지정해야함 'Log.i(..)'같은 Static 호출은 일반적인 Mocking불가능 PowerMock을 쓰면 가능하긴 함
  • 18. Robolectric을 이용 @Test public void validDate(){ input.setText("2013-03-14"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertDate(parsed, 2013, 3, 14); } @Test public void strangeButValidDate(){ input.setText("2013-13-03"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertDate(parsed, 2014, 1, 3); } @Test public void wrongDateFormat(){ input.setText("2013/5/3"); Calendar parsed = ViewParseUtils.parseDate(input, format); assertToday(parsed); } ( )ViewParseUtilsTest.java View 객체를 별도의 Mock없이 그대로 사용가능 Log.i같은 static 메서드도 바로 기본 동작을 수행
  • 19. Log를 System.out으로 출력하기 @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class SystemUtilsTest { @Before public void setUp() { ShadowLog.stream = System.out; } android.util.Log를 호출하는 클래스라도 JVM에서 바로 실행
  • 20. 단말의 SDK 정보를 원하는 값으로 public static void setModel(String model) { Robolectric.Reflection.setFinalStaticField(Build.class, "MODEL", model); } public static void setManufacturer(String manufacturer) { Robolectric.Reflection.setFinalStaticField(Build.class, "MANUFACTURER", manufacturer); } public static void setOsVersion(String version){ Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "RELEASE", version); } public static void setSdkVersion(int version) { Robolectric.Reflection.setFinalStaticField(Build.VERSION.class, "SDK_INT", version); } Build.VERSION클래스 정보를 마음대로 설정 가능 Robolectric.Reflection.setFinalStaticField(..) 이용 Http호출을 하는 클라이언트에서 단말의 정보를 조합해서 userAgent를 생성하는 기 능을 테스트할때 유용했음
  • 21. System서비스의 결과를 원하는 값으로 public static void setDeviceId(String deviceId) { getTelManager().setDeviceId(deviceId); } public static void setNetworkOperatorName(String operatorName) { getTelManager().setNetworkOperatorName(operatorName); } private static ShadowTelephonyManager getTelManager() { Context context = Robolectric.application; return Robolectric.shadowOf((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE)); }
  • 22. HTTP API 파싱 테스트 실제 API 서버를 호출해서 통합테스트해도 좋다 예상되는 호출결과를 고정해서 테스트하기 예외 테스트에 특히 유용 비정상적인 응답 (예: 서버 점검 중일때 ) 앞으로 변화가 예상되는 응답 별도의 파일로 분리해서 관리하면 편리함 편집 용이성 API 명세의 예제가 됨
  • 23. 통합 테스트 사례 @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class NaverSearchTest { @Test public void shouldSearch(){ // given RestTemplate apiClient = createRestClient(); Map<String, String> params = new HashMap<String, String>(); String url = "http://openapi.naver.com/search?key={key}&target={target}&query={query}&start={start}&display={display}"; params.put("key", "...."); params.put("target", "news"); params.put("query", "네이버 오픈세미나"); params.put("start", "1"); params.put("display", "15"); // when Channel channel = apiClient.getForObject(url, Channel.class, params); // then @SuppressWarnings("unchecked") List<Item> items = channel.getItems(); // assert대신 System.out으로 출력 } ( )SearchServiceTest.java SpringAndroid RestTemlate을 이용한 네이버 검색 오픈 API Robolectric이 없이 그냥 JUnit4만 쓴다면? java.lang.UnsatisfiedLinkError: android.util.Log.isLoggable(Ljava/lang/String;I)Z at android.util.Log.isLoggable(Native Method) at org.springframework.http.client.support.HttpAccessor.createRequest(HttpAccessor.java:85) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:472)
  • 24. 같이 쓸만한 라이브러리 비동기 호출을 테스트 가짜 API서버를 쉽게 만듬 Awaitility Mock Http Server
  • 25. Awaitility + Mock Http Server 활용 사례 @Test public void responseShouldBeParsedWithUnknownProperties() throws Exception { // Given responseProvider .expect(com.github.kristofa.test.http.Method.GET, "/") .respondWith(200, "application/json", "{"name":"hello", "unknown":"unknown", "property":"unknown property"}"); // When Request<Person> request = new Jackson2Request<Person>(url, Person.class, listener, errorListener); requestQueue.add(request); with().await("testWhenUnknownPropertiesExist").until( wasListenerCalled(listener)); // Then Person person = listener.getLastResponse(); assertThat(person.name, is("hello")); } ( )Jackson2RequestIntegrationTest Volley(네트워크 라이브러리)를 활용한 비동기 호출을 테스트 앞으로 속성이 추가되어도 기존 버전의 파싱메서드가 잘 동작하는지를 검증
  • 26. DisplayMetricsDensity @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) public class PixelUtilsTest { private Context context; @Before public void setUp() { ShadowLog.stream = System.out; this.context = Robolectric.application; } @Test public void shouldGetDpFromPixel(){ Robolectric.setDisplayMetricsDensity(1.5f); int dp = PixelUtils.getDpFromPixel(context, 50); assertThat(dp, is(33)); } ( )PixelUtilsTest 클래스의 다양한 메서드를 활용할 수 있다org.robolectric.Robolectric
  • 27. View 의존 테스트 @Test public void shouldChangeScreenBrightness() { TestActivity activity = createActivity(TestActivity.class); float brightness = 0.5f; ScreenUtils.setScreenBrightness(activity, brightness); LayoutParams lp = activity.getWindow().getAttributes(); assertThat(lp.screenBrightness, is(brightness)); } private <T extends Activity> T createActivity(Class<T> activityClass) { ActivityController<T> controller = Robolectric.buildActivity(activityClass); controller.create(); return controller.get(); } ( )ScreenUtilsTest Activity는 ActivityController를 이용해서 생성
  • 29. 어떻게 활용할 것인가? 문자열, 날짜 처리, 프로토콜 파싱 영역에서 이득이 많다 java.lang, java.util, java.io 패키지가 다루는 영역에 우선 집중 특히 예외 상황 UI영역의 테스트에 너무 많은 기대를 걸지는 말자 Utility클래스부터 시작 버전 2.3부터는 실제 Sqlite 구현체를 이용하기 시작 DB관련 테스트도 시도해볼만함 테스트의 이득이 높은 영역을 분리해서 설계하라 재활용/기능 추가/버그 발견에도 좋은 구조가 될것이다.
  • 32. ShadowCookieManager 재구현 https://github.com/robolectric/robolectric/pull/853 Robolectric 2.2까지는 단순히 HashMap에 key,value를 저장하는 수준 Expires같은 속성이 들어가면 실제 SDK와 다르게 동작함. (아래 코드는 fail) cookieManager.setCookie(httpUrl, "name=value; Expires=Wed, 11 Jul 2035 08:12:26 GMT"); assertThat(cookieManager.getCookie(httpUrl)).isEqualTo("name=value");
  • 33. 실제 단말에서의 동작을 AndroidTestCase로 확인 CookieManager cookieManager; public void setUp() { Context context = getContext(); CookieSyncManager.createInstance(context); cookieManager = CookieManager.getInstance(); cookieManager.removeAllCookie(); } public void testRemoveExpiredCookie() { cookieManager.setCookie(url, "name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT"); cookieManager.setCookie(url, "name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT"); cookieManager.removeExpiredCookie(); assertEquals("name=value", cookieManager.getCookie(url)); } https://gist.github.com/benelog/7655764
  • 34. 유사한 테스트 케이스를 Robolectric으로 작성 이를 통과시키는 ShadowCookieManager를 구현하여 Pullrequest Robolectric에 들어갈 코드를 Robolecric으로 검증했음 CookieManager cookieManager = Robolectric.newInstanceOf(CookieManager.class);; @Test public void shouldRemoveExpiredCookie() { cookieManager.setCookie(url, "name=value; Expires=Wed, 11 Jul 2035 10:18:14 GMT"); cookieManager.setCookie(url, "name2=value2; Expires=Wed, 13 Jul 2011 10:18:14 GMT"); cookieManager.removeExpiredCookie(); assertThat(cookieManager.getCookie(url)).isEqualTo("name=value"); }
  • 35. ShawdowProcess 구현 https://github.com/robolectric/robolectric/pull/861/ android.os.Process.myPid()에서 나오는 값을 가짜로 지정할 수 있도록 Android 소스 저장소의 Robolectric fork판에도 유사한 클래스가 있음 @Test public void shouldBeTrueWhenThisContextIsForeground(){ int pid = 3; ShadowProcess.setPid(pid); createRunningAppProcessInfo(pid); boolean foreground = ActivityUtils.isContextForeground(context); assertThat(foreground, is(true)); }
  • 36. 코드 기여할 때 유의할 점 master의 최신 커밋으로 rebase 적절한 테스트 코드를 같이 커밋 LocalPC에서 모든 테스트를 돌려보고 커밋 (mvn test) Travis CI 통과를 확인 참조 Indent에는 탭대신 공백 2칸 http://robolectric.org/contributor-guidelines/
  • 38. Android 테스트는 난관이 많다 특히 느린 실행속도 Robolectric이 도움이 된다 테스트하기 쉬운 영역부터 문자열, API 파싱. 유틸리티 궁극적으로는 설계개선을 고민 코드 기여도 어렵지 않다 기여자에게 관대하다
  • 39. 참고자료 (helloworld 블로그) Gradle + Robolectric 설정 예제 Android에서 @Inject, @Test https://github.com/kvandermast/my-robolectric-app https://github.com/robolectric/deckard-gradle