EmbVision チュートリアル(3)

クラス定義と単体テスト

新潟大学 工学部 電気電子工学科  村松 正吾

Copyright (c), All rights reserved, 2014-2015, Shogo MURAMATSU

Contents

Part2 | メニュー | Part4

概要

本演習では、MATLABでのオブジェクト指向プログラミングと 単体テスト機能について簡単に学ぶ。

オブジェクト指向プログラミング(OOP)

プログラムのモジュール化は高い信頼性と広い拡張性をもたらす。 オブジェクト指向プログラミング ( OOP )では、

状態(プロパティ)振舞い(メソッド)

をもつプログラムモジュールをクラスとして定義し、

クラスの実体(インスタンスオブジェクト)

を生成しながら組合せ、相互に作用させて大規模なプログラムを構築する。

MATLABでもOOPを利用することができる。

バージョンアップを重ねる毎に OOP機能が強化されてきており、今では System object™ 基底クラスの継承により、

が容易となっている。

[ トップ ]

System objectクラスの定義

では、例としてRGB画像 から グレースケール画像に変換する System object クラスの型枠を定義してみよう。

ホームタグから、

  1. 「新規作成」
  2. →「System object >」
  3. →「標準」

を選択するとエディタが開き以下のようなコードの編集準備が整う。

classdef Untitled < matlab.System
  % Untitled Add summary here
  %
  % This template includes the minimum set of functions required
  % to define a System object with discrete state.
  properties
      % Public, tunable properties.
  end
  properties (DiscreteState)
  end
  properties (Access = private)
      % Pre-computed constants.
  end
  methods (Access = protected)
      function setupImpl(obj,u)
          % Implement tasks that need to be performed only once,
          % such as pre-computed constants.
      end
      function y = stepImpl(obj,u)
          % Implement algorithm. Calculate y as a function of
          % input u and discrete states.
          y = u;
      end
      function resetImpl(obj)
          % Initialize discrete-state properties.
      end
   end
end

classdef 直後の Untitled を

Rgb2GraySystem

と書き換え、先頭行が以下のようになるよう編集しよう。

classdef Rgb2GraySystem < matlab.System
  % RGB2GRAYSYSTEM RGB to Grayscale Converter
  %

編集したファイルを Rgb2GraySystem.m として保存しよう。

なお、 classdef に続く文字列はクラス名となる。 クラス名はファイル名と一致させる必要がある。

利用手順を以下にまとめる。

  1. インスタンスオブジェクトを生成(コンストラクタの呼出し)
  2. step メソッドを実行(stepImplの呼出し)
>> u = 1;
>> obj = Rgb2GraySystem(); % コンストラクタの呼出し
>> y = step(obj,u)         % stepImpl の呼出し
y =
      1

現時点で Rgb2GraySystem のクラス定義は自動生成されたままである。

stepImpl メソッドは、入力 u をそのまま出力する。

function y = stepImpl(obj,u)
   y = u;
end

クラス Rgb2GraySystem が所望の機能を満たすためには状態(プロパティ)と 振舞い(メソッド)を適切に実装しなければならない。

以下では、 テスト駆動開発 により Rgb2GraySystem クラスの実装を進める。

[ トップ ]

テスト駆動開発

プログラムの信頼性を向上させるためには、テスト用のコードを充実させると良い。

MATLABでは、モジュール毎の 単体テスト を自動化するフレームワークが提供されている。

単体テストはクラス単位で行うテストで、実装コードよりもテストの記述を 優先するテスト駆動開発に欠かせない。

テストを優先して充実させることで バグが少なく改変や拡張に強いプログラムを開発できる。

一般に、テスト駆動開発では、

を繰返し、テストクラスとターゲットクラスを充実させる。

以下の手順で、 Rgb2GraySystem のクラス実装を進めよう。

  1. テストクラス Rgb2GraySystemTestCase の定義
  2. テストクラス Rgb2GraySystemTestCase へのメソッド testSize の実装
  3. ターゲットクラス Rgb2GraySystem のメソッド testSize の失敗の確認
  4. ターゲットクラス Rgb2GraySystem のメソッド stepImpl の実装
  5. ターゲットクラス Rgb2GraySystem のメソッド testSize の成功の確認

テストクラスの定義

では、テストクラス Rgb2GraySystemTestCase を定義してみよう。

ホームタグから、

  1. 「新規作成」
  2. →「クラス」

