LoginSignup
21
27

More than 5 years have passed since last update.

研究者へのテストコードのススメ(研究者だからむしろ正しい研究結果のために実践しよう)

Posted at

はじめに

対象者

  • 研究者
  • MatlabやPythonでシミュレーション書いている人
  • 実機を実装している/しようとしている人

コメント

テストケース初心者です.
今の俺に絶対必要だ!と思ってはじめました.
一応ある会社でインターンしてた時Rubyで少し触れたぐらいの記憶です.
自分のメモのためにの記述です.

きっかけ

現在,自分がドローンの研究をしていることもあり,
Matlabでシミュレーション, C++に実装(実現不可だったので), Simulinkでの実装という風に
いくつか言語化跨る上にコードの改変が非常に多いので,あっちこっち編集してたら数ヶ月無駄にしたのでテストコードを書くことを決心した.

利点

  • あっちこっち編集してもモジュール一つ一つは「正確に動く」ことが保証できる
  • 一気にコード書かずに細かく細かくモジュール単位で考えられるようになる
  • 汎用性の高いモジュール(クラス)を設計しようと心がけるため,結局一つ作れば色んなところで使えるようになる
    • カルマンフィルタ
    • 制御器
    • ドローンのモデル, モータのモデル etc..
  • 指導教員に「なんでそこまでは正しいと言えるの?」と聞かれても「外部装置と比較した値を使ったテストコードがエラーを履かないので自分のコードが間違っていない限り正しいと言える」とさらっと攻撃を避けることができる
  • 研究に信憑性を
  • 実機に実装する時,[大体どのくらいの値に収まるか」「どういうパラメータが欲しいか・計測しなければいけないのか」がシミュレーション書く時から見えてくるので設計の見通しがしやすい.

テストケース書かないと

  • どこがエラってるのか分からない
  • 一個一個手で実行していく必要がある
  • ひとつのモジュールを変えて他が動くなっても,変えた方のモジュールのみ疑うようになり最終的に奥底に潜む問題点にたどり着かない
  • 結果,時間が無駄に過ぎていく

 テストケースの書き方

Matlab編

参考資料:
- MATLAB Unit Testing Framework
- runtests

チャチャっと書いた数行のコードをテストしたい(runtests

フォルダ内全部をテスト

以下のようなコードを用意

%% Test size
exp = [7 13];
act = ones([7 13]);
assert(isequal(size(act),exp))

以下で実行
runtests

1つのmファイル

以下のようなmファイルを用意

function tests = runtestsExampleTest
tests = functiontests(localfunctions);

function testA(testCase)
verifyEqual(testCase,5,5);

function testB(testCase)
verifyTrue(testCase,logical(1));

以下で実行
results = runtests('runtestsExampleTest.m');

いくつかの関数・mファイル・クラステストをまとめて実行したい(testsuite

フォルダ内全部

suite = testsuite

mファイル1つのみ

以下のようなmファイル1つ用意

function tests = eyeTest
tests = functiontests(localfunctions);

function doubleClassTest(testCase)
actValue = eye;
verifyClass(testCase,actValue,'double')

function singleClassTest(testCase)
actValue = eye('single');
verifyClass(testCase,actValue,'single')

function uint16ClassTest(testCase)
actValue = eye('uint16');
verifyClass(testCase,actValue,'uint16')

function sizeTest(testCase)
expSize = [7 13];
actValue = eye(expSize);
verifySize(testCase,actValue,expSize);

function valueTest(testCase)
actValue = eye(42);
verifyEqual(testCase,unique(diag(actValue)),1)    % diagonal are 1s
verifyEqual(testCase,unique(triu(actValue,1)),0)  % upper tri vals are 0
verifyEqual(testCase,unique(tril(actValue,-1)),0) % lower tri vals are 0

以下でsuite作成
suite = testsuite('eyeTest')
以下でテスト実行
result = run(suite)

いくつかのファイルをまとめたい

% 現在のインポートリストにmatlab.unittest.TestSuiteクラスを追加
import matlab.unittest.TestSuite;
% テストスイート作成
fileSuite    = TestSuite.fromFile('SomeTestFile.m'); 
folderSuite  = TestSuite.fromFolder(pwd);
packageSuite = TestSuite.fromPackage('mypackage.subpackage');
classSuite   = TestSuite.fromClass(?mypackage.MyTestClass); 
methodSuite  = TestSuite.fromMethod(?SomeTestClass,'testMethod');
% 全体をまとめるSuite作成
largeSuite = [fileSuite, folderSuite, packageSuite, classSuite, methodSuite];
% 実行
result = run(largeSuite)

自分で定義したクラスのテスト(KalmanFilter.mなど)

クラスをしようするセットアップコードと破棄

classdef BankAccountTest < matlab.unittest.TestCase
    % Tests the BankAccount class.

    methods (TestClassSetup)
        function addBankAccountClassToPath(testCase)
            p = path;
            testCase.addTeardown(@path,p);
            addpath(fullfile(matlabroot,'help','techdoc','matlab_oop',...
                'examples'));
        end
    end

    methods (Test)
        function testConstructor(testCase)
            b = BankAccount(1234, 100);
            testCase.verifyEqual(b.AccountNumber, 1234, ...
                'Constructor failed to correctly set account number');
            testCase.verifyEqual(b.AccountBalance, 100, ...
                'Constructor failed to correctly set account balance');
        end

        function testConstructorNotEnoughInputs(testCase)
            import matlab.unittest.constraints.Throws;
            testCase.verifyThat(@()BankAccount, ...
                Throws('MATLAB:minrhs'));
        end

        function testDesposit(testCase)
            b = BankAccount(1234, 100);
            b.deposit(25);
            testCase.verifyEqual(b.AccountBalance, 125);
        end

        function testWithdraw(testCase)
            b = BankAccount(1234, 100);
            b.withdraw(25);
            testCase.verifyEqual(b.AccountBalance, 75);
        end

        function testNotifyInsufficientFunds(testCase)
            callbackExecuted = false;
            function testCallback(~,~)
                callbackExecuted = true;
            end

            b = BankAccount(1234, 100);
            b.addlistener('InsufficientFunds', @testCallback);

            b.withdraw(50);
            testCase.assertFalse(callbackExecuted, ...
                'The callback should not have executed yet');
            b.withdraw(60);
            testCase.verifyTrue(callbackExecuted, ...
                'The listener callback should have fired');
        end
    end
end

Simulink編

参考資料:
- Simulink and MALTAB Unit Testing Frameword
- Test Models Using MATLAB Unit Test

教えてください・・・・

Python編

import unittest

def Addition(a, b):
    return a+b

class AdditionTests(unittest.TestCase):
    def testEqual(self):
       a = 1
       b = 2
       c = Addition(1,2)
       self.assertTrue(c==3)

if __name__ == "__main__":
    unittest.main()
21
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
27