C++で作成したAndroid用ライブラリをAndroidStudio + Google Testでテストする

# 概要
C++でAndroid向けにビルドしたライブラリを、AndroidStudioとGoogle Testを使ってテストする方法をまとめます。

# 方法
## Android向けC++ライブラリ作成
Android用ライブラリの作成方法は別の記事にまとめました。
https://qiita.com/Wagomu056/items/c21ba1c428e9574defbc

## テスト実行用プロジェクトの作成
プロジェクトを作成します。
「Empty Activity」で作成しました。

## CMake設定
### CMakeLists.txtを追加
**app/src/androidTest** に、**cppディレクトリ**を作成します。
そのディレクトリに**CMakeLists.txt**を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3310601/1ca4e4d5-4a08-4338-6f55-013579360272.png)

**CMakeLists.txt**

```CMake
cmake_minimum_required(VERSION 3.10.2)

project("native-lib-tests")

# --------------------
# Google Test
# --------------------
# Add Google Test directory
set(GOOGLETEST_DIR ${ANDROID_NDK}/sources/third_party/googletest)

# Add Google Test sources
add_library(gtest STATIC
        ${GOOGLETEST_DIR}/src/gtest_main.cc
        ${GOOGLETEST_DIR}/src/gtest-all.cc)

# Set include directories
target_include_directories(gtest PRIVATE
        ${GOOGLETEST_DIR})
target_include_directories(gtest PUBLIC
        ${GOOGLETEST_DIR}/include)

# --------------------
# Add test executable
# --------------------
add_executable(native-lib-tests test.cpp)
target_link_libraries(native-lib-tests gtest)

# Change output path to app/.cxx/Debug/output
set_target_properties(native-lib-tests PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../../output"
        )
```
gtestはNDKに含まれているものを使います。
また、「**RUNTIME_OUTPUT_DIRECTORY**」で実行ファイルを分かりやすいパスに出力するよう変更しています。

### build.gradleにCMakeLists.txtを読み込むように設定
**app/src/build.gradle**に以下を追加します。
abiFiltersにはテストを実行する端末のアーキテクチャを設定します。

```
android {
    testOptions {
        unitTests {
            externalNativeBuild {
                cmake {
                    path "src/androidTest/cpp/CMakeLists.txt"
                }
            }
            defaultConfig {
                ndk {
                    abiFilters "arm64-v8a"
                }
            }
        }
    }
}
```

## テストコードの追加
**app/src/androidTest/cpp** に、テストコードを追加します。
まずはテストが実行されるかを確認するためのコードです。

```cpp:test.cpp
#include <gtest/gtest.h>

TEST(ExampleTest, BasicAssertion) {
    EXPECT_EQ(1, 1);
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
```

## テスト実行
この状態でビルドすると、**app/.cxx/Debug/output**に実行ファイル**native-lib-tests**が出力されます。
これをadbで実機にコピーして、実行権限をつけて実行します。

```bash:run-test.sh
adb push "app/.cxx/Debug/output/native-lib-tests" /data/local/tmp
adb shell "chmod 755 /data/local/tmp/native-lib-tests && \
  /data/local/tmp/native-lib-tests"
```

:::note warn
実行前にエミュレータを起動しておくか、実機を繋いでおく必要があります。
:::

以下のようにファイルが転送・実行されて成功すると思います。

```shell-session
app/.cxx/Debug/output/native-lib-tests: 1 file pushed, 0 skipped. 111.4 MB/s (7193864 bytes in 0.062s)
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from ExampleTest
[ RUN      ] ExampleTest.BasicAssertion
[       OK ] ExampleTest.BasicAssertion (0 ms)
[----------] 1 test from ExampleTest (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 1 test.
```

## ライブラリの追加
テストを実行できるのが確認できたので、ライブラリを追加します。
いつも通り、ファイルを追加してCMakeLists.txtに追加します。

### ライブラリファイル追加
Android向けにビルドしたライブラリとヘッダファイルを**app/libs**以下に追加します。

```
app/libs
├── include
│   └── ndk_library.h
└── libndk_lib.a
```

### CMakeLists.txtに追加
追加したライブラリをCMakeLists.txtに追記します。
IMPORTEDとして追加します。

```CMake
# --------------------
# Libs
# --------------------
set(APP_ROOT ${CMAKE_SOURCE_DIR}/../../..)
add_library(ndk_lib STATIC IMPORTED)
set_target_properties(ndk_lib PROPERTIES
        IMPORTED_LOCATION ${APP_ROOT}/libs/libndk_lib.a
        INTERFACE_INCLUDE_DIRECTORIES ${APP_ROOT}/libs/include)
```

ライブラリをリンクするよう追記します。
```CMake
target_link_libraries(native-lib-tests gtest ndk_lib)
```

### Testを追加
テストファイルにライブラリのテストを追記します。

```cpp:test.cpp
#include <ndk_library.h>
...
TEST(ExampleTest, NDKLibTest) {
    EXPECT_EQ(ndk_lib_add(2, 2), 4);
}
```

### 実行
テストを追加したので、２つのテストが実行されます。

```shell-session
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from ExampleTest
[ RUN      ] ExampleTest.BasicAssertion
[       OK ] ExampleTest.BasicAssertion (0 ms)
[ RUN      ] ExampleTest.NDKLibTest
[       OK ] ExampleTest.NDKLibTest (0 ms)
[----------] 2 tests from ExampleTest (0 ms total)
```

# まとめ
以上でAndroid向けライブラリをGoogle Testで実行できるようになりました。
最終的なCMakeLists.txtとtest.cppを書いておきます。

<details><summary>CMakeLists.txtとtest.cpp</summary><div>

```CMake:CMakeLists.txt
cmake_minimum_required(VERSION 3.10.2)

project("native-lib-tests")

# --------------------
# Google Test
# --------------------
# Add Google Test directory
set(GOOGLETEST_DIR ${ANDROID_NDK}/sources/third_party/googletest)

# Add Google Test sources
add_library(gtest STATIC
        ${GOOGLETEST_DIR}/src/gtest_main.cc
        ${GOOGLETEST_DIR}/src/gtest-all.cc)

# Set include directories
target_include_directories(gtest PRIVATE
        ${GOOGLETEST_DIR})
target_include_directories(gtest PUBLIC
        ${GOOGLETEST_DIR}/include)

# --------------------
# Libs
# --------------------
set(APP_ROOT ${CMAKE_SOURCE_DIR}/../../..)
add_library(ndk_lib STATIC IMPORTED)
set_target_properties(ndk_lib PROPERTIES
        IMPORTED_LOCATION ${APP_ROOT}/libs/libndk_lib.a
        INTERFACE_INCLUDE_DIRECTORIES ${APP_ROOT}/libs/include)

# --------------------
# Add test executable
# --------------------
add_executable(native-lib-tests test.cpp)
target_link_libraries(native-lib-tests gtest ndk_lib)

# Change output path to app/.cxx/Debug/output
set_target_properties(native-lib-tests PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/../../output"
        )
```

```cpp:test.cpp
#include <gtest/gtest.h>
#include <ndk_library.h>

TEST(ExampleTest, BasicAssertion) {
EXPECT_EQ(1, 1);
}

TEST(ExampleTest, NDKLibTest) {
    EXPECT_EQ(ndk_lib_add(2, 2), 4);
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}
```

</div></details>

# これから
もしテストに必要なモジュールがある場合、build.gradleに追加すればいいのでしょうか？
今後、分かりましたら追記しようと思います。

# 参考元
https://zenn.dev/suudai/articles/9c3267bc1e0929