を選択するとエディタが開き以下のようなコードの編集準備が整う。

classdef Untitled
%UNTITLED このクラスの概要をここに記述
   %   詳細説明をここに記述
   properties
   end
   methods
   end
end

matlab.unittest.TestCase クラスを継承して、テストクラス Rgb2GraySystemTestCase を定義すればよい。

すなわち、先頭部 classdef の行を

classdef Rgb2GraySystemTestCase < matlab.unittest.TestCase
%RGB2GRAYSYSTEMTESTCASER Rgb2GraySystem のテストケース

と書き換えればよい。

また、 Test 属性の付いた methods ブロック

methods (Test)
end

を用意する。

編集後のコード全体は以下のようになる。

classdef Rgb2GraySystemTestCase < matlab.unittest.TestCase
    %RGB2GRAYSYSTEMTESTCASE Rgb2GraySystem のテストケース
    properties
    end
    methods (Test)
    end
end

編集したファイルを Rgb2GraySystemTestCase.m として保存しよう。

[ トップ ]

テストメソッドの追加

では、テストクラス Rgb2GraySystemTestCase に テストメソッドを追加しよう。

Test 属性のついた methods ブロック内に テストの内容を記述したメソッドを追加すればよい。 同ブロック内の全てのメソッドが自動的にテストの対象となる。

テストメソッド

function testSize(testCase)
   % 準備
   u = zeros(1,2,3);   % 1行2列3成分の三次元零配列
   % 期待値
   szExpctd = [ 1 2 ]; % 1行2列の二次元配列
   % ターゲットのインスタンス化
   obj = Rgb2GraySystem();
   % 実行結果
   y = step(obj,u);
   % サイズの検証
   testCase.verifySize(y,szExpctd);
end

を Rgb2GraySystemTestCase の methos (Test) ブロックに追加しよう。

なお、 verifySize は、値が指定されたサイズであることを検証する。

zeros 関数は、指定されたサイズの零配列を 生成する。

コマンドウィンドウ上で run メソッドを呼び出してテストを実行しよう。

>> result = run(Rgb2GraySystemTestCase);

Rgb2GraySystem の実装が不十分なので、検証は失敗に終わる。

ターゲットクラスの実装

検証の失敗を回避するためにクラス Rgb2GraySystem の stepImpl メソッド を

function y = stepImpl(obj,u)
   y = zeros(size(u,1),size(u,2));
end

と定義しなおして、再度テストを実行しよう。

>> result = run(Rgb2GraySystemTestCase);
Rgb2GraySystemTestCase を実行しています
.
Rgb2GraySystemTestCase が完了しました
__________

結果は失敗から成功に変わる。

テストクラスとターゲットクラスの充実化

以降、以下の手順を繰り返してテストクラスとターゲットクラスを充実させる。

  1. テストクラスへのテストメソッドの実装
  2. ターゲットクラスのテスト失敗の確認
  3. ターゲットクラスへのメソッドの実装
  4. ターゲットクラスのテスト成功の確認

次のテストクラス

classdef Rgb2GraySystemTestCase < matlab.unittest.TestCase
    %RGB2GRAYSYSTEMTESTCASE Rgb2GraySystem のテストケース
    properties
    end
    methods (Test)
        function testSize(testCase)
            % 準備
            u = zeros(1,2,3);   % 1行2列3成分の三次元配列
            % 期待値
            szExpctd = [ 1 2 ]; % 1行2列の二次元配列
            % ターゲットのインスタンス化
            obj = Rgb2GraySystem();
            % 実行結果
            y = step(obj,u);
            % サイズの検証
            testCase.verifySize(y,szExpctd);
        end
        function testValues(testCase)
            % 準備
            u = rand(4,6,3);        % 三次元ランダム配列
            % 期待値
            arrayExpctd = rgb2gray(u); % グレースケールの期待値
            % ターゲットのインスタンス化
            obj = Rgb2GraySystem();
            % 実行結果
            arrayActual = step(obj,u);
            % 配列の値の検証
            testCase.verifyEqual(arrayActual,arrayExpctd);
        end
    end
end

を満足するように、 Rgb2GraySystem クラスを実装しよう。

なお、 verifyEqual は、値が指定値と等しいことを検証する。

rand 関数は、0から1の実数型の一様乱数を生成 する。

result = run(Rgb2GraySystemTestCase);
Rgb2GraySystemTestCase を実行しています
..
Rgb2GraySystemTestCase が完了しました
__________

