空飛ぶロボットのつくりかた

ロボットをつくるために必要な技術をまとめます。ロボットの未来についても考えたりします。

mockテストのお勉強

mock.return_value

  • 返り値を指定できる

mock.called

mock.call_count

  • 何回呼ばれたか確認できる

mock.call_args

mock.call_args_list

  • 引数のリストがわかる

mock.side_effect

  • いろんなものが指定できる

例えば(イテレート)

side_effect = chain([1,2,3,4])

mock.method_calls

mock.mock_calls

  • mock_calls は、メソッド,特殊メソッド,戻り値のモックまで、モックオブジェクトに対するすべての呼び出しを記録する

mock.assert_called

mock.assert_called_once

mock.assert_called_with

mock.assert_called_once_with

mock.assert_any_call

assert_has_calls

参考:

26.5. unittest.mock — モックオブジェクトライブラリ — Python 3.6.1 ドキュメント

パッチデコレータ

@patch

参考:

Pythonのテストコードでmockを使ってみた | Developers.IO

mock(Pyhtonモックライブラリ)についてのメモ - Qiita

Pythonのデバッグとテストモジュール - Qiita

Pythonで学ぶ 基礎からのプログラミング入門 (34) Pythonのテスト手法 | マイナビニュース

ユニットテスト

# @raises 例外のテスト
@raises(Error)
def test_invalid_arg():
    actual = add(None, 1)

参考:

Python nose でユニットテストを書いてみた / 桃缶食べたい。

rospyのお勉強

rospy.get_param

  • パラメータサーバーから値をとってくる
rospy.get_param(param_name, default)

参考: rospy/Overview/Parameter Server - ROS Wiki

rospy.Time

  • rate,sleep,durationなどよく使うものが多い
  • rospy.Timer(period, callback, oneshot=False)
    • periodで指定した周期で定期的にcallbackが呼ばれる

参考:

rospy/Overview/Time - ROS Wiki

rospy.wait_for_message(topic, topic_type, timeout)

  • トピックを一回だけサブする関数

rospy.ROSInterruptException

  • KeyboardInterruptなどInterruptの操作のException

pythonモジュールとパッケージ

参考:

Python: モジュールとパッケージ - CUBE SUGAR CONTAINER

Python: モジュールとパッケージ - CUBE SUGAR CONTAINER

Python/setup.pyによるインストール - Glamenv-Septzen.net

Python入門 - パッケージとモジュール

