Maven Central 에 라이브러리 올리기

개발 이야기 2014. 8. 8. 00:21

최근에 오픈 소스 작업 해야겠다! 라고 맘 먹은 것이 있었는데 최종 결과물을 메이븐 Central 저장소에 올리는 것이 목표였습니다.

역시나 작업은 쉽지 않더군요. 우여곡절 끝에 업로드 완성했고 생각보다 폭발적인 반응을 얻어왔는데요.

오늘은 그 고생한 이야기를 털어보고자 남깁니다.


1. 작업환경

언어 : 자바 6 +Groovy
IDE : Intellij 13+
빌드 툴 : Gradle

2. gradle 을 활용한 maven pom 파일 생성하기



groupId = com.nobrain.gradle
artifactId = gradle-play-publisher

로 설정하였습니다.

maven 이라는 플러그인을 통해서 pom 작성하는데

저장소, 라이센, 개발자 정보 등을 작성하였습니다.

참고로 mavenDeployer 에 저장된 정보들 중 일부는 maven central 업로드시 반드시 작성되어야 하는 정보가 있습니다.
(https://docs.sonatype.org/display/Repository/Central+Sync+Requirements)

uploadArchives.repositories.mavenDeployer.repository(url : uri('../repo')

라는 설정이 있는데 이는 local disk 의 ../repo 에 라이브러리를 만들겠다는 의미입니다.


이렇게 하면 가장 기본적인 작업은 완료 되습니다. 이제부터는 외부의 작업이 필요한대요...

3. PGP 등록 및 Signing 하기

maven 프로젝트는 각각이 보안이 유지되기 위해서 PGP 라는 정보를 통해서 올바른 업데이트인지를 확인하는 것으로 보입니다.
그래서 반드시 PGP 를 생성하고 서버에 등록하여야 합니다.

3-1 PGP 생성하기

https://www.gnupg.org/download/index.html 에 접속하시면 PGP 를 생성할 수 있는 툴들을 굉장히 많이 제공하고 있습니다.
그중에서 저는 PGP KeyChain Access (Only Mac) 라는 툴을 이용하여 Public 키를 생성하였습니다.
(이메일과 Full Name 을 작성하여야 합니다.)


3-2 PGP 등록하기

생성한 PGP 키는 http://pgp.mit.edu/ 사이트를 통해서 등록하여야 하는데요.
(일부 잘 되어진 툴은 업로드도 함께 제공해줍니다.)

방법은 PGP KeyChain Access 기준으로 export 시 ASCII 로 추출하시면 됩니다.
추출된 ASCII 형태의 PGP 를 메모장으로 열게 되면 


---BEGIN...---

---END...--- 

 형식으로 되어 있는데 [Submit a Key] 란에 복사 붙여넣기 후 Submit 하시면 됩니다.

3-3 PGP 로 Signing 하기

Maven Central 로 업로드시 PGP 로 Signing 하지 않으면 절대로 업로드가 안되니 반드시 진행해주시기 바랍니다.


위와 같이 작성해주시면 빌드시 자동으로 signing 이 진행됩니다.


4. jar 생성하기

./gradlew uploadArchives

를 실행하면 1 에서 설명한 '../repo' 에 jar 파일이 생성되어 있습니다.


5. maven central upload 대행 서비스 이용하기

제가 이용한 서비스는 https://bintray.com/ 라는 서비스를 통해서 mavenCentral 에 업로드를 진행하였습니다.

해당 서비스에 가입하신 후 maven repository 에 package 를 추가 -> version 을 추가 순으로 진행하시면 됩니다.

6. bintray 에 jar 파일 업로드 하기

생성된 version 에 Files 에 들어가시면 해당 웹 페이지 UI 를 통해서 업로드가 가능하도록 되어 있습니다.

먼저 $groupId/$artifactId/$version/메이븐 파일 + jar 파일 이 되어 있는데
$version/메이븐파일 압축을 합니다. 

파일 업로드시에 Target Path 를 $groupId/$artifactId/ 로 지정 후 압축 파일을 업로드하면

업로드된 file 이 $groupId/$artifactId/$version/메이븐 파일 에 된 것을 확인 하실 수 있습니다.


6. maven cetral 업로드 준비

6-1. sonatype 저장소 가입하기

5 에서 얘기한 bintray 서비스는 sonatype 을 통해서 maven central 에 sync 를 합니다. 그러기 위해서는 sonatype 에 가입이 되어야 하는데요

https://issues.sonatype.org/secure/Signup!default.jspa 을 통해서 가입을 하실 수 있습니다.


6-2. 프로젝트 생성 요청하기

로그인 후 https://issues.sonatype.org/secure/CreateIssue!default.jspa 에 접속하시면 새로운 프로젝트를 생성할 수 있도록 요청할 수 있습니다. groupId, 소스 저장소나 이슈를 프로젝트를 관리하는 URL등은 필수 사항이니 꼭 적어주시기 바랍니다.

프로젝트 생성을 요청하면 빠르면 10분 안에 생성이 되었음을 알려줍니다.
(생각보다 피드백이 빠르고 댓글로 질문시 바로바로 대답해주니 잘못된 부분은 직접 질문하는게 빠릅니다.)


7. bintray <-> maven central Sync 하기

다시 bintray 로 돌아와서 5 에서 업로드한 버전으로 이동합니다.
메뉴 중에 Maven Central 을 보실 수 있는데 sync 를 요청하면 bintray 의 자체 repository 에 업로드를 진행 한 후
maven central 에 업로드 됩니다.
maven central 에 업로드시 6-1 에서 가입한 정보를 물어보니 꼭 6-1 을 진행 후 해주시기 바랍니다

promote 권한이 없다는 오류 : 6-2 가 정상 진행되지 않은 것입니다.
3-3 이 없을 시 오류 발생 할 수 있습니다.
source.jar, docs.jar 가 미작성시에도 오류가 날 수 있습니다.


8. 업로드와 sync 가 모두 완료되면

https://oss.sonatype.org/ 의 search 에서 검색하실 수 있습니다. :)



처음 해본 것인데 국내 자료가 그다지 없어서 직접 작성해보았습니다.

그림도 없고 대략적으로 작성한거라 부족함이 많지만 그래도 라이브러리 등록에 큰 도움이 되었으면 합니다.


위의 동작은

https://github.com/ZeroBrain/gradle-play-publisher 을 업로드하는 과정에서 얻은 경험을 토대로 작성하였습니다.


Maven Central 에 올라가는 라이브러리는 매우 신중히 작업해주시기 바랍니다.
누군가는 그 라이브러리로 작업을 하기 때문에 지원을 더이상 하지 않는다면 공지를 해줘야겠죠.

호기심 반 뿌듯함 반으로 작업을 했는데 생각보다 감당하기 어려운 책임감이 느껴지네요.

설정

트랙백

댓글

Android PlayStore Publisher Sample 동작

개발 이야기 2014. 8. 2. 11:29

1. 개발환경

- 개발언어 : Java 7

- IDE : Intellij 13.+

- 환경 : Maven

- 샘플코드 저장소 (Play Publisher api sample for Java Code)

Git : https://github.com/googlesamples/android-play-publisher-api


2. Maven Dependency




3. Project SDK



 


4. Project Code Struct


 


 확인 사항

1) 샘플코드 복사 (자바 파일만..)

2) APK 파일

