RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-03-14上級

Rork Maxアプリのテスト — Jest単体テスト・Detox E2Eテスト・品質管理

Rork Maxで生成したアプリに本格的なテストを導入する完全ガイド。Jest/React Native Testing LibraryによるUnit Test、DetoxによるE2Eテスト、スナップショットテスト、CI自動化まで。

Rork515testingJestDetoxE2EReact Native209

取り組みの背景:Rork Maxアプリのテストがなぜ重要か

Rork MaxはAIプロンプトやビジュアルビルダーからReact Nativeコードを生成し、モバイルアプリ開発を加速します。しかし、AI生成コードには独特のテスト課題があります:自動生成されたコンポーネントは微細なバグを含む可能性があり、依存関係は急速にバージョン更新され、統合ポイントは慎重な検証が必要です。生成されたアプリであっても、本番品質にはテストが不可欠です

モバイルアプリのテストピラミッドは3つのレイヤーで構成されます:

  1. ユニットテスト(70%) — 個別のコンポーネント、フック、ユーティリティを隔離してテスト
  2. 統合テスト(20%) — 複数のコンポーネントの連携、API呼び出しをテスト
  3. E2Eテスト(10%) — 実デバイス/シミュレータ上で完全なユーザーフローをテスト

このガイドでは、Jest(ユニット/統合テスト)とDetox(E2Eテスト)を使用し、GitHub Actionsで継続的な品質保証を実装します。

ℹ️
Rork Maxプロジェクトは通常、ExpoとJestが事前設定されています。このガイドは生成されたRork Maxアプリ構造(Expo管理ワークフロー)から始まることを想定しています。

Part 1:ユニットテスト用Jest設定

テスト依存関係のインストール

Rork Maxプロジェクトは既にJestを持っていますが、必要なライブラリがすべてあることを確認します:

npm install --save-dev @testing-library/react-native @testing-library/jest-native jest-expo babel-jest

またはYarnで:

yarn add --dev @testing-library/react-native @testing-library/jest-native jest-expo babel-jest

jest.config.js の設定

プロジェクトルートに jest.config.js を作成または更新します:

module.exports = {
  preset: 'jest-expo',
  testEnvironment: 'node',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  testPathIgnorePatterns: ['/node_modules/', '/android/', '/ios/'],
  collectCoverageFrom: [
    'app/**/*.{ts,tsx}',
    '!app/**/*.d.ts',
    '!app/**/index.ts',
  ],
  coverageThreshold: {
    global: {
      branches: 60,
      functions: 60,
      lines: 60,
      statements: 60,
    },
  },
};

jest.setup.js の作成

import '@testing-library/jest-native/extend-expect';

最初のテストの実行

テストファイル app/__tests__/App.test.tsx を作成します:

import React from 'react';
import { render, screen } from '@testing-library/react-native';
import App from '../App';
 
describe('Appコンポーネント', () => {
  it('ウェルカムメッセージをレンダリングする', () => {
    render(<App />);
    expect(screen.getByText(/Welcome/i)).toBeTruthy();
  });
});

テストを実行します:

npm test
⚠️
「Cannot find module 'react-native'」エラーが表示される場合は、jest.config.jsのプリセットが「jest-expo」であることを確認してください。「jest-expo」プリセットは自動的にReact Nativeをモックします。

Part 2:コンポーネントのユニットテスト

ボタンコンポーネントのテスト

ほとんどのRork Maxアプリはボタンコンポーネントを含みます。カスタムボタンをテストしましょう:

// app/components/Button.tsx
import React from 'react';
import { TouchableOpacity, Text } from 'react-native';
 
interface ButtonProps {
  title: string;
  onPress: () => void;
  disabled?: boolean;
}
 
export const Button: React.FC<ButtonProps> = ({ title, onPress, disabled }) => (
  <TouchableOpacity onPress={onPress} disabled={disabled}>
    <Text>{title}</Text>
  </TouchableOpacity>
);

テストを作成します:

// app/__tests__/Button.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Button } from '../components/Button';
 