[http://samurait.hatenablog.com/entry/init.py%E3%81%AE%E5%BD%B9%E5%89%B2:title]

Pythonのお勉強(1)

with文

コンテキストマネージャとして利用して、動作を制御することができる。(enterメソッド、exitメソッド)

以下の場合、writeメソッドの後、必ずファイルrobot.txtが閉じられる。

with open('robot.txt', 'r', encoding = 'utf8') as f:
    all_text = f.read()
    print(all_text) 

以下のように書くと、エラー処理などでファイルが閉じられない可能性があるため、上記のように書くことを推奨。

f = open('robot.txt', 'r', encoding = 'utf8')
for line in f:
    print(line , end='')
f.close()

ラムダ式

lambda 引数:命令

is_even = lambda x: x % 2 == 0
is_even(2)
-> True

引数

可変引数
  • *のときタプルになる
>>> def func(*hoge):
...   print hoge
...
>>> func("1","2","3")
('1', '2', '3')
  • **のとき辞書型になる
>>> def func(**hoge):
...   print hoge
...
>>> func(a=1,b=2,c=3)
{'a': 1, 'c': 3, 'b': 2}
>>>

イテレータ

参考;

10.1. itertools — 効率的なループ実行のためのイテレータ生成関数 — Python 3.6.5 ドキュメント

ジェネレータ

イテレータの一種

def robot_num(m):
    print("%d robot num" % m)
    while m < 5:
        if m == 4:
            print("m=4")
        yield m
        m += 1
    print("stop product robot")
    return

実行結果

In [2]: cnt = robot_num(2)

In [3]: next(cnt)
2 robot num
Out[3]: 2

In [4]: next(cnt)
Out[4]: 3

In [5]: next(cnt)
m=4
Out[5]: 4

In [6]: next(cnt)
stop product robot
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-6-77509b5792d1> in <module>()
----> 1 next(cnt)

StopIteration: 

デコレータ

関数やクラスの機能変更、拡張を行うための機構

@robot_log
def calc_add(robot1,robot2):
    return robot1+robot2

は以下と同じ

def calc_add(robot1,robot2):
    return robot1+robot2
calc_add = robot_log(calc_add)

ここで、robot_logを正しく作ると、この関数を呼ぶとlogをとるような機能を追加できる

スコープ

参考:RubyプログラマがPythonを学び始めて知ったこと10選 ④ - donghai821の日記

Pythonのクラスメンバのスコープまとめ - Qiita

Pythonメモ: privateやprotectedは

pythonにprivate変数がない件:千里の道も一歩から:So-netブログ

Python - Pythonの変数やメソッドの命名について(アンダーバー)(41277)|teratail

クロージャ

参考:Python Tips: Python でクロージャを使いたい - Life with Python

クラス継承

参考:

[Python]クラス継承(super) - Qiita

Python の super() 関数の使い方 - Life with Python

クラス属性とインスタンス属性

大事な考え方。要復習。クラスと名前空間と関連付けて整理しよう。

参考:Pythonでのクラス属性とインスタンス属性 - gogochephy’s diary

スタティックメソッド

以下のようにインスタンスに作用しないメソッド

class robot_calc(object):
    @staticmethod
    def robot_add(x,y): #selfがない
        return x+y

a = robot_calc.robot_add(1,2)

クラスメソッド

インスタンスではなくクラスそのものに作用するメソッド

class RoboVar:
    ene = 1

    @classmethod
    def full(cls, power):
        return cls.ene * power

class FullPower(RoboVar):
    ene = 2

x = FullPower.full(3) # RoboVar.full(FullPower,3) 

プライベートメンバ

  1. アンダースコアひとつ _robot → クラス内部だけで利用する
  2. アンダースコアふたつ __robot → クラス外部から参照できなくなる(厳密には_<クラス名>__<属性名>とすればアクセスできるがより安全)

Linuxコマンドを呼ぶ

#!/usr/bin/env python

import subprocess

p_test = subprocess.check_call(['ls','-a'])

参考: pythonでLinuxコマンドを実行する - ihit's diary

入出力

csv
import csv

with open('robodata.csv','r',encoding='utf8') as f:
    robodata = [k for k in csv.reader(f)]

with open('roboout.csv','w',newline='') as f:
    writer = csv.writer(f)
    writer.writerows(robodata)
pickle

以下のようにすることでオブジェクトの保存ができる

import pickle

mydata = [1,2,3]

with open('pickle_test.pickle','wb') as f: #バイナリ形式で開くこと
    pickle.dump(mydata,f)

with open('pickle_test.pickle','rb') as f:
    pic_data = pickle.load(f)

複数のオブジェクトを保存するときは以下のように記述

import pickle
import numpy as np

robo1 = np.float(28.0)
robo2 = np.array([[1.0,2.0],[3.0,4.0]])
robo3 = {'tetsujin':21, 'atom':'1000000'}

with open('pickle_test2.pickle','wb') as f: #バイナリ形式で開くこと
    pickle.dump([robo1,robo2,robo3],f)

with open('pickle_test2.pickle','rb') as f:
    [r1,r2,r3] = pickle.load(f)

print(r3['tetsujin'])

ソケット通信

  • connect : サーバーに接続
  • send : データ送信
  • sendall : すべて送りきるまで終わらない
  • settimeout : タイムアウト値を設定する
  • recv : データ受信
  • socket.timeout : exception OSErrorのサブクラス
  • socket.error : exception OSErrorの非推奨のエイリアス

参考:

スレッド

参考:

その他

  • struct.pack("i",data) : int型でdataをパックする
  • unicode() : unicodeへ変換
  • .split() : 単語を分割してリストへ
  • ~[-1] : リストの最後のもの
  • ~[0:-1] : リストの最初から最後から一つ手前まで
  • .append : リストの最後に追加
  • time.sleep : ~s待つ
  • .first()
  • .join() : 連結して一つの文字にする
  • .items() : 辞書をリストに変換
  • continue : 現在のループを終了して次のループへ

参考:

スクリプト言語コンパイル言語の違い

参考:

lint

*args **kwargs

glob

rosテストのお勉強(python)

unittest

CMakeLists.txt

catkin_python_setup()

if(CATKIN_ENABLE_TESTING)
  find_package(rostest REQUIRED)
  add_rostest(test/mytest.test)
endif()

package.xml

<test_depend>rostest</test_depend>
<test_depend>python-nose</test_depend>

↑テストにnoseをつかう場合

mytest.test

<launch>
  <node pkg="mypkg" type="mynode" name="mynode" />
  <test test-name="test_mynode" pkg="mypkg" type="test_mynode.py" />
</launch>

test_mynode.py

#!/usr/bin/env python

from nose.tools import eq_, raises

import unittest
import rostest, rospy

from mypkg.mytest import MyTest


class TestMyTest(unittest.TestCase):
    def setUp(self):
        pass

    @raises(rospy.ROSException)
    def test_mytest1(self):
        instance = MyTest()
        
    def test_mytest2(self):
        eq_(10 / 5, 2)

if __name__ == '__main__':
    rospy.init_node('test_sample')
    rostest.rosrun('mypkg',
                   'test_sample',
                   TestMyTest)

実行

$rostest test_rospy/test/mytest.test

setup.py

#!/usr/bin/env python

from distutils.core import setup

from catkin_pkg.python_setup import generate_distutils_setup

setup_args = generate_distutils_setup(
    packages=['rostest'],
    package_dir={'': 'src'},
)

setup(**setup_args)

rostest-test.test

<launch>
  <test test-name="test_rostest" pkg="rostest" type="test_rostest.py" />
</launch>

ディレクトリ構成

/src/rostest/src/rostest/~.py

参考:

ja/rospy_tutorials/Tutorials/Makefile - ROS Wiki

2. setup スクリプトを書く — Python 3.6.5 ドキュメント

参考:

ROS勉強記録: catkinでのpythonのtestについて

ROS C++コード用テストライブラリgtestの使い方(日本語訳) - MyEnigma

rostest - ROS Wiki

rostest - Minimum Working Example - ROS Answers: Open Source Q&A Forum

nose まとめ 1 - kuma8の日記

noseで気軽にテストを書く(+geventの場合) - Qiita

Pythonのデコレータについて - Qiita

ROSにおけるUnit/Integration/Regression Test (日本語訳) - MyEnigma

「rostest」 の使い方 - うごくものづくりのために

google test のインストールでハマったのでメモ - Qiita

pythonのテストを書いてみる

unittest

Robot and Animal

roboani.py

class Robot:
    def get_name(self):
        return "robot"
    def get_legs(self):
        return 2

class Animal:
    def get_name(self):
        return "animal"
    def get_legs(self):
        return 4

def calc_roboani_legs(robot,animal,heads,legs):
    robo_l = robot.get_legs()
    ani_l = animal.get_legs()
    robot_name = robot.get_name()
    animal_name = animal.get_name()
    animal_num = (legs - (robo_l*heads)) // (ani_l-robo_l)
    robot_num = heads - animal_num
    print "------"
    print "head",heads,"leg",legs
    print robot_num,animal_num
    return robot_num,animal_num

if __name__ == '__main__':
    calc_roboani_legs(Robot(),Animal(),heads=10,legs=28)

Test

test_roboani.py

import unittest, roboani

class TestRobotAnimal(unittest.TestCase):

    def setUp(self):
        self.robot = roboani.Robot()
        self.animal = roboani.Animal()

    def tearDown(self):
        pass

    def test_legs(self):
        self.assertEqual(self.robot.get_legs(),2,"leg num")
        self.assertEqual(self.animal.get_legs(),4,"leg num")

    
    def test_roboani(self):
        robot, animal = roboani.calc_roboani_legs(
            self.robot,self.animal,
            heads = 10, legs = 28)

        self.assertEqual((robot,animal) ,(6,4), "robot animal calc")

テスト実行

$ python -m unittest test_roboani

結果

.------
head 10 leg 28
6 4
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

mocktest

RobotDataBaseConnect

# -*- coding:utf-8 -*-

import unittest
from mock import Mock, patch

class DataBaseConnect(object):
    def __init__(self):
        pass

    def find(self, param):
        print('find ' + param)

class CalculationMovement(object):
    def __init__(self, data):
        self.__dataget = data

    def execute(self):
        return self.__dataget.find('param')

calc = CalculationMovement(DataBaseConnect())
calc.execute()

# ---------------- mock test --------------------------#

class CalculationModelTestCase(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def test_execute_1(self):
        mock_test = Mock()  
        mock_test.find.return_value = 'mock_test'
        test_obj = CalculationMovement(mock_test)
        self.assertEqual('mock_test', test_obj.execute())
        self.assertEqual(mock_test.find.call_count, 1)
        self.assertEqual(list(mock_test.find.call_args_list[0]),
                         [('param',),{}])

    def test_execute_2(self):
        DataBaseConnect.find = Mock(return_value='mock_test')
        mock_test = DataBaseConnect()
        test_obj = CalculationMovement(mock_test)
        self.assertEqual('mock_test', test_obj.execute())

    def test_execute_3(self):
        with patch('__main__.DataBaseConnect') as mock_test:
            mock_test.find.return_value = 'with_mock_test'
            test_obj = CalculationMovement(mock_test)
            self.assertEqual('with_mock_test', test_obj.execute())

unittest.main()

結果

find param
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s

OK

参考:

PythonでMockを使ったユニットテスト - 工作とかプログラミングとか

python - How to unittest that a thread is spawned? - Stack Overflow

サンプルでみる Python の Mock ライブラリ Mox の使い方 | CUBE SUGAR STORAGE

EasyMockでSocketクラスのモックを作る - Yのはてな

Pythonのテストコードでmockを使ってみた | DevelopersIO

Pythonのコードをきれいに書くためのTips | TRIVIAL TECHNOLOGIES 4 @ats のイクメン日記

http://momijiame.tumblr.com/post/22379282563/モックオブジェクトフレームワーク-pymox-を使って-python-のテストを書く

標準ライブラリのunittestで例外の発生を確認するテスト - podhmoの日記

unit testing - How do you test that a Python function throws an exception? - Stack Overflow