3)0Auth 정보

4) 메이븐 의존성 라이브러리


5. Google Developer Console (https://console.developers.google.com/project)

1) 새로운 프로젝트나 기존의 프로젝트 선택

2) APIs -> Google Play Android Developer API [ON] 으로 변경




 


3) OAuth 정보 만들기

- [OAuth - Create New Client ID] 선택




 


- Json 파일 다운로드




 


6. Project Code

1) APK 파일과 OAuth 정보 준비


 $prj.dir/src/main/resources 에 복사해두세요.




2) ApplicationConfig.java



3) AndroidPublisherHelper.java


7. Run BasicUploadApk.main()


8. Play Store Developer API <-> Google Developer API연결하기



 


API 는 구글 개발자 콘솔 뿐만 아니라 플레이스토어 콘솔에서도 연동을 허가 해야 가능합니다.


project ID 가 연결되지 않았다는 콘솔 에러가 뜬다면 반드시 연결을 확인해보시기 바랍니다.





성공하시면 아래와 같은 화면을 보실 수 있습니다.





PS API 연동을 새로 작성하시거나 인증 정보를 새로 설정하신 경우에는

$HOME/.store 를 삭제 후 진행해주시기 바랍니다.



설정

트랙백

댓글

Android-Gradle-Robolectric 테스트용 iml 파일 자동 설정하기

개발 이야기 2014. 7. 23. 00:38

Robolectric 테스트 코드를 위해서 매번 iml 을 설정해줘야 하는 번거로움이 있어서
오늘은 작정하고 테스트용 iml 을 자동 설정하도록 했습니다.


1. iml 수정용 Task 생성