describe('ボタンコンポーネント', () => {
  it('正しいタイトルでレンダリングされる', () => {
    render(<Button title="押してください" onPress={jest.fn()} />);
    expect(screen.getByText('押してください')).toBeTruthy();
  });
 
  it('押下時にonPressが呼ばれる', () => {
    const mockPress = jest.fn();
    render(<Button title="押してください" onPress={mockPress} />);
 
    const button = screen.getByRole('button', { name: /押してください/i });
    fireEvent.press(button);
 
    expect(mockPress).toHaveBeenCalledTimes(1);
  });
 
  it('無効化時はonPressが呼ばれない', () => {
    const mockPress = jest.fn();
    render(
      <Button title="押してください" onPress={mockPress} disabled={true} />
    );
 
    const button = screen.getByRole('button');
    fireEvent.press(button);
 
    expect(mockPress).not.toHaveBeenCalled();
  });
});

フォームのテスト

フォームはアプリで重要です。フォーム送信をテストします:

// app/__tests__/LoginForm.test.tsx
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from '../components/LoginForm';
 
describe('ログインフォームコンポーネント', () => {
  it('メールとパスワードでフォームが送信される', async () => {
    const mockSubmit = jest.fn();
    render(<LoginForm onSubmit={mockSubmit} />);
 
    const emailInput = screen.getByPlaceholderText('メール');
    const passwordInput = screen.getByPlaceholderText('パスワード');
    const submitButton = screen.getByText('サインイン');
 
    fireEvent.changeText(emailInput, 'user@example.com');
    fireEvent.changeText(passwordInput, 'password123');
    fireEvent.press(submitButton);
 
    await waitFor(() => {
      expect(mockSubmit).toHaveBeenCalledWith({
        email: 'user@example.com',
        password: 'password123',
      });
    });
  });
 
  it('メールが無効な場合はエラーを表示', async () => {
    render(<LoginForm onSubmit={jest.fn()} />);
 
    const emailInput = screen.getByPlaceholderText('メール');
    fireEvent.changeText(emailInput, 'invalid-email');
 
    await waitFor(() => {
      expect(screen.getByText(/無効なメール/i)).toBeTruthy();
    });
  });
});

Part 3:renderHookでフックをテストする

カスタムフックはReact Nativeアプリで一般的です。renderHookでテストします:

// app/hooks/useAuth.ts
import { useState, useCallback } from 'react';
 
export const useAuth = () => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
 
  const login = useCallback(async (email: string, password: string) => {
    setLoading(true);
    try {
      // API呼び出し
      const response = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify({ email, password }),
      });
      const data = await response.json();
      setUser(data.user);
    } finally {
      setLoading(false);
    }
  }, []);
 
  const logout = useCallback(() => {
    setUser(null);
  }, []);
 
  return { user, loading, login, logout };
};

フックをテストします:

// app/__tests__/useAuth.test.ts
import { renderHook, act, waitFor } from '@testing-library/react-native';
import { useAuth } from '../hooks/useAuth';
 
describe('useAuthフック', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
 
  it('nullユーザーとfalseロード状態で初期化される', () => {
    const { result } = renderHook(() => useAuth());
 
    expect(result.current.user).toBeNull();
    expect(result.current.loading).toBe(false);
  });
 
  it('ログイン中はloadingがtrueになる', async () => {
    const { result } = renderHook(() => useAuth());
 
    act(() => {
      result.current.login('user@example.com', 'password123');
    });
 
    expect(result.current.loading).toBe(true);
  });
 
  it('ログイン成功後、ユーザーが更新される', async () => {
    const mockUser = { id: '123', email: 'user@example.com' };
 
    global.fetch = jest.fn(() =>
      Promise.resolve({
        json: () => Promise.resolve({ user: mockUser }),
      })
    ) as jest.Mock;
 
    const { result } = renderHook(() => useAuth());
 
    await act(async () => {
      await result.current.login('user@example.com', 'password123');
    });
 
    await waitFor(() => {
      expect(result.current.user).toEqual(mockUser);
      expect(result.current.loading).toBe(false);
    });
  });
 
  it('ログアウト時はユーザーがクリアされる', () => {
    const { result } = renderHook(() => useAuth());
 
    act(() => {
      result.current.logout();
    });
 
    expect(result.current.user).toBeNull();
  });
});

Part 4:APIとサービスのモッキング

外部サービス(Supabaseなど)のモッキングは必須です。jest.mock()を使用します:

