Rork に説明文を渡して作ったアプリへ、後から新しい画面を1つ足したつもりが、関係ないはずの課金まわりの判定が静かに壊れていた——個人開発でいくつものアプリを App Store と Google Play に出してきた中で、私自身がいちばん肝を冷やすのは、この種の「離れた場所で起きる事故」です。
生成されたコードは、見た目が整っているぶん油断します。画面は綺麗に動いて見えるのに、有料機能のロック判定だけがいつの間にか緩んでいた、という壊れ方は、目視では気づけません。
ここで効くのが、ほんの数本の自動テストです。網羅率を上げる話ではありません。壊れると売上や信頼に直結する数か所だけを、機械に見張らせておくという発想です。実際に手を動かしながら、最小の足場を組んでいきます。
なぜ生成コードほどテストの足場が要るのか
自分で1行ずつ書いたコードなら、どこを触ると何が動くかが頭の中に地図として残ります。一方、Rork が一気に書き出したコードは、自分の記憶より先にファイルが存在しています。
つまり「ここを直すとあそこが動く」という因果が、頭の中に十分たまっていない状態で改修が始まります。これが、離れた場所の事故が起きやすい根本の理由です。
テストは、その足りない地図の代わりになります。コードを読み込まなくても、「この条件のとき、有料機能はロックされたままであるべき」という約束だけを固定できます。改修で約束が破れた瞬間に、赤いエラーが教えてくれます。
全部はテストしない — 壊れると痛い3か所だけ
最初に強くお伝えしたいのは、全画面をテストしようとしないことです。個人開発で網羅率を追いかけると、テストの保守だけで時間が溶けます。
私が実際に守っているのは、次の3か所だけです。
| 守る場所 | 守る理由 | テスト手段 |
| 課金ゲート(有料機能のロック判定) | 緩むと売上が即座に消える | 条件分岐の単体テスト |
| 価格・金額の表示 | 桁や通貨記号のズレは返金や苦情に直結 | 絞ったスナップショット |
| 主要導線のナビゲーション | 遷移が切れると主機能に到達できない | タップ後の表示確認 |
逆に、装飾的なUI・アニメーション・文言の細部はテストしません。ここは頻繁に変わりますし、壊れても被害が小さいからです。テストは「変わってほしくない約束」にだけ掛けるのが、保守を軽く保つコツです。
最小構成のセットアップ(Jest と React Native Testing Library)
Rork から書き出した Expo プロジェクトを前提にします。必要なのは2つのライブラリと、短い設定だけです。
# 開発依存として追加(実行時のアプリには含まれません)
npm install --save-dev jest jest-expo @testing-library/react-native @testing-library/jest-native
次に package.json へ、テスト実行コマンドと Jest の基本設定を足します。
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
},
"jest": {
"preset": "jest-expo",
"setupFilesAfterEnv": ["@testing-library/jest-native/extend-expect"],
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?|expo(nent)?|@expo(nent)?/.*|@react-navigation/.*))"
]
}
}
この transformIgnorePatterns が抜けていると、Expo 系パッケージの読み込みで構文エラーになります。私も最初にここで30分ほど詰まりました。生成コードに自前のテストを足すときの定番のハマりどころなので、最初から入れておくことを推奨します。
これで npm test が動けば、足場は完成です。
課金ゲートを1本のテストで守る
いちばん最初に書くべきは、有料機能のロック判定です。多くの生成コードでは、こうした判定がひとつの関数や Hook にまとまっています。仮に canUsePremiumFeature という関数があるとします。
// premiumAccess.ts(生成コードに既にある想定の判定ロジック)
type Entitlement = { active: boolean; expiresAt: number | null };
export function canUsePremiumFeature(
entitlement: Entitlement,
now: number
): boolean {
if (!entitlement.active) return false;
if (entitlement.expiresAt === null) return true; // 買い切り
return entitlement.expiresAt > now; // 期限内のみ許可
}
ここへ、約束を固定するテストを掛けます。コードの中身ではなく、入力と出力の関係だけを検証するのがポイントです。
// premiumAccess.test.ts
import { canUsePremiumFeature } from "./premiumAccess";
const NOW = 1_700_000_000_000;
describe("有料機能のロック判定", () => {
it("未購入ユーザーは常にロックされたまま", () => {
expect(canUsePremiumFeature({ active: false, expiresAt: null }, NOW)).toBe(false);
});
it("買い切り購入者は期限なしで許可", () => {
expect(canUsePremiumFeature({ active: true, expiresAt: null }, NOW)).toBe(true);
});
it("期限切れのサブスクは許可しない", () => {
expect(canUsePremiumFeature({ active: true, expiresAt: NOW - 1 }, NOW)).toBe(false);
});
});
たった3本ですが、これで「未購入者に有料機能が漏れる」事故は機械が止めてくれます。改修で判定の向きをうっかり逆にしても、npm test が赤くなります。
スナップショットは「金額表示」だけに絞る
スナップショットテストは、画面の出力を丸ごと記録して差分を見る手法です。便利な反面、画面全体に掛けると、装飾を1つ変えるたびにテストが壊れて更新作業に追われます。
そこで私は、スナップショットを金額の表示部分だけに絞っています。価格は桁や通貨記号がズレると返金や苦情に直結する一方、見た目はめったに変わらないため、相性が良いからです。
// PriceLabel.test.tsx
import { render } from "@testing-library/react-native";
import { PriceLabel } from "./PriceLabel";
it("日本円は3桁区切りと円記号で表示される", () => {
const { getByText } = render(<PriceLabel amount={2480} currency="JPY" />);
expect(getByText("¥2,480")).toBeTruthy();
});
it("0円は無料表示にフォールバックする", () => {
const { getByText } = render(<PriceLabel amount={0} currency="JPY" />);
expect(getByText("無料")).toBeTruthy();
});
画面全体ではなく、確認したい1文字列だけを getByText で取り出します。こうすると、無関係なUI変更でテストが壊れることがなくなり、本当に守りたい「表示の正しさ」だけが残ります。
失敗するテストをまず書く、という順番
生成コードへテストを足すとき、おすすめの順番があります。直したい挙動について、まず「失敗するテスト」を書くことです。
たとえば「期限切れのサブスクで有料機能が見えてしまう」不具合に気づいたら、修正の前に、その状況を再現するテストを1本書きます。テストが赤くなることを確認してから、コードを直します。テストが緑になれば、不具合が確かに直ったという証拠が残ります。
この順番には、もう一つ利点があります。同じ不具合が将来また混入したら、そのテストが二度目を防いでくれることです。生成コードは再生成や大きな書き換えで同じ穴が再発しやすいので、この「再発防止の杭」が効いてきます。
テストを置く場所と、CI で回すかどうかの判断
テストファイルは、対象コードと同じフォルダに .test.ts として並べて置くのが、後から探しやすくて実用的です。離れた __tests__ フォルダにまとめると、どのコードを守っているのかが見えにくくなります。
CI(自動実行の仕組み)まで組むかは、規模で判断します。
| 状況 | おすすめ |
| 1人で開発・リリース月数回 | push 前に手元で npm test を回すだけで十分 |
| 複数アプリを並行運用 | GitHub Actions などで自動実行し、赤いまま公開しない仕組みに |
私の場合は複数アプリを同時に動かしているため、テストは自動実行に寄せています。とはいえ、最初の一歩としては手元で回すだけでも、生成コードの事故はかなり減らせます。
既存の生成コードへ、無理なくテストを足す順番
いきなり全部を整えようとすると挫けます。本番運用しているアプリへ後付けするときは、次の順番が現実的です。
- まず課金ゲートの判定だけにテストを3本掛ける
- 次に価格表示のスナップショットを1本足す
- 主要導線のナビゲーションを1本だけ確認する
- 不具合が出た箇所に、再発防止のテストを都度足していく
この順番なら、最初の30分で「壊れるといちばん痛い場所」が守られます。AdMob の広告表示や課金率に影響する分岐は、特に優先して固定することを推奨します。
テストを足すときの落とし穴は、最初から網羅率を目標にしてしまうことです。網羅率は結果であって目標ではありません。本番で実際に壊れて困った場所から1つずつ対処していくほうが、個人開発では長続きします。
次の一歩
まずは課金ゲートの判定に、3本のテストを掛けてみてください。それだけで、いちばん痛い事故が一つ減ります。守る範囲を広げるのは、痛い目を見た場所が増えてからで十分です。
テストは網羅率の競争ではなく、自分が安心して改修するための地図です。生成コードと長く付き合うほど、この小さな地図がじわじわ効いてくると感じています。