Rgb2GraySystem を正しく実装できれば、テストは成功する。

[ トップ ]

プロパティの利用

クラスは振舞い(メソッド)の他に状態(プロパティ)を持つことができる。

クラスの状態は properties ブロック内で プロパティ名を列挙する。

例えば、 Kernel という名前のプロパティをもつ GradFiltSystem という名の System object クラスの定義は

classdef GradFiltSystem < matlab.System
    properties
       Kernel % 追加したプロパティ
    end
    properties (DiscreteState)
    end
    properties (Access = private)
    end
    methods (Access = protected)
        % セットアップ(最初のステップ直前に実行)
        function setupImpl(obj,srcImg)
        end
        % ステップ
        function resImg = stepImpl(obj,srcImg)
           resImg = srcImg;
        end
        % リセット
        function resetImpl(obj)
        end
     end
 end

のように記述される。

プロパティには初期値を与えることもできる。 プロパティ Kernel の初期値が

Kernel = [  1  1  1 ;
            0  0  0 ;
           -1 -1 -1 ];

となることを期待するテストクラスを実装しよう。

classdef GradFiltSystemTestCase < matlab.unittest.TestCase
    %GRADFILTSYSTEMTESTCASE GradFiltSystem のテストケース
    properties
    end
    methods (Test)
        function testDefaultKernel(testCase)
            % 期待値 
            kernelExpctd = [  1  1  1 ;
                              0  0  0 ;
                            -1 -1 -1 ];
            % ターゲットクラスのインスタンス化
            obj = GradFiltSystem();
            % プロパティー Kernel の取得
            kernelActual = get(obj,'Kernel');
            % プロパティー Kernel の検証
            testCase.verifyEqual(kernelActual,kernelExpctd)
        end
    end
end

GradFiltSystem クラスを上のテストが通るように Kernel プロパティを 編集する。

properties
    Kernel = [  1  1  1 ; % 初期化したプロパティ
                0  0  0 ;
               -1 -1 -1 ];
end

なお、メソッド内でプロパティにアクセスは、

function y = stepImpl(obj,u)
   y = conv2(obj.Kernel,u);
end

のように、第一引数(上の例では変数 obj )を介してドット(.)により行う。

[ トップ ]

コンストラクタ

プロパティ Kernel は、 GradFiltSystem クラスの インスタンスオブジェクトを生成する際に変更することもできる。 まず、以下のテストメソッドを GradFiltSystemTestCase に追加しよう。

methods (Test)
   ...(省略)
   function testSobelKernel(testCase)
      % 期待値
      kernelExpctd = [  1  2  1 ;
                        0  0  0 ;
                       -1 -2 -1 ];
      % ターゲットクラスのインスタンス化
      obj = GradFiltSystem('Kernel',kernelExpctd);
      % プロパティー Kernel の取得
      kernelActual = get(obj,'Kernel');
      % プロパティー Kernel の検証
      testCase.verifyEqual(kernelActual,kernelExpctd)
   end
   ...(省略)
end

上のテストを通過するようにコンストラクタを定義しよう。

コンストラクタは、 属性を指定しないmethodsブロック(パブリック)内で クラス名と同じ名前のメソッドとして定義する。

methods
   % コンストラクタ
   function obj = GradFiltSystem(varargin)
      setProperties(obj,nargin,varargin{:})
   end
end
methods (Access = protected)
   ...
end

varargin は可変長の引数入力を受け取る。 setProperties は、

'プロパティ名1', プロパティ値1,_'プロパティ名2'_, プロパティ値2,...

の組合せでの設定を可能とする。

[ トップ ]

演習課題

課題3-1. HSV2RGBクラス

次のテストクラス

