Автоматическое тестирование MS Calc на Python

В данной статье описывается процесс создания автоматического теста простого GUI приложения на примере Microsoft Calc. Целью статьи является описание базовых этапов автоматизации для библиотеки pywinauto языка Python. Статью следует рассматривать как обучающий материал, как пример для автоматизации тестирования Вашего собственного приложения. Условия заведомо упрощены.

Цель теста: проверить правильность сложения и умножения в приложении MS Calc для диапазона целых чисел от -10 до 10.

Платформа: Windows Seven Ultimate 64bit.

Инструменты: Python 2.7 - 32bit, pywinauto 0.4.0, SWAPY 0.4.1

Обозначим основные этапы:

  1. Создания кода для автоматических действий.
  2. Написание тела теста с основной проверкой.
  3. Отладка теста.

1. Запись кода автоматических действий

На этом этапе следует определиться что именно нам придется делать с графическим интерфейсом тестируемого приложения и получить рабочий код для этих действий.

Разобьем на под-этапы:

  • запуск приложения
  • нажатие на кнопки
  • чтение результата

Запуск приложения выполним так, как показано в примере к pywinauto:

app = pywinauto.application.Application()
app.Start_('calc')
window = app.top_window_()

В нашем тесте окно закрываться не будет, поэтому можем ограничиться этим кодом и смело использовать переменную window в последующих действиях.

Для поиска и генерации кода нажатия кнопки, воспользуемся утилитой SWAPY.

К сожалению в Windows 7 pywinauto не видит названий кнопок в Calc, вследствие чего SWAPY называет все кнопки Unknown control name. В этом случае, нам опытным путем придется найти соответствие кнопок и ControlID, благо SWAPY подсвечивает выделенный контрол на окне.

Создадим вспомагательный словарь:

BUTTONS = {'MC': 122, 'MR': 123, 'MS': 124, 'M+': 125, 'M-' : 126,\
           '<' : 83,  'CE': 82,  'C' : 81,  '+-': 80,  'sqr': 110,\
           '7' : 137, '8' : 138, '9' : 139, '/' : 91,  '%'  : 118,\
           '4' : 134, '5' : 135, '6' : 136, '*' : 92,  '1/x': 114,\
           '1' : 131, '2' : 132, '3' : 133, '-' : 94,  '='  : 121,\
           '0' : 130, ',' : 84,  '+' : 93}

Клик на контрол (по ControlID) выглядит следующим образом:

c_handle = pywinauto.findwindows.find_window(parent=window.handle, control_id=BUTTONS['7'], top_level_only=False)
control = app.window_(handle=c_handle)
control.Click()

В дальнейшем вынесем это в функцию Click().

Проверять результат вычислений будем чтением текста из статика:

result = window['Static4'].Texts()[0]

Еще нам понадобиться вспомагательная функция, которая будет переводить число в последовательность кнопок, которую необходимо нажать, чтобы написать это число. Это не должно быть сложно...

def type_number(num):
    str_num = str(num)
    map(Click, list(str_num))

2. Код теста

Основой теста станут циклы по всем тестируемым математическим операциям и для всех чисел, указанных в задании. Выполнение теста будет прервано при первом несоответствии результатов вычислений (в Python и Калькуляторе).

...
```
for op_key, op_funk in OPERATIONS.items():
    for a in range(*RANGE):
        for b in range(*RANGE):
            #clear all
            Click('C')
        #enter a
        type_number(a)
        
        #enter operation
        Click(op_key)
        
        #enter b
        type_number(b)
        
        #compute
        Click('=')
        
        #read result
        result = window['Static4'].Texts()[0]
        
        #check result
        if int(result) == op_funk(a, b):
            print REPORT %(a, op_key, b, result, 'OK')
        else:
            print REPORT %(a, op_key, b, result, 'ERROR')
            exit(1)
</pre>
<p>Первый же запуск теста на ограниченном диапазоне выявил ошибку:</p>
<pre>...
1+-1=0 - OK
1+0=1 - OK
1+1=2 - OK
-1*-1=-2 - ERROR</pre>
<h3 style="text-align: center;">&nbsp;</h3><h2 style="text-align: center; ">3. Отладка теста</h2><p>Оказывается, мы неправильно вводим отрицательные числа:</p><p><span style="font-size:12px;"><code>type_number(-1)</code></span> - фактически нажмет на следующие кнопки калькулятора<span style="font-family: monospace;"> </span>"-" и "1". Что неверно, так как для ввода отрицательных чисел предусмотрена другая кнопка - "+-", причем нажимать её следует после ввода цифры.</p><p>Немедленно исправляемся:</p>
<pre class="brush: python;">

def type_number(num):
‘’’
click all digit of a number
‘’’
is_negative = num < 0
str_num = str(num)
if is_negative:
map(Click, list(str_num[1:]))
Click(‘±’)
else:
map(Click, list(str_num))

</pre>
<p>Привожу полный код теста:</p>
<pre class="brush: python;">

import pywinauto
import operator

#key codes
BUTTONS = {‘MC’: 122, ‘MR’: 123, ‘MS’: 124, ‘M+’: 125, ‘M-’ : 126,
‘<’ : 83, ‘CE’: 82, ‘C’ : 81, ‘±’: 80, ‘sqr’: 110,
‘7’ : 137, ‘8’ : 138, ‘9’ : 139, ‘/’ : 91, ‘%’ : 118,
‘4’ : 134, ‘5’ : 135, ‘6’ : 136, ‘*’ : 92, ‘1/x’: 114,
‘1’ : 131, ‘2’ : 132, ‘3’ : 133, ‘-’ : 94, ‘=’ : 121,
‘0’ : 130, ‘,’ : 84, ‘+’ : 93}

#math operations
OPERATIONS = {‘+’ : operator.add,
‘*’ : operator.mul,}

#num ranges
RANGE = (-10, 10 +1)

#report format
REPORT = ‘%s%s%s=%s - %s’

def Click(key):
‘’’
Make a click on a key
‘’’
c_handle = pywinauto.findwindows.find_window(parent=window.handle, control_id=BUTTONS[key], top_level_only=False)
control = app.window_(handle=c_handle)
control.Click()

def type_number(num):
‘’’
click all digit of a number
‘’’
is_negative = num < 0
str_num = str(num)
if is_negative:
map(Click, list(str_num[1:]))
Click(‘±’)
else:
map(Click, list(str_num))

#start ms calc
app = pywinauto.application.Application()
app.Start_(‘calc’)
window = app.top_window_()

for op_key, op_funk in OPERATIONS.items():
for a in range(*RANGE):
for b in range(*RANGE):
#clear all
Click(‘C’)

        #enter a
        type_number(a)
        
        #enter operation
        Click(op_key)
        
        #enter b
        type_number(b)
        
        #compute
        Click('=')
        
        #read result
        result = window['Static4'].Texts()[0]
        
        #check result
        if int(result) == op_funk(a, b):
            print REPORT %(a, op_key, b, result, 'OK')
        else:
            print REPORT %(a, op_key, b, result, 'ERROR')
            exit(1)
</pre>
<p><a href="http://code.google.com/p/swapy/"></a></p><p><a href="http://code.google.com/p/pywinauto/"></a></p>

Статья хорошая для начинающих, даже отличная. Но для себя в ней нашел решение проблеммы - шаблон REPORT задали вначале, а потом использовали подстановку. Я в свом коде этого не сделал, ругалось почему-то, но теперь знаю что это возможно. Так что теперь свой сорс автотеста уменьшу раз эдак в 10. Спасибо автору.

1 лайк