pyATSの初歩

#pyATSとは
pyATS は、Python Automated Test Systems の略で、python3 ベースのテストフレームワークです。Cisco によって開発されています。license は、Apache 2.0 です。

Document などの情報は、以下のリンクにあります。
[Cisco DevNet: pyATS](https://developer.cisco.com/site/pyats/)

pytest のように、pythonプログラムのテストに使用できますが、Device object や ConnectionManager object という router 等の network device のテストをするための機能も含まれています。
この記事では、とりあえず pyATS の基本機能を動作させてみただけで、Device object などは使っていません。
pyATS で Cisco IOS Router にアクセスした事例は、 「[pyATSによるCisco IOS Routerのテスト例](https://qiita.com/tokatsu/items/46565062e93d01405f93)」 に記載しています。
#インストール
## 環境
* pyATS は、python 3.4 以上を必要とします。今回は、3.4.5を使いました。
* python3 の development library が必要です。今回は、python34-devel.x86_64 を使いました。
* CentOS7 上で動かしました。
* pyATS は、4.0.0 を使用しています。
なお、pyATS 4.0.0 は、Linux と Mac OS には対応していますが、Windows 系は未サポートとなっています。

##インストール方法
普通に pip でインストールできます。今回は、以下のように virtualenv で動かしています。

```
$ python3 -m virtualenv pyATS
$ cd pyATS
$ source bin/activate
$ pip install pyats
```

#テストをしてみる
##テスト対象
テスト対象として以下のようなクラスを使いました。

```python:shape.py
#!/usr/bin/env python

class Shape(object):

    def __init__(self, name):
        self.name = name
        if name == 'triangle':
            self.sides = 3
        elif name == 'rectangle':
            self.sides = 4
        elif name == 'pentagon':
            self.sides = 6
        else:
            self.sides = 0
```
##テストスクリプトの構造
テストスクリプトは、ats.aetest モジュールの Class を使って記述します。
pyATS では、スクリプトは以下のような構造になっています。
* 0または1個の CommonSetup Class
* 1個以上の Testcase Class
* 0または1個の CommonCleanup Class

CommonSetup と CommonCleanup は、複数の subsection を method として持ちます。

Testcase は更に以下の sub section に分かれます。
* 0または1個の setup method
* 1個以上の test method
* 0または1個の cleanup method

それぞれ、`@setup, @test, @cleanup` decoratorを使って修飾することで、その subsection と aetest に認識されます。

pyATS を実行すると最初に CommonSetup が実行され、その次に Testcase が記述された順番で実行されます。最後に、CommonCleanup が実行されます。
ats.aetest は、各セクション毎に、実行結果をレポートする機能ももっています。

##テストスクリプトの例
Shape class をテストするテストケースを書いてみます。
Testcase classのみを使います。
name が triangle, rectangle, pentagon の Shape object を作って、sides の値が正しいかをcheckします。
pentagon の時には、間違った sides が設定されるので、そのケースは fail になるはずです。

```python:simple_test.py
from ats import aetest
from shape import Shape

# test section within Testcases
class Testcase(aetest.Testcase):

    # define test section by applying @test decorator
    @aetest.test
    def testcase_one(self):
        shape = Shape('triangle')
        assert(shape.sides == 3)

    @aetest.test
    def testcase_two(self):
        shape = Shape('rectangle')
        assert(shape.sides == 4)

    @aetest.test
    def testcase_three(self):
        shape = Shape('pentagon')
        assert(shape.sides == 5)
```

##実行方法
前記のテストスクリプトの最後で、ats.aetest.main() を実行すれば、テストを実行できます。今回は、以下のような wrapper を作成して、任意のスクリプトを実行できるようにしました。

```python:run_test.py
#!/usr/bin/env python
from ats.aetest import main
import sys

testscript = sys.argv[1]
main(testable = testscript)
```

以下のようにして、simple_test.py を実行します。

```
$ ./run_test.py simple_test.py
```

##実行結果
結果は、以下のようになります。
testcase_three で fail が発生しています。

```
2017-12-06T23:53:14: %AETEST-INFO: Starting testcase Testcase
./run_test.py:6: DeprecationWarning: Starting v3.0.0, section.id is deprecated and replaced by section.uid. Please modify your scripts. This will be removed next release
  main(testable = testscript)
2017-12-06T23:53:14: %AETEST-INFO: Starting section testcase_one
2017-12-06T23:53:14: %AETEST-INFO: The result of section testcase_one is => PASSED
2017-12-06T23:53:14: %AETEST-INFO: Starting section testcase_two
2017-12-06T23:53:14: %AETEST-INFO: The result of section testcase_two is => PASSED
2017-12-06T23:53:14: %AETEST-INFO: Starting section testcase_three
2017-12-06T23:53:14: %AETEST-WARNING: An assertion failure was caught:
2017-12-06T23:53:14: %AETEST-WARNING: Traceback (most recent call last):
2017-12-06T23:53:14: %AETEST-WARNING:   File "/home/tokatsu/pyATS/myCases/simple_case/simple_test.py", line 21, in testcase_three
2017-12-06T23:53:14: %AETEST-WARNING:     assert(shape.sides == 5)
2017-12-06T23:53:14: %AETEST-WARNING: AssertionError
2017-12-06T23:53:14: %AETEST-INFO: The result of section testcase_three is => FAILED
2017-12-06T23:53:14: %AETEST-INFO: The result of testcase Testcase is => FAILED
2017-12-06T23:53:14: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-06T23:53:14: %AETEST-INFO: |                               Detailed Results                               |
2017-12-06T23:53:14: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-06T23:53:14: %AETEST-INFO:  SECTIONS/TESTCASES                                                      RESULT
2017-12-06T23:53:14: %AETEST-INFO: --------------------------------------------------------------------------------
2017-12-06T23:53:14: %AETEST-INFO: .
2017-12-06T23:53:14: %AETEST-INFO: `-- Testcase                                                             FAILED
2017-12-06T23:53:14: %AETEST-INFO:     |-- testcase_one                                                     PASSED
2017-12-06T23:53:14: %AETEST-INFO:     |-- testcase_two                                                     PASSED
2017-12-06T23:53:14: %AETEST-INFO:     `-- testcase_three                                                   FAILED
2017-12-06T23:53:14: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-06T23:53:14: %AETEST-INFO: |                                   Summary                                    |
2017-12-06T23:53:14: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-06T23:53:14: %AETEST-INFO:  Number of ABORTED                                                            0
2017-12-06T23:53:14: %AETEST-INFO:  Number of BLOCKED                                                            0
2017-12-06T23:53:14: %AETEST-INFO:  Number of ERRORED                                                            0
2017-12-06T23:53:14: %AETEST-INFO:  Number of FAILED                                                             1
2017-12-06T23:53:14: %AETEST-INFO:  Number of PASSED                                                             0
2017-12-06T23:53:14: %AETEST-INFO:  Number of PASSX                                                              0
2017-12-06T23:53:14: %AETEST-INFO:  Number of SKIPPED                                                            0
2017-12-06T23:53:14: %AETEST-INFO: --------------------------------------------------------------------------------
```

#Loopを使ってみる
同じようなテストケースを3回書くのは冗長なので、pyATS の loop 機能を使って前記のスクリプトを書き直してみました。
##Loop を使った Testcase
Loop を実行するには、`@loop` decorator を使います。`@loop` の中で指定した loop parameter を順に使って、loop を実行します。

```python:simple_test_loop.py
from ats import aetest
from shape import Shape

# test section within Testcases
class Testcase(aetest.Testcase):

    # define test section by applying @test decorator
    @aetest.loop(name = ['triangle', 'rectangle', 'pentagon'], sides = [3, 4, 5])
    @aetest.test
    def testcase_one(self, name, sides):
        shape = Shape(name)
        assert(shape.sides == sides)
```

##実行結果
test methond の名前以外は、simple_test.py と同じ結果になります。
method の名前は、元の名前に loop parameter の値を連結したものになります。

```
2017-12-07T00:00:33: %AETEST-INFO: Starting testcase Testcase
./run_test.py:6: DeprecationWarning: Starting v3.0.0, section.id is deprecated and replaced by section.uid. Please modify your scripts. This will be removed next release
  main(testable = testscript)
2017-12-07T00:00:33: %AETEST-INFO: Starting section testcase_one[name=triangle,sides=3]
2017-12-07T00:00:33: %AETEST-INFO: The result of section testcase_one[name=triangle,sides=3] is => PASSED
2017-12-07T00:00:33: %AETEST-INFO: Starting section testcase_one[name=rectangle,sides=4]
2017-12-07T00:00:33: %AETEST-INFO: The result of section testcase_one[name=rectangle,sides=4] is => PASSED
2017-12-07T00:00:33: %AETEST-INFO: Starting section testcase_one[name=pentagon,sides=5]
2017-12-07T00:00:33: %AETEST-WARNING: An assertion failure was caught:
2017-12-07T00:00:33: %AETEST-WARNING: Traceback (most recent call last):
2017-12-07T00:00:33: %AETEST-WARNING:   File "/home/tokatsu/pyATS/myCases/simple_case/simple_test_loop.py", line 12, in testcase_one
2017-12-07T00:00:33: %AETEST-WARNING:     assert(shape.sides == sides)
2017-12-07T00:00:33: %AETEST-WARNING: AssertionError
2017-12-07T00:00:33: %AETEST-INFO: The result of section testcase_one[name=pentagon,sides=5] is => FAILED
2017-12-07T00:00:33: %AETEST-INFO: The result of testcase Testcase is => FAILED
2017-12-07T00:00:33: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-07T00:00:33: %AETEST-INFO: |                               Detailed Results                               |
2017-12-07T00:00:33: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-07T00:00:33: %AETEST-INFO:  SECTIONS/TESTCASES                                                      RESULT
2017-12-07T00:00:33: %AETEST-INFO: --------------------------------------------------------------------------------
2017-12-07T00:00:33: %AETEST-INFO: .
2017-12-07T00:00:33: %AETEST-INFO: `-- Testcase                                                             FAILED
2017-12-07T00:00:33: %AETEST-INFO:     |-- testcase_one[name=triangle,sides=3]                              PASSED
2017-12-07T00:00:33: %AETEST-INFO:     |-- testcase_one[name=rectangle,sides=4]                             PASSED
2017-12-07T00:00:33: %AETEST-INFO:     `-- testcase_one[name=pentagon,sides=5]                              FAILED
2017-12-07T00:00:33: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-07T00:00:33: %AETEST-INFO: |                                   Summary                                    |
2017-12-07T00:00:33: %AETEST-INFO: +------------------------------------------------------------------------------+
2017-12-07T00:00:33: %AETEST-INFO:  Number of ABORTED                                                            0
2017-12-07T00:00:33: %AETEST-INFO:  Number of BLOCKED                                                            0
2017-12-07T00:00:33: %AETEST-INFO:  Number of ERRORED                                                            0
2017-12-07T00:00:33: %AETEST-INFO:  Number of FAILED                                                             1
2017-12-07T00:00:33: %AETEST-INFO:  Number of PASSED                                                             0
2017-12-07T00:00:33: %AETEST-INFO:  Number of PASSX                                                              0
2017-12-07T00:00:33: %AETEST-INFO:  Number of SKIPPED                                                            0
2017-12-07T00:00:33: %AETEST-INFO: --------------------------------------------------------------------------------
```