task initGradleTest << {

    def imlFile = project.name + '.iml'

    def parse = new XmlParser().parse(imlFile)

    def modulePath = parse.@'external.linked.project.path'

    // It's Robolectric Default ouputPath
    def outputTestPath = "file://$modulePath/build/test-classes"
    def moduleComponent = parse.component.find { it.@name == 'NewModuleRootManager' }
    def outputTest = moduleComponent.find {it.name() == 'output-test'}

    if (outputTest != null) {
        outputTest.@url = outputTestPath
    } else {
        moduleComponent.appendNode('output-test', [url : outputTestPath])
    }

    // jdk orderEntry must be last
    def orderEntry = moduleComponent.orderEntry
    def jdkOrderEntry = orderEntry.find { it.@type == 'jdk' }
    moduleComponent.remove(jdkOrderEntry)
    moduleComponent.append(jdkOrderEntry)

    // rewrite $project.iml file
    FileWriter fileWriter = new FileWriter(imlFile)
    new XmlNodePrinter(new PrintWriter(fileWriter)).print(parse)


}


2. Gradle 빌드시 Task 동작하도록 하기

tasks.preBuild.dependsOn initGradleTest

prebuild 는 모든 Task 동작시 최초의 Android Gradle Task 입니다.

3. JUnit 테스트때마다 test-code compile 하도록 하기

 

테스트 코드가 동작할 때 Run 설정에서 Defaults -> JUnit -> Before Launch 설정에 Make 보다 compileTestDebugJava 가 동작하도록 해주십시요.

(Robolectric 에서 생성한 Task 로 1번 설정에서 해주었던 output-test 경로에 맞춰서 test-class 가 compile 됩니다)


이리 하면 이제부터 JUnit 으로 하는 Robolectric 테스트는 별다른 오류 없이 동작하는 것을 확인하실 수 있습니다.


게을러터져서는 조금만 섬세하면 되는 것을 전부 자동화 하려는 욕심에 이런것까지 하게 되었네요.
원래는 idea.module 을 손대서 아예 자동 생성하도록 하려고 했는데
아무래도 그건 무리였나보더군요. 그거 하려다가 Android-Gradle 원소스를 통째로 보고 있는 제 스스로가 느껴지니 이걸 왜 하나 싶어서 이정도만으로 만족하려고 합니다.


위의 코드는 
Github : https://github.com/ZeroBrain/Android-Sqlite-Object-Convert
를 통해 공유됩니다.

설정

트랙백

댓글

Robolectric 에서 SupoortFragment 사용하기

개발 이야기 2014. 7. 10. 21:12

UI 테스트에서 가장 큰 축은 2가지입니다.

1. Activity

2. Fragment


사실 이 2개만 제어할 수 있어도 UI 테스트의 대부분을 수행하는 거라 볼 수 있지요.

그러면 이번에는 Fragment 중에서도 support.v4.fragment 를 테스트해보도록 하겠습니다.


1. MyFragment.class

public class MyFragment extends Fragment {

    public static MyFragment newInstance() {
        MyFragment fragment = new MyFragment();
        return fragment;
    }
    public MyFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_my, container, false);
    }
}

2. fragment_my.xml



    


3. MyFragmentTest.class

@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class MyFragmentTest {

    private MyFragment fragment;

    @Before
    public void setUp() throws Exception {

        FragmentActivity fragmentActivity = Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();

        fragment = new MyFragment();
        FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager();
        fragmentManager.beginTransaction().add(android.R.id.content, fragment).commit();
    }

    @Test
    public void testInit() throws Exception {

        TextView textView = (TextView) fragment.getView().findViewById(R.id.tv_frag);
        assertNotNull(textView);

        assertThat(textView.getText().toString(), equalTo(Robolectric.application.getString(R.string.hello_blank_fragment)));

    }
}

Robolectric 패키지 내부에 FragmentTestUtil.startFragment(support.v4.fragment) 가 존재 하지만 정상적으로 Fragment 를 바인딩 못해주는 버그가 있어서 저는 임의로 Fragment 바인딩을 설정하였습니다.

그리고 Fragment 가 Activity 와 직접적인 상호작용이 있다면 해당하는 액티비티를 사용해주시는게 좋겠죠?


덧.
아직 Support V7 에서 사용하는 ActionBarActivity 는 방법을 찾지 못했습니다. resource 를 못 찾는 문제가 있더군요. 방법을 찾으면 갱신하다록 하겠습니다.

설정

트랙백

댓글

Robolectric 에서 Activity 테스트하기

개발 이야기 2014. 7. 10. 20:49

Robolectric 에서 일반적인 연산 로직을 테스트 하는 것은 매우 쉽습니다. 일반적인 JUnit 테스트와 동일하기 때문입니다. Robolectric 을 이용하여 테스트 코드를 작성하는 가장 큰 이유는 UI 를 에뮬레이터 없이도 테스트 할 수 있기 때문이다.

UI 를 테스트하기 위해서 가장 기본은 뭐니뭐니해도 Activity 아니겠는가요? 연산 -> 결과 정상 반영을 확인 하기 위해서 Activity 를 올리는 것이 먼저 되어야 합니다.