// app/services/supabaseClient.ts
import { createClient } from '@supabase/supabase-js';
 
export const supabase = createClient(
  process.env.EXPO_PUBLIC_SUPABASE_URL!,
  process.env.EXPO_PUBLIC_SUPABASE_KEY!
);

テストでモックします:

// app/__tests__/supabaseService.test.ts
import { supabase } from '../services/supabaseClient';
 
jest.mock('../services/supabaseClient', () => ({
  supabase: {
    from: jest.fn(),
  },
}));
 
describe('Supabaseサービス', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
 
  it('投稿を正常に取得する', async () => {
    const mockPosts = [
      { id: 1, title: '投稿1' },
      { id: 2, title: '投稿2' },
    ];
 
    (supabase.from as jest.Mock).mockReturnValue({
      select: jest.fn().mockResolvedValue({
        data: mockPosts,
        error: null,
      }),
    });
 
    const { data, error } = await supabase
      .from('posts')
      .select('*');
 
    expect(data).toEqual(mockPosts);
    expect(error).toBeNull();
  });
 
  it('エラーを正常に処理する', async () => {
    const mockError = { message: 'ネットワークエラー' };
 
    (supabase.from as jest.Mock).mockReturnValue({
      select: jest.fn().mockResolvedValue({
        data: null,
        error: mockError,
      }),
    });
 
    const { data, error } = await supabase
      .from('posts')
      .select('*');
 
    expect(data).toBeNull();
    expect(error).toEqual(mockError);
  });
});
ℹ️
テスト中のネットワーク呼び出しを避けるため、常に外部サービスをモックしてください。`jest.clearAllMocks()`を`beforeEach`で使用し、テスト間でモック状態をリセットしましょう。

Part 5:スナップショットテスト

スナップショットテストはコンポーネント出力をキャプチャし、回帰を検出します:

// app/__tests__/ProfileCard.snapshot.test.tsx
import React from 'react';
import { render } from '@testing-library/react-native';
import { ProfileCard } from '../components/ProfileCard';
 
describe('プロフィールカードスナップショット', () => {
  it('正しくレンダリングされる', () => {
    const tree = render(
      <ProfileCard
        name="太郎"
        bio="開発者"
        avatar="https://example.com/avatar.jpg"
      />
    ).toJSON();
 
    expect(tree).toMatchSnapshot();
  });
});

npm test -- -uを実行すると、Jestはスナップショットファイル(ProfileCard.snapshot.test.tsx.snap)を保存します。その後の実行では、Jestは出力をスナップショットと比較します。

スナップショットを使うべき場合:

  • ✅ 構造が安定したUIコンポーネント
  • ✅ デザイン変更の回帰検出

スナップショットを避けるべき場合:

  • ❌ 動的コンテンツ(タイムスタンプ、ID)
  • ❌ 頻繁に変わるUI

Part 6:Detox E2Eセットアップ

Detoxは実デバイスまたはシミュレータ上でアプリをエンドツーエンドテストします。インストールします:

npm install --save-dev detox-cli detox detox-build-configuration

.detoxrc.js の設定

プロジェクトルートに.detoxrc.jsを作成します:

module.exports = {
  testRunner: 'jest',
  apps: {
    ios: {
      type: 'ios.app',
      binaryPath: 'artifacts/build/Build/Products/Release-iphonesimulator/YourApp.app',
      build: 'xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Release -sdk iphonesimulator -derivedDataPath artifacts/build',
    },
  },
  configurations: {
    'ios.sim.debug': {
      device: {
        type: 'iPhone 14',
      },
      app: 'ios',
    },
  },
  testRunner: 'jest',
};

アプリのビルド

detox build-framework-cache
detox build-app --configuration ios.sim.debug

最初のE2Eテストの実行

e2e/firstTest.e2e.jsを作成します:

describe('最初のE2Eテスト', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
 
  beforeEach(async () => {
    await device.reloadReactNative();
  });
 
  it('ウェルカムスクリーンを表示すべき', async () => {
    await expect(element(by.text('Welcome'))).toBeVisible();
  });
});

テストを実行します:

