お茶漬けびより

"あなたに教わったことを、噛んでいるのですよ" 五等分の花嫁 7巻 「最後の試験が五月の場合」より

数学文章作法 基礎編を読んだ

『数学文章作法 基礎編』本を読んで良かったので、勢いで書いてます。

この本は何か

分かりやすい文章の書き方を解説した本です。対象読者は解説するような文章を書く人で、特に論文のような文章を書く人に向けた本です。著者は結城 浩(ゆうき ひろし)さん。

インタビュー記事があったのでなんとなく貼っておきます。

next.rikunabi.com

どんな人におすすめか

文章を書く人におすすめです。内容は数式を書くような文章で解説されていますが、読者に何かを教える本を書こうとしている人全員におすすめです。この本自体も解説されている方法で書かれているので分かりやすいです。解説と実例が同時に体験できるので内容に納得せざるを得ません。

また、自分はこういった文章を書く中で「なんか読みづらいなぁ……」と感じていたので読み始めたのですが、読んでいると文章を書く側だけでなく技術書を読む人にとってもいいかもと思いました。というのは、文章を読んでいると理解できないときがありますが、この本の分かりづらい文章に当てはまれば、なぜ理解できないかが客観的に判断できるからです。

あとは仕事をしている人にもおすすめです。どう話せば相手が理解しやすいかということに応用できそうだからです。自分は、考えを相手にまとめて話すことが上手くないので、この本に書いてあることを実践すればマシになりそうだなと思いました。

つまり人と関わる人みんなにおすすめです。

内容について

文章を書くときに気をつけることや、どうすれば分かりやすい文章になるか、数式はどう書くか、例の作り方、問題の作り方、目次や索引など文章を書くために必要なことが全て書いてあります。

4章の数式と命題は読んでいるときあんまり関係ないかなと思っていましたが、今振り返るとコードを載せるときに参考になりそうです。

どの章も良かったですが、とくに5章のや7章の目次と索引は勉強になりました。読者が理解しやすい例や索引の作り方はどうやって作っているのか分からなかったのでためになります(索引を作るような文章はめったに書きませんが)。

おわりに

大学生になったときに、『理科系の作文技術』という本が入学前に読む課題であったのですが、あの本も良かった記憶があります。『数学文章作法 基礎編』と比べると堅い印象がありますが、『理科系の作文技術』を読んでなかったら自分の文章は今よりもひどいものだったでしょう。内容はほとんど忘れてしまったので、また機会があれば読みたいです(読まない)。ちなみに『数学文章作法 基礎編』の参考文献にも含まれていました。

今後は、『数学文章作法 基礎編』の内容を活かして記事を書いたり、同人誌書いたりしたいと思います。

Angular の HttpClient を使うサービスクラスのテスト

最近、TypeScript の楽しさに気づきました。

HttpTestingModuleHttpTestingController を使ったテストの書き方です。

テスト対象を用意するために https://angular.jp/tutorial/toh-pt6 で公開されているプロジェクトを使います。

このプロジェクトの HeroService をテストします。

概要

HttpClientTestingModuleHttpClientTestingModule を使えば、テスト対象のクラスの動きだけでなく、リクエストした内容が期待通りの結果になっているか検証できる。

公式が詳しいのでもっと知りたい人は、公式ドキュメントの HTTPリクエストのテスト を参照しよう。

準備

これ を落としてきて、任意の場所で展開します。

動作することを確認し、テストも動かして全て通ることを確認します。

hero.service.spec.ts を作成し、以下のように書きます。

import { MessageService } from "./message.service";
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';

describe('HeroService', () => {
  let httpClientSpy = jasmine.createSpyObj<HttpClient>(["get"]);
  let messageServiceSpy = jasmine.createSpyObj<MessageService>(["add"]);
  let service: HeroService;

  beforeEach(() => {
    service = new HeroService(httpClientSpy, messageServiceSpy)
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

テストが通っているか確認して、問題なければ次に進みましょう。

ちなみにここでやっているのは、サービスに必要な2つのインスタンスhttpClientSpy, messageServiceSpy)のスパイオブジェクトを作成し、そのスパイオブジェクトを引数にサービスを生成しています。

TetBed を使った方法に変える

引数で渡すのは面倒なので、TestBed を使って生成するように変更します。

import { MessageService } from "./message.service";
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';

describe('HeroService', () => {
  let httpClientSpy = jasmine.createSpyObj<HttpClient>(["get"]);
  let messageServiceSpy = jasmine.createSpyObj<MessageService>(["add"]);
  let service: HeroService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: HttpClient, useValue: httpClientSpy },
        { provide: MessageService, useValue: messageServiceSpy }
      ],
    });
    service = TestBed.inject(HeroService);

  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

変更箇所は、以下です。

import { HttpClient } from '@angular/common/http';
+ import { TestBed } from '@angular/core/testing';



  beforeEach(() => {
+   TestBed.configureTestingModule({
+     providers: [
+       { provide: HttpClient, useValue: httpClientSpy },
+       { provide: MessageService, useValue: messageServiceSpy }
+     ],
+   });
+   service = TestBed.inject(HeroService);
-   service = new HeroService(httpClientSpy, messageServiceSpy)

これも問題なく通るはずです。本題ではないので、説明は省きますが、何をやっているか分からない場合、以下を参照するのをオススメします。

サービスのテスト

HttpClientTestingModule と HttpClientTestingModule を使う

ここが今回の本題です。

以下のように変えます。

import { MessageService } from "./message.service";
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('HeroService', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;
  let messageServiceSpy = jasmine.createSpyObj<MessageService>(["add"]);
  let service: HeroService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule ],
      providers: [
        { provide: MessageService, useValue: messageServiceSpy }
      ],
    });
    service = TestBed.inject(HeroService);

    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

変更箇所は、以下です。

import { TestBed } from '@angular/core/testing';
+ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('HeroService', () => {
-  let httpClientSpy = jasmine.createSpyObj<HttpClient>(["get"]);
+  let httpClient: HttpClient;
+  let httpTestingController: HttpTestingController;
...
      providers: [
-       { provide: HttpClient, useValue: httpClientSpy },
        { provide: MessageService, useValue: messageServiceSpy }
...
    service = TestBed.inject(HeroService);

+   httpClient = TestBed.inject(HttpClient);
+   httpTestingController = TestBed.inject(HttpTestingController);
  });

とりあえずテストを通るか確認します。HttpClientTestingModule, HttpTestingController を使うとリクエストした内容の検証までできるようになります。次はそれを試すためにテストを追加します。

リクエストの内容を検証するテストを作る

以下のテストを追加して、通るか確認します。

  it('サーバからヒーローを取得できる', async () => {
    const expected: Hero[] = [
      { id: 11, name: 'Dr Nice' },
      { id: 12, name: 'Narco' },
    ];

    service.getHeroes().subscribe(actual => {
      expect(actual).toEqual(expected);
    });

    const req = httpTestingController.expectOne('api/heroes');

    expect(req.request.method).toEqual('GET');

    req.flush(expected);

    httpTestingController.verify();
  });

解説

最初に期待値を作っています。この期待値は、同時にサーバから返ってくる値にもなります(req.flush(expected); で行っています)。

そのあと、service.getHeroes() を実行して、サブスクライブしています。データを受け取るとそのデータが期待通りになっているか検証しています(expect(actual).toEqual(expected); の箇所です)。

この検証の処理は、まだ行われません。サーバからの応答は、HttpTestingController で制御できます(厳密には、TestResult?)。

先の処理で、リクエストは投げられたので、その内容が httpTestingController で取得できます。const req = httpTestingController.expectOne('api/heroes'); がそれです。

expectOne には URL を指定していますが、URL だけでマッチングさせるのが不都合な場合、独自に定義することができます。詳しくは、公式のドキュメントに委ねます。

HttpTestingController

Custom request expectations

expect(req.request.method).toEqual('GET'); は見たら分かる通り、GET や POST などの期待するメソッドになっているか検証しています。

req.flush(expected); でサーバからの応答を擬似的に行っています。ここで指定した引数が actual に入ってきます。この関数を抜けたあとにデータが流れて、expect(actual).toEqual(expected); が行われるはずです。

最後に httpTestingController.verify(); で未処理のリクエストがないことを確認しています。 afterEach で行うのが一般的みたいです。残っている場合は、テストが失敗します。

すごく雑な解説になりましたが、以上です。他にもエラーのテスト同じAPIの複数のリクエストなどの検証ができるみたいなので、知りたい方は、公式ドキュメントを参照してみるといいと思います。

最終的なコード

import { MessageService } from "./message.service";
import { HttpClient } from '@angular/common/http';
import { HeroService } from './hero.service';
import { Hero } from './hero';
import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpClientTestingModule } from '@angular/common/http/testing';

describe('HeroService', () => {
  let httpClient: HttpClient;
  let httpTestingController: HttpTestingController;

  let messageServiceSpy = jasmine.createSpyObj<MessageService>(["add"]);
  let service: HeroService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [ HttpClientTestingModule ],
      providers: [
        { provide: MessageService, useValue: messageServiceSpy }
      ],
    });
    service = TestBed.inject(HeroService);

    httpClient = TestBed.inject(HttpClient);
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  it('サーバからヒーローを取得できる', async () => {
    const expected: Hero[] = [
      { id: 11, name: 'Dr Nice' },
      { id: 12, name: 'Narco' },
    ];

    service.getHeroes().subscribe(actual => {
      expect(actual).toEqual(expected);
    });

    const req = httpTestingController.expectOne('api/heroes');

    expect(req.request.method).toEqual('GET');

    req.flush(expected);

    httpTestingController.verify();
  });
});

「TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング 」を見た

見ながら Scrapbox に書いたことをここに貼り付けるだけのお仕事。

さすがに何度か見直さないと理解が追いつかなかった。

動画


TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング

まとめた内容

f:id:pickles-ochazuke:20200801181734p:plain
TDD のサイクル

  • 最初に簡単な設計を行う

    • どこかに何かの形式で今考えていることを書き出す
      • 実装脳になると視野が狭くなる
      • レッド、グリーン、リファクタリングを繰り返していると何をしているか忘れるため
    • アジャイル開発では設計をしない」は間違い
      • 常に設計し続ける
      • 実装脳になってしまうので、現在の理解を書き出す
    • TDD においては、TODO リストを作る
      • 箇条書きで書き出す
        • 例えば、10 項目書き出したら全てをテストを書くわけではない
          • 動作するコードを書いてからきれいなコードを書くので、徹底的に問題を小さく分解して一つずつ解決していくやり方
      • 1 個最初に倒すやつを選ぶ
        • 選び方
          • 重要度から選ぶやりかた
          • テスト書きやすいものから選ぶやり方 TDDでは、最初はテスト容易性の高いものを選ぶ
          • することが多いので、最初に重要度のものを選ぶと作業が重くなる
          • テスト容易性の高いものと重要度が高いものは両立できる
        • 重要なものと簡単なもの
          • 重要なものと簡単なものは一致させることができる
          • テスト容易性と重要度の4象限
            • テスト容易性、重要度どちらも高いもの
            • テスト容易性、重要度のどちらも低いものに寄せていくことができる
  • TDDはテストファースト

  • リファクタリングのやめどき

    • 時間で区切る(何度でもチャンスはやってくる)
      • 5~10分
    • 数を数える
      • 重複している箇所を共通の関数に抜き出す
    • 一周するとたくさんのフィードバックがやってくる
      • 目標を考え直す(TODOリストを改定する)
  • ライブコーディング(https://youtu.be/Q-FJ3XmFlT8?t=2504

    • 面倒くさいと感じる
      • 問題が小さくない
      • 面倒臭いと感じる部分を分解する
    • 1-100まで
      • 1からnまで
      • あまり大事ではないのであとに回す
    • プリントする
      • 標準出力をテストするのは難しい
        • その割に効果が薄い
        • テスト容易性が低い
        • ビューのテスト
        • 壊れやすい
      • テスト容易性が低いので、あとに回す
    • 表現にブレがあるなら揃える
      • 大事なとこのみに集中できるようになる
    • 3の倍数のときは数の代わりにFizzにプリントする
      • プリントする部分はテスト容易性が低、重要度も低
      • プリントするを切り離し、変換するに変える
        • 重要で、テスト容易性が高くなった
      • 練習すればできるようになる
    • ただし
      • 準正常系
      • 例外系
      • 普通ではない振る舞い
    • 正常系
      • 1から100までの数をプリントする
        • 数を文字列に変換する
  • ライブコーディング前半(https://youtu.be/Q-FJ3XmFlT8?t=3376

    • 1周目と2周目は設計箇所が重い
      • なにもないところから始めるため
      • 設計の色合いが濃い
      • 名前空間やファイル名を考える
    • テストファイル名
      • テストは入れ子構造にできる
      • 対象クラスのテスト名にする
        • 対象クラス名+テスト
    • 一番最初はテスト環境がちゃんとできているか確認する
      • そのためにテスト失敗のコードを変更して、失敗することを確認する
    • テストコードは、動く仕様書であってほしい
      • 母語でテストコードを書く
      • プロジェクトに合わせる
    • テストコードの構造
      • 4フェーズテスト(3Aパターン(後片付けなし))
        • 準備(Arrange)
        • 実行(Act)
        • 検証(Assert)
        • 後片付け(言語によって必要)
      • 振る舞い駆動開発
        • Given
        • When
        • Then
    • テスト駆動開発では、下から書く
      • 検証がテストのゴール
        • ゴールは期待値
      • 軸をぶらさずにテストを書いていくことができる
    • プログラミングの手が止まる
      • できると思っていたのが、できない
      • すらすら行くと思っていたが、いけない
      • TODOリストに戻る(設計を考え直す)
      • 早く手が止まるほうがいい(あとになると取り返しがつかない)
      • テストは常に具体的になるので、なんとなくでは進めることはできない
    • TDDでは、抽象と具体をいったりきたりする
    • assertEquals は実測値と期待値がバラバラ
      • 最初に確認する
    • コンパイルエラーはレッド
      • レッドは設計の駆動力
      • 予想通りの失敗は悪くない情報
    • 作る前に使う
      • 作りやすいコードと使いやすいコード
        • 作りやすいコードより使いやすいコードの方が大事
        • 使いやすいコードは利用者から考えないと難しい
    • テストコードのテストはどうする?
      • ディフェクトインサーション(?)
        • わざと実装コードに判別可能な誤りを入れる
        • ミューテーションテスティング(変異性テスト)
          • プロダクトコードの中の機械的に変更可能な箇所を1箇所ずつ変えながら、テストを全件実行する
          • 全件すると時間がかかる(終わらない)
          • 実行コストがでかい
      • テストコードの実装に不安があるときにする
        • ベストなタイミング
          • 実装を変えるコストが一番小さいタイミング(コストが低い)でする
            • つまり一番最初にテストコードのテストをする
            • 仮実装
              • 茶番でテストを成功させにいく方法
              • テストのテストを先にするための方法
      • リファクタリングは実装から
        • コメントもリファクタリングの対象
    • ひどい実装
      • メリット
        • 設計に重きを置きたいとき
          • 実装に頭を使わないようにする
        • テストのテストのような厄介な問題を解決したい
      • デメリット
        • 実装がひどくなる(汚いコード)
      • 三角測量
        • 仮実装に対して、別の値を与えることでまともな方向に戻していく方法
        • テストを増やす
    • テストの増やし方
    • タスクの分割のスキル
    • [質問] 複数組み合わさったテストをしたいときは、そういうテスト用のメソッドを作るべき?それとも分解(のやり方)が間違っている?
      • 半分正解、半分不正解
      • 複数の値がまとめてアサーションしないと意味がないならば一つのアサーションという条件は解除して構わない
        • ゲッター2つまとめて見ないといけない場合とか
        • 戻り値と副作用同時に見ないといけない場合とか
      • けれど、まずは問題を疑う
        • 複数アサーションしないといけないのは、問題を上手く分解できていない
        • ドキュメントとしてのテストの体をなしていない可能性がある
      • ユニットテスト、スモールテストはテストの実行コストは考えなくていい
      • インテグレーションテスト、E2Eの場合
      • カスタムアサーション
        • ドメインの言葉でテストを書けるようになるのでおすすめ
    • テストを増やしてリファクタリングしたい状況が出たとき
      • 例えば、3箇所重複している箇所を発見した
      • レッドならリファクタリングしてはいけない
        • グリーンにする
          • アンドゥでテストを除去する
          • 今失敗しているテストを最短時間でグリーンにする
    • テストからログを出力するのは良くない
      • デモでは、テストの動きを確認するためにやっている
    • テストは、実行する順番が決まっていない

      • プログラマーがテストの実行順序に依存するようなテストを書いてしまうのを防ぐ
      • それぞれのテストが依存していないなら並列実行できるようになる
    • グリーンな状態を維持しながらリファクタリング

    • 仮実装は三角測量を通らないといけないルールはない

      • 実装者の不安レベルで間を挟むといい
    • テストの結果が予想通りなのはプログラマーにとっていいニュース

      • プログラマーのコンディションがいいので、思い切った実装にすすめる
        • テストや実装に不安がないのであれば大股で進めていい
          • 失敗した(レッドになった)なら謙虚に進める(仮実装や三角測量などを利用する)
        • 明白な実装
    • テスト駆動開発には、3つのギアがある(https://youtu.be/Q-FJ3XmFlT8?t=6817

f:id:pickles-ochazuke:20200801181817p:plain
TDD のスキル

  • [質問] 外部仕様、ストーリー、ユースケース、インプットとしてTDDで開発するときは、E2Eで開発するのがいいか? xUnitを使うときにテストファーストで書けない

  • ライブコーディング後半(https://youtu.be/Q-FJ3XmFlT8?t=7005

    • 月日が経ってテストコードのタイトルを見てみると具体的な振る舞いはわかるが、別のパターンの場合にどうなるか分からない
      • 次に何をしたらいいか分からない
      • テストコードの実装を見てみる
        • テスト対象がどうあるべきか伝わってこない
          • プロダクトコードを見に行かないと分からない
          • プロダクトコードにしか仕様が書かれていない
      • 動作するドキュメントになっていない
        • TDDはまだ終わっていない
    • テストコードをドキュメントにする
      • 具体レベルのテストコードで残すと仕様書にはならない
        • 実装するときはそれでもいいが、仕様書として残す場合、仕様レベルに直す必要がある
          • 仕様レベルの名称にする
            • 愚直なやりかた(言語によってはこれしかない)
              • テストコード名を長くする
                • 説明を仕様レベルと具体レベルの両方を書く
          • 階層構造に作っていく
            • 仕様レベルのツリー構造
              • ツリー構造をTODOリストと合わせる
              • 仕様の集合を分解していく
                • ドメイン理解をツリー構造やテストコード名に適用する
    • テストの数をなるべく統一する
      • テストの数の意味は、知らない人からは分からない
      • 三角測量で追加したコードは、いらなくなる(一時的なテスト)
        • メンテナンスコストになる
  • テストの構造化とリファクタリングhttps://youtu.be/Q-FJ3XmFlT8?t=8065

    • テストコードの約束の地
      • そんなものはなかった
        • テストのメンテナンスコストがかかる
          • テストはメンテナンスコスト
        • メンテビリティの低いテストコードが量産されている
    • 理解容易性が低いテストコードはメンテナビリティが低い
      • 読みにくいテストコードは癌になる
    • テスト駆動開発
      • プログラマーが気持ちよく書けるようにするのも大事
        • プログラマーの不安と自身の度合いに応じてテストを書けるので、生産を高める事ができる
        • ただし、TDDのゴールではない
          • プログラマーのコンディションの影響を受けているので、テストの粒度がバラバラになってしまう
      • 仕様として構造化とリファクタリングするのが大事
        • TDD(テスト)を行った本人が行う

f:id:pickles-ochazuke:20200801181847p:plain
まとめ

感想

大事な話が何個もあるので、たぶん時間が経つと忘れそう。

TDDの本を読んだことはあったけど、読んでいると自分の都合のいいように解釈してしまって、TDDはプログラマーが気持ちよく書けるようにするためのプラクティスなんだと少し思ってた節がある。ここで紹介しているように、テストコードが仕様として構造化されるとこまでリファクタリングするのがTDDだと説明していてTDDするときの姿勢を改めようと思った。といっても全然TDDできていないのだけど……。

仮実装や三角測量の話は、TDDの本読むだけだとあまり理解できなかったので、今回のライブコーディングで少し理解した気がする。

テストコードがあるコードでリファクタリングするとき、リファクタリング中はレッドになっていたので、今回の常にグリーンで進めていくのは勉強になった。今後は、グリーンを維持して書いていきたい。

仕事ではテストコードが必須ではないのでついプロダクトコードの実装から入ってしまって、どこかのタイミングでテストを書いたり書かなかったりするのだけど、やっぱりTDDを身に着けてTDDで実装を進めていきたいなぁと思い直した。

今回は話が濃いくてとても良いのだけど、TDDって何?ってのを知りたい人には長いかもしれない。そんな人は、以下の動画がおすすめ。こちらも同じ t-wada さんの講演です。

channel9.msdn.com

あとは、ここらへんもためになる(内容は組織に根付かせるとあるけど、個人でする場合も頭に入れておいたほうがいい話がある)。組織というか自分にテストの文化を馴染ませるために見てる。

組織にテストを書く文化を根付かせる戦略と戦術

リファクタリングする際、この本もためになるとコメントにあった(確か)。

コメント内容は忘れてしまったけど、ここらへんも紹介されていた。

t-wada.hatenablog.jp

テストとリファクタリングに関する深い方法論 #wewlc_jp

私にとってのテスト

http://xunitpatterns.com/~gerard/AgileSingapore2016-slides.pdf

講演ありがとうございました!!!

「ランウェイで笑って」を見た

今年は何故かアニメをたくさん(主観)見ているのでせっかくだし見て面白かったアニメはここに記録していこうと思った。

とりあえず今回は「ランウェイで笑って」。

概要

原作

漫画(週刊少年マガジン

あらすじ

身長158cmの藤戸千雪の夢は、 パリコレモデル。 モデルとして致命的な低身長を理由に、 周囲は「諦めろ」と言うが、 それでも折れない。 そんなとき、家族を養うために ファッションデザイナーの夢を 諦めようとする都村育人に出会う。

――これは一途に夢を追って 走り続ける、2人の物語。

こちらから引用

runway-anime.com

感想

二回見た。一回目を見終わったとき、まだランウェイで笑っての世界に浸りたくなったので見終わった次の日にもう一度見ていた。このアニメの好きなところは以下

  • 毎話先が気になる終わり方
  • 暗い状況になってもあまり引き伸ばさずに前向きに解決する爽快さ(潔さ?)
  • 千雪ちゃんかわいい

アニメの専門的な話はよくわからないけど、このアニメはシリーズ構成が良いように感じた。原作は知らないので原作通りに進んでいるかも分からないけど、ぽんぽん話が進んで飽きなかった。ただちょっと話を飛ばしているように感じる部分があった(日数の経過が早くて混乱することがあった)。

ショーモデル(ファッションショーで服を魅せるために歩く人)の話なのに、ショーモデルの作画に違和感があったので作画を気にする人は辛いかもしれない。でも全体的に良かったのであまり気にならなかった。

あとは出てくるキャラクター、特に自分の夢を目指そうとしてる人たちみんなが前向きな感情で動いてて良かった。後ろ向きな気持ちを持ってるキャラが出てくるけど、他のキャラが引っ張って前向きに動くような感じ。

見終わったあとに似たようなアニメを見たいと思ったけど見つかる気がしなかったのでじゃあもっかい見るかと。あと千雪ちゃんのかわいさを補充したかった。

最後の終わり方も続きが気になる感じで原作を読みたくなった。

おわりに

千雪ちゃんが自宅でテレビ見ながら仰向けに寝転んでるときのシーンがとても良かったです。

参考

www.amazon.co.jp

Android のコンポーネント(Activity)の単体テスト3 - 別アクティビティからの結果を取得する

タイトル長いな……。簡潔に言うと onActivityResult のテストをする方法です。

前回の続きになります。

pickles-ochazuke.hatenablog.com

概要

前回と同じ Espresso.Intents を使います。Espresso-Intents については、前回話しているので飛ばします。

onActivityResult は、requestCode, resultCode, インテントデータを受け取ります。この内、resultCode と インテントデータの2つのデータは、別のアクティビティが setResult() で設定しています。この別のアクティビティが設定する部分をスタブ化します(スタブ化は、簡単に言えば、機能を真似するようにする。ということです)。

スタブ化するためには、Intents.intending() とその戻り値である OngoingStubbing のメソッド respondWith() を使います。

developer.android.com

developer.android.com

developer.android.com

次のように使います。

intending(hasComponent(OtherActivity::class.java.name)).respondWith(result)

使い方としては、intending() の引数にスタブ化したい対象が一致する条件を渡し、そのスタブ化の対象が呼ばれたときに渡したい結果を respondWith() に渡します。

上記だと OtherActivity が呼ばれた(OtherActivity が作成された)ときに、そのアクティビティが終了されたものとし、result を onActivityResult() の各引数(resultCode, intent)に渡されます。

上手く説明出来ませんが、実際に使用すると感覚が掴めると思います。

実践では、前回のプロジェクトを使うので、なければこちらの ActivityUnitTestExample_2 ブランチを使うと同じ状態に出来ます。

github.com

実践

今回追加する処理は、前回のプロジェクトを使い、OtherActivity を起動したら挨拶の情報を持ったインテントが返ってくるので、それをテキストに反映させるという処理にします。テストで確認するだけなので OtherActivity 側は何も変更しません。ですので、実際のアプリで OtherActivity は何も返してきません。

まずは、別アクティビティから結果を受け取れるようにします。MainActivity.kt を以下のように変更します。

import android.widget.TextView
...
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if (resultCode != RESULT_OK) { return }
        if (requestCode != 1) { return }

        findViewById<TextView>(R.id.helloWorld).text = data?.getStringExtra("greeting")
    }

ついでに、startActivity() から startActivityForResult() に変更します。

startActivityForResult(intent, 1)

次にテストを追加します。

import android.app.Activity
import android.app.Instrumentation
import android.content.Intent
import androidx.test.espresso.intent.Intents.intending

    @Test
    fun OtherActivityから受け取った結果がテキストに反映されるべき() {
        val intent = Intent().apply {
            this.putExtra("greeting", "Hi World!")
        }

        val result = Instrumentation.ActivityResult(Activity.RESULT_OK, intent)
        intending(hasComponent(OtherActivity::class.java.name)).respondWith(result)

        onView(withId(R.id.button)).perform(click())
        onView(withId(R.id.helloWorld)).check(matches(withText("Hi World!")))
    }

intending() 手前までは、ActivityResult を作成しています。intending() の引数は、スタブ化したい対象の条件を指定しています。そして、respondWith()ActivityResult を渡しています。

https://developer.android.com/reference/android/app/Instrumentation.ActivityResult

スタブ化が完了したら、Espresso で UI を操作し、ビューが期待通りになっているか検証します。デバッグ実行を行い、onStartActivity()onView(withId(R.id.button)).perform(click())ブレークポイントを設定するとが perform() 実行されたあとに onStartActivity() で止まることが確認できます。

以上です。

ここまで行った状態が途中に上げた Github にあるリポジトリActivityUnitTestExample_3 ブランチです。

雑記

GW で調べていたことを放出しました。本当は Todo アプリを作って Android の理解を深めようと思ったのですが、その Todo アプリをテストしようとしたらいろいろエラーを踏んで3,4日潰れました……。まだテストのことは理解が足りていないので間違ったことを書いているかもしれませんが、誰かの助けになれば幸いです(間違っていれば指摘しただけると助かります)。

今年のGWは、技術書読み進めたり、Android のテスト調べたり、これ書いたりと割と充実していました(それでもやり残したことはありますが)。テストのエラー解決できないとき諦めようと思いましたが時間あけて考え直すと解決したので、やっぱり諦めずにいるのは大事だなと思いました(そして適度な休息!)。

あと恋する小惑星を見て地学に興味湧いたので NHK の地学基礎って動画見てるんですがかなり面白くてノートにまとめたりしています。

koiastv.com

www.nhk.or.jp

ちなみに私はジャイアント・インパクト説派です(他の説あんまり知りませんが)。