그래서 오늘은 Activity 를 올리는 테스트를 먼저 해보도록 하겠습니다.

1. MyActivity.class

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    }
}

2. activity_my.xml



    




Robolectric 에서 Activity 생성하기

3. MyActivityTest.class

@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class MyActivityTest {

    private MyActivity myActivity;

    @Before
    public void setUp() throws Exception {

        myActivity = Robolectric.buildActivity(MyActivity.class).create().start().resume().get();

    }
}


Robolectric.buildActivity(Activity.class) 가 액티비티를 가상으로 만들어주는 메소드입니다.여기에 create, start, resume 은 해당 액티비티의 라이프 사이클을 동작시키는 메소드입니다.

위의 테스트 코드 중에서 중요한 것은 @Config 설정입니다.

manifest 를 설정해주셔야 하고, emulateSdk 값을 설정해줘야 테스트가 정상 수행됩니다.


그리고 다음과 같은 테스트 코드를 동작시켜보도록 하겠습니다.

    @Test
    public void testInit() throws Exception {

        assertNotNull(myActivity);
    }


테스트가 통과 하는 것을 확인 하실 수 있습니다.



설정

트랙백

댓글

Android-Gradle-Robolectric 에서 Sqlite DB 테스트 하기

개발 이야기 2014. 6. 30. 23:51

Robolectric 을 테스트 프레임웍으로 선택한 이유는 단 하나이다.

에뮬레이터 없이 View와 로직 테스트가 되기 때문이다

그래서 오늘은 Sqlite 에서 DB 테스트하는 것으로 공부해봤습니다.


아참! 참고로 이 로그들은 모두 제가 쓰면서 기록하는 것이 아니라
공부 하면서 남기는 로그이기 때문에 항상 맞다고 보장할 순 없습니다.
다만 테스트를 돌렸을 때 예상된 값이 정상 출력 되기 때문에 되는구나! 하는 것입니다.


1. 존재하는 Sqlite DB 파일 테스트하기.

 

우선 위와 같이 테스트용 DB 파일을 준비하였습니다.

구조는 아래와 같습니다.

컬럼명 

자료형 

id 

INTEGER , NN/PK/AI 

name 

TEXT 

ages

INTEGER 


그리고 아래와 같이 테스트 코드를 작성합니다.

@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class DatabaseConverterTest {
   @Test
    public void testConvertCursor() {

        SQLiteDatabase sqLiteDatabase = SQLiteDatabase.openDatabase("src/androidTest/resources/databases/test.db", null, 0);
        Cursor user = sqLiteDatabase.query("user", null, null, null, null, null, null);
        List mockTOs = DatabaseConverter.convertCursorToObject(user, MockTO.class);

        assertTrue(mockTOs.size() > 0);

    }

    @DatabaseBeans
    static class MockTO {

        @Id
        @Column
        private int id;

        @Column
        private String name;
        @Column(column = "ages")
        private int age;
    }
}


그리고는 그냥 테스트를 돌립니다...-_-;
참 쉽죠잉?


2. Sqlite DB 파일이 없는 경우

1번이야 DB 파일을 기기에서 추출하면 된다지만 사실 이게 매번 번거롭기 그지 없습니다.
언제나 그랬듯이 귀차니즘은 최고의 효율을 만든다 하였으니...
우리 테스트하고자하는 원본 소스 내부에 있는 SqliteOpenHelper 클래스를 이용하여 테스트용 데이터를 완성 후 자동으로 돌리면 어떤가 하고 생각해봤습니다.

그러기 위해서는 가장 필요한 SqliteOpenHelper 로도 자유롭게 DB 생성-쿼리 동작까지 정상적으로 되는지를 확인해야죠.

그래서 이번에는 아래와 같이 테스트해봤습니다.

@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class DatabaseConverterTest {
    @Test
    public void testConvertCursotNoDBFile() {
        MockOpenHelper openHelper = new MockOpenHelper(Robolectric.application);
        SQLiteDatabase writableDatabase = openHelper.getWritableDatabase();


        ContentValues values = new ContentValues();
        values.put("name", "aaa");
        values.put("ages", 155);
        long userSeq = writableDatabase.insert("user", null, values);

        SQLiteDatabase readableDatabase = openHelper.getReadableDatabase();

        Cursor user = readableDatabase.query("user", null, null, null, null, null, null);
        List mockTOs = DatabaseConverter.convertCursorToObject(user, MockTO.class);

        assertTrue(mockTOs.size() > 0);
        MockTO mockTO = mockTOs.get(0);
        assertEquals(155, mockTO.age);
        assertEquals("aaa", mockTO.name);
        assertEquals(userSeq, mockTO.id);

    }

    static class MockOpenHelper extends SQLiteOpenHelper {

        public MockOpenHelper(Context context) {
            super(context, "mockdb", null, 1);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL("create table if not exists user  ( id integer not null primary key autoincrement, name text, ages integer);");
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

        }
    }
}