detox test e2e/firstTest.e2e.js --configuration ios.sim.debug
⚠️
DetoxはmacOS上でXcodeとiOSシミュレータが必要です。Androidテストの場合は`android.emu.debug`設定を使用します。Detoxセットアップは複雑になる可能性があります。プラットフォーム固有の問題については、公式Detoxドキュメントを参照してください。

Part 7:Detox E2Eテストの例

ログインフロー テスト

// e2e/loginFlow.e2e.js
describe('ログインフロー', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
 
  beforeEach(async () => {
    await device.reloadReactNative();
  });
 
  it('有効な認証情報でログインに成功すべき', async () => {
    // ログイン画面に移動
    await element(by.id('loginButton')).multiTap();
 
    // メールを入力
    await element(by.id('emailInput')).typeText('user@example.com');
 
    // パスワードを入力
    await element(by.id('passwordInput')).typeText('password123');
 
    // サインインボタンをタップ
    await element(by.id('signInButton')).multiTap();
 
    // ダッシュボードをチェックして、ログイン成功を確認
    await waitFor(element(by.text('ダッシュボード')))
      .toBeVisible()
      .withTimeout(5000);
  });
 
  it('無効なメールでエラーを表示すべき', async () => {
    await element(by.id('loginButton')).multiTap();
    await element(by.id('emailInput')).typeText('invalid-email');
    await element(by.id('signInButton')).multiTap();
 
    await expect(element(by.text('無効なメール'))).toBeVisible();
  });
});

ナビゲーション フロー テスト

// e2e/navigation.e2e.js
describe('ナビゲーションフロー', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
 
  it('タブ間をナビゲートすべき', async () => {
    // ホームタブから開始
    await expect(element(by.text('ホーム'))).toBeVisible();
 
    // プロフィールタブをタップ
    await element(by.id('profileTab')).multiTap();
    await expect(element(by.text('プロフィール'))).toBeVisible();
 
    // 設定タブをタップ
    await element(by.id('settingsTab')).multiTap();
    await expect(element(by.text('設定'))).toBeVisible();
 
    // ホームに戻る
    await element(by.id('homeTab')).multiTap();
    await expect(element(by.text('ホーム'))).toBeVisible();
  });
});

フォーム送信テスト

// e2e/formSubmission.e2e.js
describe('フォーム送信', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
 
  it('すべてのフィールドが入力されたフォームを送信すべき', async () => {
    // フォームに移動
    await element(by.id('openFormButton')).multiTap();
 
    // フォームフィールドを入力
    await element(by.id('nameInput')).typeText('太郎');
    await element(by.id('emailInput')).typeText('john@example.com');
    await element(by.id('messageInput')).typeText('こんにちは');
 
    // 送信
    await element(by.id('submitButton')).multiTap();
 
    // 成功メッセージを確認
    await waitFor(element(by.text('フォームが正常に送信されました')))
      .toBeVisible()
      .withTimeout(5000);
  });
 
  it('必須フィールドが空の場合、検証エラーを表示すべき', async () => {
    await element(by.id('openFormButton')).multiTap();
    await element(by.id('emailInput')).typeText('john@example.com');
    await element(by.id('submitButton')).multiTap();
 
    await expect(element(by.text('名前は必須です'))).toBeVisible();
  });
});

Part 8:カバレッジレポート

カバレッジレポートの生成

カバレッジ付きでJestを実行します:

npm test -- --coverage

これにより、coverage/ディレクトリにカバレッジレポートが生成されます。カバレッジメトリクスには以下が含まれます:

  • Statements — 実行されたステートメントの割合
  • Branches — テストされた条件分岐の割合
  • Functions — 呼ばれた関数の割合
  • Lines — 実行された行の割合

カバレッジしきい値の設定

jest.config.jsで最小しきい値を設定します:

coverageThreshold: {
  global: {
    branches: 70,
    functions: 75,
    lines: 80,
    statements: 80,
  },
  './app/services/': {
    branches: 90,
    functions: 90,
    lines: 90,
    statements: 90,
  },
};

カバレッジがしきい値以下の場合、Jestはコード1で終了し、ビルドが失敗します。

ブラウザでカバレッジレポートを表示

カバレッジの実行後、ブラウザでcoverage/lcov-report/index.htmlを開き、詳細でインタラクティブなカバレッジレポートを確認します。


Part 9:GitHub Actions CI統合

