Windows Application DriverでWindowsアプリケーションのテストを自動化する～Python編

# これまでのあらすじ
@ootakenさんの[Windows Application DriverでWindowsアプリケーションのテストを自動化しよう](http://qiita.com/ootaken/items/33099fcbd730e775e4db)を見て、なかなか面白そうだったので、Pythonでもやってみようと思ったのです。
それだけだとつまらないので、[RobotFramework](http://robotframework.org)の[ApplumLibrary](https://github.com/serhatbolsu/robotframework-appiumlibrary)を使ったキーワード駆動によるテストケースも試してみようと思いついたのでした。

RobotFrameworkって何？という方は、[以前書いた記事](http://qiita.com/totutiteta/items/e7b99a76231dbfb0aaf1)を参照してください。
前回の記事（2年以上前だけど）ではSelenium2のキーワードライブラリを使用しましたが、今回はAppium用のライブラリを使います。

# 準備

まず、Windows Application Driverを以下のURLからダウンロードしてインストールします。
 https://github.com/Microsoft/WinAppDriver/releases

また、Pythonのサンプルファイルもダウンロードしておきます。
　https://github.com/Microsoft/WinAppDriver/blob/master/Samples/Python/calculatortest.py

次に、関係するPythonのライブラリをインストールします。
robotframework-appiumlibraryをインストールすれば、一式まとめて入ります。

```
> pip install robotframework-appiumlibrary
```

ちなみに確認環境は、Windows10 Home Version 1703 の64bit版＋Python3.6.0です。
ライブラリのバージョンは以下になります。

```
>pip freeze                   
apipkg==1.4                                         
Appium-Python-Client==0.24                          
colorama==0.3.7                                     
coverage==4.3.4                                     
decorator==4.0.11                                   
docutils==0.13.1                                    
execnet==1.4.1                                      
mock==1.0.1                                         
py==1.4.33                                          
pytest==3.0.7                                       
pytest-cov==2.4.0                                   
pytest-pythonpath==0.7.1                            
pytest-xdist==1.15.0                                
robotframework==3.0.2                               
robotframework-appiumlibrary==1.4.3                 
sauceclient==0.2.1                                  
selenium==3.3.3                                     ```
```

# サンプルの実行
本家のPythonサンプルを実行してみますが、[Windows Application DriverでWindowsアプリケーションのテストを自動化しよう](http://qiita.com/ootaken/items/33099fcbd730e775e4db)でのJavaのサンプル同様、日本語環境ではFailするので、Locatorの指定方法とAssertの判定方法を変更します。
python のunittestにはassertThatはないので、asserInを使いました。引数の順番が反転しているので注意。

```py:calculator.py
"""
//******************************************************************************
//
// Copyright (c) 2016 Microsoft Corporation. All rights reserved.
//
// This code is licensed under the MIT License (MIT).
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//******************************************************************************
"""

import unittest
from appium import webdriver

class SimpleCalculatorTests(unittest.TestCase):

	def setUp(self):
		#set up appium
		desired_caps = {}
		desired_caps["app"] = "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App"
		self.driver = webdriver.Remote(
			command_executor='http://127.0.0.1:4723',
			desired_capabilities= desired_caps)

	def tearDown(self):
		self.driver.quit()

	def test_initialize(self):
		self.driver.find_element_by_accessibility_id("clearButton").click()
		self.driver.find_element_by_accessibility_id("num7Button").click()

		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn(" 7 ", str(result.text))

	def test_addition(self):
		self.driver.find_element_by_accessibility_id("num1Button").click()
		self.driver.find_element_by_accessibility_id("plusButton").click()
		self.driver.find_element_by_accessibility_id("num7Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()

		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn( " 8 ", str(result.text))

	def test_combination(self):
		self.driver.find_element_by_accessibility_id("num7Button").click()
		self.driver.find_element_by_accessibility_id("multiplyButton").click()
		self.driver.find_element_by_accessibility_id("num9Button").click()
		self.driver.find_element_by_accessibility_id("plusButton").click()
		self.driver.find_element_by_accessibility_id("num1Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()
		self.driver.find_element_by_accessibility_id("divideButton").click()
		self.driver.find_element_by_accessibility_id("num8Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()

		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn( " 8 ", str(result.text))

	def test_division(self):
		self.driver.find_element_by_accessibility_id("num8Button").click()
		self.driver.find_element_by_accessibility_id("num8Button").click()
		self.driver.find_element_by_accessibility_id("divideButton").click()
		self.driver.find_element_by_accessibility_id("num1Button").click()
		self.driver.find_element_by_accessibility_id("num1Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()
		
		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn( " 8 ", str(result.text))

	def test_multiplication(self):
		self.driver.find_element_by_accessibility_id("num9Button").click()
		self.driver.find_element_by_accessibility_id("multiplyButton").click()
		self.driver.find_element_by_accessibility_id("num9Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()

		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn( " 81 ", str(result.text))

	def test_subtraction(self):
		self.driver.find_element_by_accessibility_id("num9Button").click()
		self.driver.find_element_by_accessibility_id("minusButton").click()
		self.driver.find_element_by_accessibility_id("num1Button").click()
		self.driver.find_element_by_accessibility_id("equalButton").click()

		result = self.driver.find_element_by_accessibility_id("CalculatorResults")
		self.assertIn( " 8 ", str(result.text))



if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(SimpleCalculatorTests)
    unittest.TextTestRunner(verbosity=2).run(suite)
```
Window Application Driverを起動してから、テストを実行してみます。

```
>python calculatortest.py
test_addition (__main__.SimpleCalculatorTests) ... ok
test_combination (__main__.SimpleCalculatorTests) ... ok
test_division (__main__.SimpleCalculatorTests) ... ok
test_initialize (__main__.SimpleCalculatorTests) ... ok
test_multiplication (__main__.SimpleCalculatorTests) ... ok
test_subtraction (__main__.SimpleCalculatorTests) ... ok

----------------------------------------------------------------------
Ran 6 tests in 11.818s

OK
```
全部Passしました。


# キーワード駆動テスト化してみる
次は同じテストをRobotFrameworkで書いて実行してみます。
AppiumLibraryに用意されているキーワードをそのまま使えます。
使えるキーワードのドキュメントはこちら。
　http://serhatbolsu.github.io/robotframework-appiumlibrary/AppiumLibrary.html

各テストケースについてはほとんど説明いらないかと思います。
数字を${SPACE}で囲んでいるところがいまいち感ありますが、そこを除けば、非常に直感的に書けると思います。
~~Test Setupの、Open Applicationの引数にplatformNameを指定しないと、Click Elementでエラーになってしまったため、明示しています。注意点はそこくらいでしょうか。~~

(2017/04/22訂正）
http://appium.io/slate/en/master/?python#create-test-project
に、Windows App Automation Sessionのdesired capabilitiesに関して、以下の記載があったので、コードを修正しました。

```
STARTING A SESSION

Note that you should additionally use these capabilities to ensure you are getting a Windows App automation session:

platformName: Windows deviceName: WindowsPC
```
公式サンプルは従ってないけどね。。。

```:calculator.robot
*** Settings ***
Library  AppiumLibrary
Test Setup  Open Application  http://127.0.0.1:4723  app=Microsoft.WindowsCalculator_8wekyb3d8bbwe!App  platformName=Windows  deviceName=WindocwPC
Test Teardown  Close Application

*** Test Cases ***
Test Initialization
    Click Element  accessibility_id=clearButton
    Click Element  accessibility_id=num7Button
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}7${SPACE}

Test Addition
    Click Element  accessibility_id=num1Button
    Click Element  accessibility_id=plusButton
    Click Element  accessibility_id=num7Button
    Click Element  accessibility_id=equalButton
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}8${SPACE}

Test Combination
    Click Element  accessibility_id=num7Button
    Click Element  accessibility_id=multiplyButton
    Click Element  accessibility_id=num9Button
    Click Element  accessibility_id=plusButton
    Click Element  accessibility_id=num1Button
    Click Element  accessibility_id=equalButton
    Click Element  accessibility_id=divideButton
    Click Element  accessibility_id=num8Button
    Click Element  accessibility_id=equalButton
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}8${SPACE}

Test Division
    Click Element  accessibility_id=num8Button
    Click Element  accessibility_id=num8Button
    Click Element  accessibility_id=divideButton
    Click Element  accessibility_id=num1Button
    Click Element  accessibility_id=num1Button
    Click Element  accessibility_id=equalButton
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}8${SPACE}

Test Multiplication
    Click Element  accessibility_id=num9Button
    Click Element  accessibility_id=multiplyButton
    Click Element  accessibility_id=num9Button
    Click Element  accessibility_id=equalButton
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}81${SPACE}

Test Substraction
    Click Element  accessibility_id=num9Button
    Click Element  accessibility_id=minusButton
    Click Element  accessibility_id=num1Button
    Click Element  accessibility_id=equalButton
    Element Should Contain Text  accessibility_id=CalculatorResults  ${SPACE}8${SPACE}
```
実行してみましょう。全部Passです。

```
>robot -d result calculatortest.robot
==============================================================================
Calculatortest
==============================================================================
Test Initialization                                                   | PASS |
------------------------------------------------------------------------------
Test Addition                                                         | PASS |
------------------------------------------------------------------------------
Test Combination                                                      | PASS |
------------------------------------------------------------------------------
Test Division                                                         | PASS |
------------------------------------------------------------------------------
Test Multiplication                                                   | PASS |
------------------------------------------------------------------------------
Test Substraction                                                     | PASS |
------------------------------------------------------------------------------
Calculatortest                                                        | PASS |
6 critical tests, 6 passed, 0 failed
6 tests total, 6 passed, 0 failed
==============================================================================
Output:  D:\Dev\rf_home\result\output.xml
Log:     D:\Dev\rf_home\result\log.html
Report:  D:\Dev\rf_home\result\report.html
```

# まとめ
* WIndows Application DriverとPythonのAppium関連ライブラリのインストール
* 本家サイトのPythonサンプルテストケース（を修正したもの）の実行
* サンプルテストケースをRobotFrameworkのAppiumLibraryを使ってキーワード駆動型のテストケースに書き直して実行

を試した結果、Windows Application Driverにより、Windowsのデスクトップアプリケーションのテスト自動化がかなり簡単に対応できることがわかりました（行儀よくIDがふられたアプリの単純なボタン操作とかなら、ね）。

いやはや、Windows Application Driverの今後が非常に楽しみですね！