그리고는 테스트를 돌려봤습니다...잘 되더군요 -ㅁ-


위 사항은 Robolectric 2.3 부터 제공되는 것이고
2.2 까지는 ShadowSqliteOpenHelper 를 이용하여 구현했어야 하는데
RobolectricTestRunner 가 자동으로 설정해주는 것 같습니다 :)


어쨋든 현재까지는 일반 연산 로직과 Sqlite DB 테스트가 가능하네요.
사실 이정도만 되도 테스트 커버리지를 60% 가량 채우는데는 문제 없습니다.


하지만..
인간의 욕심은 끝이 없지요..
조만간 View 공부해야겠네요


위 내용들은 현재 제 보잘것 없는 Github 에서 확인하실 수 있습니다. :)

Github : https://github.com/ZeroBrain/Android-Sqlite-Object-Convert

설정

트랙백

댓글

Android-Gradle-Robolectric 에서 Jacoco 를 활용하여 Test Coverage 측정하기

개발 이야기 2014. 6. 28. 17:44

UnitTest 는 내가 만들고 있는 로직을 부분단위로 테스트하기 위함이다. 이는 내가 만들고 있는 로직에 대해 잠재적인 오류를 나도 모르게 발생 시킬 수 있기 때문에 세분화 하여 테스트 하기 위함이다.

하지만 개발자들이 어느 순간 Test Code 작성에 익숙해지면 그 때부터는 일부 테스트를 빼먹거나 하기 마련이다. 또한 나도 모르게 테스트 했어야 하는 로직을 빼먹기도 한다.
(여기엔 이야기가 많지만 나도 Trivial Solution 에 대해서는 굳이 테스트 해야하나 싶기도 하다.)

어쨋든 얼마나 TestCode 가 잘 작성되었는지를 알아보기 위해 Test Coverage 측정툴을 이용한다.

누군가 말했지 않는가? SW 는 최신식 가내수공업이라고...사람이 직접 하는 일이기 때문에 뭘 하든 믿을 수 없다..언제든 오류가 발생할 수 있고 빼먹을 수 있는게 SW 지요.
(오류에 관대해지자는게 아니라 버그에 대해 지나친 죄책감을 가지지 말고 줄이기 위해 노력은 하자는 말입니다 :)


사설이 너무 길었네요 시작하도록 하지요.

1. Jacoco 설정

Jacoco 란? Emma 가 꽤 오래전에 개발이 중단되면서 EclEmma 팀에서 별도의 Coverage 측정 툴을 만들기 위해 시도한 프로젝트입니다. 그래서 이름도 Java Code Coverage 를 줄여서 Jacoco 라 지었지요. (물론 믿거나 말거나 ㅋㅋㅋ) 구글은 이를 잘 알고 EclEmma 팀을 지원해주고 있습니다.

apply plugin: "jacoco"

Android Studio 는 0.10 부터 Jacoco 를 지원하고 있지만 일반적인 jacoco 측정으로는 되지 않아 별도로 Task 를 생성할 것입니다.


2. Jacoco Task 설정하기

def coverageSourceDirs = [
        'src/main/java'
]

// This differs per what flavors buildTypes etc.
// But this example shows the 'testDebug'
// which is standard for Robolectric
task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true // coveralls plugin depends on xml format report
        html.enabled = true
    }
    // class R is used, but usage will not be covered, so ignore this class from report
    // This differs per plugin version (0.10 -> 0.11)
    // have very different fileTrees.
    // I have added rules to Ignore Dagger/Butterknife
    classDirectories = fileTree(
            dir: 'build/intermediates/classes/debug',
            excludes: ['com/nobrain/sqliteconvertor/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])
    sourceDirectories = files(coverageSourceDirs)
    executionData = files('build/jacoco/testDebug.exec')
    // Bit hacky but fixes https://code.google.com/p/android/issues/detail?id=69174.
    // We iterate through the compiled .class tree and rename $$ to $.
    doFirst {
        new File('build/intermediates/classes/').eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }

    afterEvaluate {
        // just clean up coveralls dashboard, following reports are not of interest
        testDebug.reports.junitXml.enabled = false
    }
}

위와 같이 설정하시면 jacocoTestReport 라는 Task 가 생성됨을 확인 하실 수 있습니다.

이제 돌려볼까요?


3. Jacoco 테스트 결과 보기

$> ./gradlew jacocoTestReport

위와 같이 실행하면 $rootDir/report/jacoco/jacocoTestReport 아래에 결과에 대한 xml 과 html 페이지를 내려줍니다.

저도 참 Test Report 를 참 좋아하는데 index.html 을 열어볼까요?

참으로 참담하네요 ㅎ_ㅎ 테스트용으로 만든 프로젝트였으니 그냥 그러려니 해주세요.

조만간 View 와 Sqlite 등 안드로이드에 종속적인 부분을 테스트 할 수 있는 법에 대해서 공부 한 후 돌아오겠습니다. :)