.github/workflows/test.yml を作成

name: Jest テスト
 
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]
 
jobs:
  test:
    runs-on: ubuntu-latest
 
    strategy:
      matrix:
        node-version: [18.x, 20.x]
 
    steps:
      - uses: actions/checkout@v4
 
      - name: Node.js ${{ matrix.node-version }} をセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
 
      - name: 依存関係をインストール
        run: npm ci
 
      - name: テストを実行
        run: npm test -- --coverage
 
      - name: Codecovにカバレッジをアップロード
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella
          fail_ci_if_error: false

すべてのPRで実行

ワークフローは自動的にプルリクエストで実行されます。テストが失敗するとPRは赤いXを表示します。テストが成功すると、緑のチェックマークが表示されます。

ℹ️
READMEにバッジを追加して、テストステータスを表示しましょう:
![テスト](https://github.com/YOUR_ORG/YOUR_REPO/workflows/Jest%20Tests/badge.svg)
```</div></div>
 
---
 
## Part 10:CI内でのE2Eテスト
 
CIでDetoxを実行するにはAndroid/iOSビルドツールが必要です。iOSの最小例:
 
```yaml
name: Detox E2E テスト
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  test:
    runs-on: macos-latest
 
    steps:
      - uses: actions/checkout@v4
 
      - name: Node.js をセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'
 
      - name: 依存関係をインストール
        run: npm ci
 
      - name: Detoxをビルド
        run: |
          detox build-framework-cache
          detox build-app --configuration ios.sim.debug
 
      - name: E2Eテストを実行
        run: detox test e2e --configuration ios.sim.debug --cleanup
⚠️
macOS実行環境はLinuxより高額です。CI費用を削減するため、E2Eテストはメインブランチのみで実行し、すべてのPRでは実行しないことを検討してください。

個人開発者の視点から(実体験メモ)

まとめ:Rork Maxアプリの成功するテスト戦略

Rork Maxアプリが本番対応であることを確認するために:

  1. ユニットテスト(Jest) — コンポーネント、フック、ユーティリティをカバーします。80%以上の行カバレッジを目指しましょう。
  2. コンポーネントテスト — ユーザーインタラクション(ボタンクリック、フォーム送信)をテストします。
  3. モッキング — 外部APIとサービスをモックして、ロジックを隔離します。
  4. E2Eテスト(Detox) — 重要なユーザーフロー(ログイン、支払い、コア機能)をテストします。
  5. CI/CD — GitHub Actionsですべてのプルリクエストでテストを自動化します。
  6. カバレッジレポート — カバレッジを追跡し、しきい値を実装します。

AI生成コードは完璧ではありませんが、包括的なテストにより、問題を早期に検出できます。Jestユニットテストから始め、重要なパスに対するE2Eテストを追加し、アプリの成長に合わせて反復します。

次のステップ:

  • Rork MaxプロジェクトにJestをセットアップする
  • 最も重要なコンポーネントのテストを書く
  • GitHub Actionsで継続的なテストを統合する
  • アプリが成長するにつれて、段階的にカバレッジを増やす

テストを楽しんでください!

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-07-15
「、」が行頭に落ちた — Rork が組んだ名言カードと日本語の禁則処理
React Native の Text は日本語の行頭禁則を保証しません。実測した違反率、WORD JOINER による事前結合の実装、onTextLayout での自動監査、Rork Max(SwiftUI)との挙動差までまとめました。
開発ツール2026-07-15
Rorkの癒しアプリにスリープタイマーを付けたら、フェードアウトが実際の音とズレた話
Rorkが生成するスリープタイマーは、壁時計のsetIntervalで音量を下げます。数値上は30分でも、実際の音はもっと早く消えたり不自然に途切れたりします。フェードを再生位置で駆動し直す実装と、対数カーブ・停止処理・画面ロック時の限界までを実運用の視点でまとめました。
開発ツール2026-07-14
AI再生成に壊されない設計 — Rork の生成コードと手書きロジックを分ける境界の引き方
Rork へ追加指示を出すたびに手書きのロジックが消える。プロンプトで守るのは対症療法です。生成コードと自分の所有コードを層で分け、再生成が届かない境界を設計で引く方法を、Zustand と service 層の実装例つきでお伝えします。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →