SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

単体テスト自動化に向けてDBアクセスを抽象化する

最近、RPAについて調べる機会がありました。

RPA:Robotic Process Automation(ロボットによる業務自動化)
「デジタルレイバー(Digital Labor)」や「仮想知的労働者」とも言い換えられるようです。

要するに、ホワイトカラー(事務等)の業務を自動化しよう!と取り組むためのシステムを指します。

RPAでは、Webアプリの操作自動化はほぼ確実に含まれます。*1

RPAによるWebアプリの操作自動化に関連して話に上がってくるのは、Webアプリのテスト自動化です。

WebのUIを自動で操作して、テストする場合は、DB等含めてすべて組みあがった状態で、本運用を想定したテストも行われると思います。
これもすごく大事なのですが、私が気にしている(今後やる)ことは、それより前の単体テストです。

単体テストは、UI自動テストと異なり、実施環境面や実装面で様々な制約を受けます。
途中から入ったプロジェクトだし、テスト駆動じゃないし、いろいろ躊躇しています。でもやるしかないので、制約等をおさらい含めて書いていきます。

前提

MVCフレームワークを採用したWebアプリとします。
DB接続もありです。

DBもブラウザも無い環境で動作させる

そもそも、単体テストどこで実行するのでしょうか?




通常は、ビルドサーバーで実行するように、構成すると思います。

そうなると、以下のようなリソースにはアクセスできない前提が必要になります。

  • データベース
  • ブラウザ*2
  • 動作の前提となる物理ファイル

つまりどういうことかというと、ビルドしたソースコードのみで動作する構成にしておく必要があります。

DBアクセスを無くす

なんかリクエストを受けて、DBアクセスして、その値を加工して、クライアントに返す、とします。

この場合、テスト対象としては、リクエストで受けたデータを偽装して、結果が正しいか確認すること、となります。

上記の場合、困るのはDBアクセスです。以下のソースがテスト対象とします。(クラス名等は適当)

  • テスト対象
public class Hogehoge {
    private Database db;
    public Hogehoge() {
        db = new Database();
    }

    public HogeData GetHoge(string paramStr){

        //DBアクセスとする
        var data = db.FindByParam(paramStr);

        //計算
        var edited = HogeService.Calculate(data);
        
        //返す(このHogeDataの内容が想定通りならOK)
        return HogeData.Create(edited);
    }
}
  • テストコード
public void HogeTest(){
    //テスト用の値
    string prm = "sample";

    //テスト対象クラス
    var hogehoge = new Hogehoge();

    //何もしないとDBアクセスに失敗してしまう!
    var actual = hogehoge.GetHoge(prm);

    Assert.IsEqual("expected value", actual.Value);
}

こういう時は、DBアクセス部分を抽象化します。
具体的には、インターフェイスにしてしまいます。

DBアクセスのように、何かを取ってくる機能を抽象化する場合、Repositoryという名称を使うのが一般的です。
そして、それを利用者に渡す際は、依存性の注入を使います。

依存性の注入については、以下の記事で触れています。

hiroronn.hatenablog.jp

hiroronn.hatenablog.jp

1.リポジトリ作成

まず、リポジトリを作ります。

public interface IHogeRepository {
    HogeValue FindByParam(string prm);
}

DB接続する場合のリポジトリは、さくっと作ります。

//DBアクセスのリポジトリ
public class HogeRepository: IHogeRepository {
    public HogeValue FindByParam(string prm) {
        var db = new Database();
        return db.FindByParam(prm);
    }
}

次に、ダミーのリポジトリを作成します。
これを置く場所に迷いますが、テストプロジェクトの中(つまり、システムソースから見えない位置)に置くのが好ましいです。

//テスト用フェイクリポジトリ
public class FakeHogeRepository: IHogeRepository {
    public HogeValue FindByParam(string prm) {
        HogeValue v = new HogeValue();
        //なにかしらの値割り当て
        v.~~ = ~~~;

        return v;
    }   
}

こうすると、テストに使う値を、テストする際に自由に設定できるようになります。

2.テスト対象を、リポジトリを使用するように修正する

ここまで準備できたら、次はテスト対象のソースの仕様を変えます。

public class Hogehoge {
    private IHogeRepository _repository;
    public Hogehoge(IHogeRepository repository) {
        //new Databaseが消えた!当クラスは明示的にはDBに依存しなくなった!
        _repository = repository;
    }
    public HogeData GetHoge(string paramStr){

        //リポジトリからデータを取得
        var data = _repository.FindByParam(paramStr);

        //計算
        var edited = HogeService.Calculate(data);
        
        //返す(このHogeDataの内容が想定通りならOK)
        return HogeData.Create(edited);
    }
}

db変数参照箇所を、全てIHogeRepositoryに置き換えました。

3.利用クラスの修正

あとは、このクラスのコンストラクタを作成している部分を修正したら終了です。

これで、単体テストに対応できるようになりました。

単体テストのソースはこんな感じです。

public void HogeTest(){
    //テスト用の値
    string prm = "sample";

    //テスト対象クラス
    var hogehoge = new Hogehoge(new FakeHogeRepository());

    var actual = hogehoge.GetHoge(prm);

    Assert.IsEqual("expected value", actual.Value);
}

同じようなものがたくさんある場合

一つやったらまた一つ…と、繰り返していくしかないです。
これこそ、ほぼ定型作業なので、自動化できればいいのに、と思わなくもないです。

最初からやっていれば…

最初から、リポジトリと依存性の注入使っていればいいのに!とは思います。

使われていない理由は、前担当者も現担当者も、こうやって単体テスト対応させていくことを知らなかったようです。

こういうことを知る機会に乏しい、ということでしょうか?わかりません。

まとめ

フェーズ 環境依存 自動化可否
単体テスト 依存してはいけない 可能
結合テスト 依存OK 可能だが、シナリオ策定が必須
自動UIテスト 依存OK
むしろ、環境をシナリオとして策定する
可能

こんな感じでしょうか。

テストを手動でやって、時間をつぶしていられるほど、エンジニアは暇ではないのかもしれません。
率先して、自動化のテクニックを学んでいって、世を先導するぐらいの気持ちで挑みたいものです。

まあ、できてないからこんな記事書いているのですが。今後も精進です。

こういう、どう書き直したらいいか分からない!というネタの解決策を考えるのは好きなので、いろいろ考えてみたいです。
ネタは大歓迎です!!!


*1:デスクトップアプリは、システムによって異なる

*2:ブラウザが入っていないマシンなんて無いとは思うが