Reference : http://chrisjenx.com/gradle-robolectric-jacoco-dagger



덧 앞서 http://zerobrain.tistory.com/entry/AndroidGradleRobolectric-%EC%97%90%EC%84%9C-TDD-%ED%95%98%EA%B8%B0 에서 얘기 했듯이 Eclipse -> Android Studio 로 이동한 프로젝트는 Jacoco 를 진행할 수 없습니다.

Robolectric 동작의 내부 코드 에 src/test/java, src/androidTest/java 이하의 테스트코드에 한해서만 커버리지 측정이 되도록 코드가 박혀있습니다. 이점 유의하여 주십시요.

설정

트랙백

댓글

Android-Gradle AAR 파일 추가하기

개발 이야기 2014. 6. 24. 23:33

페이스북 라이브러리 같은 경우는 Maven Repository 에 추가되어 있지 않다.
그래서 페이스북 SDK 를 이용한 개발을 하려면 소스를 프로젝트로 만든 다음 Module 화 해서 개발해야 합니다.

유지보수를 하진 않는 모듈에 대해서는 굳이 모듈로 항상 새로 빌드하는 것은 관리, 빌드 타임 면에서 손해를 많이 봅니다.


그래서 오늘은 aar 로 빌드된 모듈을 추가하는 Gradle 설정을 해보도록 하겠습니다.


1. 라이브러리 모듈 빌드하기

$>./gradlew clean assemble

위와 같이 Shell 을 동작하면 $rootDir/build/outputs/aar/ 경로에 [project 명].aar 로 빌드되어 있습니다.


2. 라이브러리로 추가하기

(1) 에서 빌드한 결과물을 $rootDir/libs 폴더로 옮겨놓읍시다.
보통은 libs 에는 jni 빌드 결과물이나 jar 라이브러리 파일로 가득차 있는데요. aar 을 빌드하기 위해서는 해당 파일을 지정해주는 것말고도 한가지 과정이 더 필요합니다.

dependencies {
    repositories {
        mavenCentral()
        // libs 파일의 경로를 참조하도록 수정        
        flatDir {
            dirs 'libs'
        }
    }
    // facebook.aar 추가하기
    compile (name:'facebook', ext:'aar')
    compile fileTree(dir: 'libs', include: '*.jar')
}

위와 같이 작업을 해주시면 외부 모듈에 대해서
더이상 참조를 하지 않아도 라이브러리를 사용하실 수 있습니다 ^^


이걸 왜 했냐면...
Robolectric - JUnit 테스트를 하려면 라이브러리 프로젝트도 Robolectric 플러그인 설정을 해줘야 하더군요..
결국 그게 귀찮아서-_- 모듈을 라이브러리화 해버렸습니다 :)

역시 귀차니즘은 최고의 학습을 이끌어내네요 ㅋㅋ

설정

트랙백

댓글

Android-Gradle-Robolectric 에서 TDD 하기

개발 이야기 2014. 6. 19. 12:16

얼마전 Android-Gradle-Robolectric 을 적용하는 블로그를 하나 썼습니다.
당시에는 Gradle-Robolectric 2.2 로 반영한것이었고 SingleTest 가 불가능한 상황이었습니다. 그래서 사실상 TDD 가 어려운 상황이었지요.


얼마 전 Gradle-Robolectric 이 2.2 -> 2.3 으로 올라오면서 Future Task 목록에 있던 Single Test 가 사라진 것으로 확인하고 부랴부랴 싱글 테스트가 되는지를 확인해보았고 잘 되는 것을 확인하였습니다.


그럼 이제 그에 대한 내용으로 적어보도록 하지요.

1. Gradle-Robolectric 설정하기


buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:0.11.+'
        classpath 'org.robolectric:robolectric-gradle-plugin:0.11.+'
    }
}

apply plugin: 'robolectric'

dependencies {
    repositories {
        mavenCentral()
    }

    androidTestCompile 'junit:junit:4.10'
    androidTestCompile 'org.robolectric:robolectric:2.3+'
    androidTestCompile 'com.squareup:fest-android:1.0.+'
}

robolectric {
    // configure the set of classes for JUnit tests
    include '**/*Test.class'
    exclude '**/espresso/**/*.class'

    // configure max heap size of the test JVM
    maxHeapSize = "2048m"
}

위와 같이 build.gradle 을 설정합니다.