classdef Hsv2RgbSystemTestCase < matlab.unittest.TestCase
    %HSV2RGBSYSTEMTESTCASE Hsv2RgbSystem のテストケース
    properties
    end
    methods (Test)
        function testSize(testCase)
            % 準備
            h = zeros(1,2);
            s = zeros(1,2);
            v = zeros(1,2);
            % 期待値
            szRExpctd = [ 1 2 ];
            szGExpctd = [ 1 2 ];
            szBExpctd = [ 1 2 ];
            % ターゲットのインスタンス化
            obj = Hsv2RgbSystem();
            % 実行結果
            [r,g,b] = step(obj,h,s,v);
            % サイズの検証
            testCase.verifySize(r,szRExpctd);
            testCase.verifySize(g,szGExpctd);
            testCase.verifySize(b,szBExpctd);
        end
        function testValues(testCase)
            % 準備
            h = rand(4,6);
            s = rand(4,6);
            v = rand(4,6);
            hsv = cat(3,h,s,v); % 三次元配列化
            % 期待値
            rgbExpctd = hsv2rgb(hsv);
            % ターゲットのインスタンス化
            obj = Hsv2RgbSystem();
            % 実行結果
            [rActual,gActual,bActual] = step(obj,h,s,v);
            % 配列の値の検証
            testCase.verifyEqual(rActual,rgbExpctd(:,:,1));
            testCase.verifyEqual(gActual,rgbExpctd(:,:,2));
            testCase.verifyEqual(bActual,rgbExpctd(:,:,3));
        end
    end
end

を満足するように、 Hsv2RgbSystem クラスを実装しよう。

なお、 cat 関数は指定した次元の方向に配列の結合を 行う。

result = run(Hsv2RgbSystemTestCase);
Hsv2RgbSystemTestCase を実行しています
..
Hsv2RgbSystemTestCase が完了しました
__________

Hsv2RgbSystem を正しく実装できれば、テストは成功する。

課題3-2. 勾配フィルタクラス

次のテストクラス

classdef GradFiltSystemTestCase < matlab.unittest.TestCase
    %GRADFILTSYSTEMTESTCASE GradFiltSystem のテストケース
    properties
    end
    methods (Test)
        function testDefaultKernel(testCase)
            % 期待値
            kernelExpctd = [  1  1  1 ;
                              0  0  0 ;
                             -1 -1 -1 ];
            % ターゲットクラスのインスタンス化
            obj = GradFiltSystem();
            % プロパティー Kernel の取得
            kernelActual = get(obj,'Kernel');
            % プロパティー Kernel の検証
            testCase.verifyEqual(kernelActual,kernelExpctd)
        end
        function testSobelKernel(testCase)
            % 期待値
            kernelExpctd = [  1  2  1 ;
                              0  0  0 ;
                             -1 -2 -1 ];
            % ターゲットクラスのインスタンス化
            obj = GradFiltSystem('Kernel',kernelExpctd);
            % プロパティー Kernel の取得
            kernelActual = get(obj,'Kernel');
            % プロパティー Kernel の検証
            testCase.verifyEqual(kernelActual,kernelExpctd)
        end
        function testStepWithPrewittKernel(testCase)
            % 準備
            H = [  1  1  1 ;
                   0  0  0 ;
                  -1 -1 -1 ];
            % 期待値の準備
            I  = imread('cameraman.tif');
            X  = im2double(I);
            Yv = conv2(H  ,X);        % 垂直勾配の計算
            Yv = Yv(2:end-1,2:end-1); % 処理画像のクリッピング
            Yh = conv2(H.',X);        % 水平勾配の計算 
            Yh = Yh(2:end-1,2:end-1); % 処理画像のクリッピング
            magExpctd = sqrt(Yv.^2+Yh.^2); % 勾配の大きさの期待値
            angExpctd = atan2(Yv,Yh);     % 勾配の偏角のの期待値
            % ターゲットクラスのインスタンス化
            obj = GradFiltSystem();
            % 処理結果
            [magActual,angActual] = step(obj,X);
            % 処理結果の検証
            testCase.verifyEqual(magActual,magExpctd,'AbsTol',1e-6)
            testCase.verifyEqual(angActual,angExpctd,'AbsTol',1e-6)
        end
    end
end

を満足するように、 GradFiltSystem クラスを実装しよう。

result = run(GradFiltSystemTestCase);
GradFiltSystemTestCase を実行しています
...
GradFiltSystemTestCase が完了しました
__________

GradFiltSystem を正しく実装できれば、テストは成功する。

(処理例)

hrs = Hsv2RgbSystem();
gfs = GradFiltSystem();

I = imread('cameraman.tif');     % 画像入力
[mag,ang] = step(gfs,I);         % 勾配フィルタリング
ang = (ang+pi)/(2*pi);           % 偏角の正規化
mag = min(mag,1);                % 大きさの飽和処理
[R,G,B] = step(hrs,ang,mag,mag); % 疑似カラー化
J = cat(3,R,G,B);                % RGB配列結合
imshow(J)                        % 画像表示


Part2 | メニュー | トップ | Part4