2. [project명].iml 파일 설정하기

Test Class Path 설정



  
     
     
     

     

     
     
     
     
     
  


3. Android Test 용 폴더 및 패키지 구조

 

만약 Eclipse -> Android Studio 로 전환한 프로젝트라면....

android {
   sourceSet{
       main {
          //...중략...
       }
        
       androidTest.setRoot("test")

   }
}

위와 같이 설정하신 다음 $rootDir/test/ 에 위와 같은 구조를 따라주시면 됩니다.


4. Gradle-Robolectric 용 TestRunner 만들기
Android 의 모든 클래스들은 기본적으로 SDK 내에서 동작하도록 되어 있습니다. 그래서 Context 와 같은 특수한 객체가 없으면 동작하지 못하는 경우가 많습니다.

Robolectric 용 TestRunner 를 만들어서 동작하도록 해야합니다.

public class RobolectricGradleTestRunner extends RobolectricTestRunner {
    public RobolectricGradleTestRunner(Class testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {

        String manifestProperty = System.getProperty("android.manifest");

        if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
            String resProperty = System.getProperty("android.resources");
            String assetsProperty = System.getProperty("android.assets");
            return new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty),
                    Fs.fileFromPath(assetsProperty));
        }
        return super.getAppManifest(config);

    }
}


위의 코드는 Robolectric 의 Gradle 용 코드에서 가져왔습니다 :)

위의 4가지 절차를 수행하시면 가장 기본적인 설정이 끝났습니다. 이제 테스트 코드를 작성해보겠습니다.


5. TestCode 작성하기

@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18)
@RunWith(RobolectricGradleTestRunner.class)
public class BeansInfoTest {

    @Test
    public void testCreateBeansInfo() throws Exception {
        BeanInfo beansInfo = new BeanInfo(null, "column", null, false);

        assertNotNull(beansInfo);

    }
}

위와 같이 테스트 코드를 작성 하시고 테스트를 돌리시면 아래와 같은 결과가 나옵니다.
(단! 테스트코드 Run 은 JUnit Tes 로 하셔야 합니다.)



언젠가부터 테스트 되지 않은 코드들은 마음 한 구석이 불안함이 컸습니다. 가끔은 저도 저를 못 믿을때가 많거든요 ^^

테스트 코드가 이러한 부분을 많이 커버해주었는데요. 이제 안드로이드도 테스트할 수 있는 코드를 만들 수 있어서 정말 기쁘네요.
(물론 이전부터 되었던 거지만 Android-Gradle 환경에서는 어려움이 많았지요)


위의 소스코드는 Github 에 정리되어 있습니다 :)

https://github.com/ZeroBrain/Android-Sqlite-Object-Convert


>>>>>> 2014.06.23 추가사항 <<<<<<

덧1
Eclipse -> Android Studio 로 이동한 프로젝트는 JUnit Test 가 잘 동작하지 않습니다.
아래와 같은 코드를 build.gradle 에 추가해주셔야 합니다.


tasks.whenTaskAdded {task ->
    if (task.name == 'testClasses') {
        task.dependsOn compileDebugTestJava
    }
}

robolectric 플러그인은 JUnit 테스트가 동작할 때 testClasses 라는 Task 를 동작시킵니다.
이 때 Test 클래스를 컴파일하도록 compileDebugTestJava Task 를 추가하도록 하였습니다.

덧2

2단계에서 설명한 내용 중 output-test 의 경로가 일정치 않습니다.
<output-test url="file://$MODULE_DIR$/build/test" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
위와 같이 주로 2가지의 경우가 있습니다. 가급적 실제 경로로 들어가셔서 클래스가 어느 위치에 있는지 확인해주세요.

덧3
Main Module, :LibModule 과 형식으로 프로젝트가 구성되어 있을 때에는
LibModule 에도 Robolectric 을 함께 정의해주십시요. 그렇지 않으면 Test 클래스가 컴파일되지 않더군요.

덧4
이제 조사한 자료를 실무에 적용하려니 이곳저곳에서 많이 이슈가 생기네요..
추가될 내용이 생기면 이 로그에 추가로 남기도록 하겠습니다.

덧5
Google - Android Studio 팀에서도 테스트 모듈에 대한 고민이 많나 봅니다.
가이드 문서 곳곳에 연결되지 않은 테스트 모듈 관련 로그들이 숨겨져있네요.

설정

트랙백

댓글

Android-Gradle 과 Flavor

개발 이야기 2014. 5. 28. 00:04

오늘은 Android - Gradle 에서 사용한 Flavor 에 대해서 이야기 해보고자 한다.

현재 내가 진행 중인 프로젝트는 한국/글로벌 또는 국내 각 스토어별로 유료/무료 상품으로 판매되고 있다. 그래서 기존에는 프로젝트를 스토어단위 별로 다 만들어서 빌드를 해주었다.
(이 때는 Ant 에 대해서 제대로 알지 못해서 이 고생을 했다 -_-)

그래서 이번에 필요한 리소스나 패키지명만 바꾸면 바로 빌드 될 수 있는 Flavor 라는 기능을 도입하려고 한다.

Android - Gradle 에서 Flavor 를 사용하기 위해서는 가장 중요한 것은 
Gradle 스크립트 내의 android 플러그인을 이해하는 것이다.

현재 내가 진행하는 프로젝트는 본디 Eclipse 환경에서 Android Studio 로 변환한 프로젝트이다. 따라서 기본적으로 Gradle 빌드 스크립트 내에서도
android.sourceSets 은 가장 이해하기 어려운 부분이었다.
(물론 지금도 Gradle 빌드 스크립트는 이해하지 못한 부분이 많습니다.)

Eclipse 에서 Android Studio 로 전환한 프로젝트들은 
기본적으로 아래와 같은 스크립트와 소스 트리들로 구성되어 있을 것이다.




 


    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
    }


Gradle 의 Android 빌드는 src/main/java 와 src/main/res 가 가장 기본적인 경로인데 반해
Eclipse 에서 변환한 프로젝트는 src 와 res 가 기본적인 경로이기 때문에
sourceSets 에서 일일이 경로를 지정해줘야 한다.


Android-Gradle 의 Flavor 는 설정한 Flavor 파일들 중에원본의 src 와 res 에 들어있는 중복되는 파일들 교체 빌드해주는 것이라고 생각하면 된다.
(간단한 빌드 정보나 특정 소스 값을 바꿔 줄 수 있으나 그건 소소한 기능이고 리소스와 소스를 대체해주는 게 제가 진행한 이유입니다.)


사설이 너무 길었네요.
본격적으로 Android-Gradle 에서 Flavor 설정을 시작하도록 하겠습니다.

1. productFlavors 설정

    productFlavors {
        kor {
        }

        malaysia {
        }
    }

전 클래스 파일에 대한 변경이라서 패키지명 설정과 같은 작업은 하지 않았습니다 :)


2. Flavors 디렉토리 지정하기+ android.sourceSets 설정

 

flavor 를 위한 디렉토리는 위와 같이 $rootDir/flavor/kor 로 설정하도록 한다.
(어떻게 지정해도 상관은 없으니 너무 저런 형태로 얽매이지 않아도 됩니다!)

그런 다음 아래와 같이 flavor sourceSet 을 지정합시다.

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        kor {

            java.srcDirs = ['flavor/kor/src']
            resources.srcDirs = ['flavor/kor/src']
            aidl.srcDirs = ['flavor/kor/src']
            renderscript.srcDirs = ['flavor/kor/src']
            res.srcDirs = ['flavor/kor/res']
            assets.srcDirs = ['flavor/kor/assets']
        }
    }


3. 빌드하기
Gradle Sync 를 설정하면 Gradle Task 목록에 아래와 같은 목록이 보일 것입니다.

 

나머지 Flavor 설정은 이제 매뉴얼대로 하시면 됩니다.


Eclipse 의 프로젝트를 포팅한 안드로이드 스투디오의 프로젝트에서 가장 큰 문제는 2가지였습니다.


1. Flavor 대상의 sourceSets 지정
2. 실제로 잘 동작하는지에 대한 의문점


7~8시간의 시행 착오 끝에 Gradle 을 이해하고 많은 것을 배울 수 있었습니다.
현재는 지금 하는 프로젝트는 특정 국가 대상으로의 빌드를
부담없이 할 수 있는 상태입니다.

flavor 를 하면서 든 느낌은
이래서 Gradle 이구나..ant 와 maven 을 대체하는구나 싶었습니다.


중요!!!!!!!!!!

Android Library Project 는 productFlavors 를 지원하지 않습니다. 

이는 구글의 가이드 문서에도 명시된 사항입니다. 
(정말 조그마하게...이걸 못봐서 4시간을 삽질을 ㅠ)

항상 메인(Root 프로젝트) 에서만 Flavor 기능이 제공됨을 잊지 마셔야 합니다.


'개발 이야기' 카테고리의 다른 글

Android-Gradle AAR 파일 추가하기  (0) 2014.06.24
Android-Gradle-Robolectric 에서 TDD 하기  (0) 2014.06.19
Android-Gradle 과 Flavor  (0) 2014.05.28
Gradle 과 Robolectric, 그리고 TDD  (0) 2014.05.25
리팩토링과 성능의 관계  (0) 2013.07.11
리팩토링의 시점  (0) 2013.07.08

설정

트랙백

댓글