Тестируемый компонент Авторизация на е

Правильно пишем тест-кейсы. памятка начинающему специалисту по тестированию

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

Для начинающих поясним, что такое тест-кейс озвучив определение из глоссария терминов ISTQB:

Тест-кейс — набор входных значений, предусловий выполнения, ожидаемых результатов и постусловий выполнения, разработанный для определённой цели или тестового условия, таких как выполнения определённого пути программы или же для проверки соответствия определённому требованию.

Определение тест-кейса языком обывателя:

Тест-кейс — это чёткое описание действий, которые необходимо выполнить, для того чтобы проверить работу программы (поля для ввода, кнопки и т.д.). Данное описание содержит: действия, которые надо выполнить до начала проверки — предусловия; действия, которые надо выполнить для проверки — шаги; описание того, что должно произойти, после выполнения действий для проверки — ожидаемый результат.

Надеюсь, теперь многим стало понятно, что такое тест-кейс. Теперь перейдём к правилам написания тест-кейсов, которые вырабатывались не один год и показывают свою эффективность до сих пор.

Обязательные атрибуты для заполнения

  • Номер тест-кейса — уникальный идентификатор тест-кейса (такие системы как TestRail, TestLink и подобные автоматически присваивают тест-кейсам уникальные номера). Если у вас тысячи тест-кейсов, то при общении с коллегами, вам будет удобнее сообщить номер тест-кейса ссылаясь на него, а не пытаться словами рассказать, где и как найти определённый тест-кейс.
  • Заголовок — краткое, понятное и ёмкое описание сути проверки.
  • Предусловия — описание действий, которые необходимо предварительно выполнить или учесть, и которые не имеют прямого отношения к проверке.
  • Шаги проверки — описание последовательности действий, которые необходимо выполнить для проверки.
  • Ожидаемый результат — проверка, которая устанавливает, что мы ожидаем получить, после выполнения определённых действий в соответствующем шаге.
Похожее:  Как работают приложения Сбербанк Онлайн: Workflow API и фрэймворки / Хабр

В зависимости от специфики компании могут присутствовать дополнительные атрибуты для заполнения: приоритет, функциональный блок, программа, ссылка на требование, номер требования и т.д.

Правила написания тест-кейсов

#1 madboy4ik

Хотелось бы узнать, а какие ещё сценарии, можно придумать для тестирование Login формы на рисунке

кроме таких что первыми идут в голову:

#2 leshal

Хотелось бы узнать, а какие ещё сценарии, можно придумать для тестирование Login формы на рисунке

кроме таких что первыми идут в голову:

#5 clauster

  • Members
  • 1 913 сообщений
    • ФИО: Худобородов Валерий
    • Город: Espoo

    Что будет если поля пустые (оба или одно из них)?

    #6 leshal

    .
    А вообщем спасибо =) некоторые мысли в голову пришли

    Вот еще на подумать.

    #7 madboy4ik

    Что будет если поля пустые (оба или одно из них)?

    #8 clauster

  • Members
  • 1 913 сообщений
    • ФИО: Худобородов Валерий
    • Город: Espoo

    Был бы я тоже гуру, я б тоже посчитал такой пост странным. Я думал что эта тема с бородой и 100% должна была б быть в этом, но к сожалению я не нашёл.

    Я гуглил по ‘тестирование Login form’, но в очередной раз убедился что по таким вопросам лучше искать в англоязычных сайтах.

    Я понимаю что случаев можно придумать очень много, но не будет же QA целый день тестировать именно login форму

    Источник

    Cypress пишем первые тесты на авторизацию

    npm install cypress --save-dev
    или yarn
    yarn add cypress --dev
    cy.intercept('http://example.com/widgets',{ fixture:'widgets.json'})
    describe(‘Test describe’, () => {
    it(‘should visit login page’, () => {
    cy.visit(‘ http://localhost:4200/')
    })
    })
    // cypress.json
    {
    “baseUrl”: “http://localhost:4200"
    }
    describe('Test describe', () => {
    it('should visit login page', () => {
    cy.visit('/login')
    cy.getEl('register').click()
    cy.url().should('include', '/register')
    })
    })
    cy.getEl(‘register’).click()
    Cypress.Commands.add('getEl', name => cy.get(`[data-cy="${name}"]`))
    describe('auth', () => {
    it('fill all the gaps', () => {
    cy.visit('/register')
    cy.getEl('firstName').type('Nikolay');
    cy.getEl('lastName').type('Kozub');
    cy.getEl('username').type('mk');
    cy.getEl('password').type('qwerty');
    })
    })
    describe('auth', () => {
    it('fill all the gaps', () => {
    cy.visit('/register')
    cy.getEl('firstName').type('Nikolay');
    cy.getEl('lastName').type('Kozub');
    cy.getEl('userName').type('mk');
    cy.getEl('password').type('qwerty');
    cy.getEl('register').click()

    cy.getEl('loader').should('be.visible')
    cy.getEl('loader').should('not.exist')
    cy.url().should('include', '/login');
    cy.getEl('alert').should('contains.class', 'alert-success');
    cy.getEl('alert').should('contain', 'successful');
    })
    })

    export const user = {
    name: 'firstName',
    lastName: 'lastName',
    userName: 'username',
    pass: 'password',
    regBtn: 'register',
    loader: 'loader'
    }

    export const alertTab = {
    alert: 'alert'
    }

    import {alertTab, user} from "../support/locators";

    describe('User', () => {
    it('fill oll the gaps', () => {
    cy.visit('/register')
    cy.getEl(user.name).type('Nikolay');
    cy.getEl(user.lastName).type('Kozub');
    cy.getEl(user.userName).type('mk');
    cy.getEl(user.pass).type('qwerty');
    cy.getEl(user.regBtn).click()

    cy.getEl(user.loader).should('be.visible')
    cy.getEl(user.loader).should('not.exist')
    cy.url().should('include', '/login');
    cy.getEl(alertTab.alert).should('contains.class', 'alert-success');
    cy.getEl(alertTab.alert).should('contain', 'successful');
    })

    import {alertTab, user, authLocators} from "../support/locators";

    describe('User', () => {
    const {loader, regBtn} = authLocators
    const {alert} = alertTab

    it('fill all the gaps', () => {
    cy.visit('/register')
    Object.values(user).forEach(el => cy.getEl(el.key).type(el.value))
    cy.getEl(regBtn).click()

    cy.getEl(loader).should('be.visible')
    cy.getEl(loader).should('not.exist')
    cy.url().should('include', '/login');
    cy.getEl(alert).should('contains.class', 'alert-success');
    cy.getEl(alert).should('contain', 'successful');
    })
    })

    {
    "baseUrl": "http://localhost:4200",
    "username": "mk",
    "password": "qwerty"
    }
    export const user = {
    firstName: 'Nikolay',
    lastName: 'Kozub',
    username: Cypress.config('username'),
    password: Cypress.config('password'),
    }
    Cypress.Commands.add('setItem', (keyName, data) => localStorage.setItem(keyName,
    JSON.stringify(data),
    )
    )
    export const loginUserData = (firstName, lastName, password, username,idUser = 1) => ([
    {
    firstName,
    lastName,
    username,
    password,
    "id": idUser
    }
    ])
    <input type="text" formControlName="username" data-cy="loginName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
    <input type=”password” data-cy=”loginPass” formControlName=”password” class=”form-control” [ngClass]=”{ ‘is-invalid’: submitted && f.password.errors }” />
    <button [disabled]="loading" class="btn btn-primary" data-cy="login">Login</button>
    <h1 data-cy="userName">Hi {{currentUser.firstName}}!</h1>
    export const authLocators = {
    regBtn: 'register',
    loader: 'loader',
    loginBtn: 'login',
    loginName: 'loginName',
    loginPass: 'loginPass'
    }
    export const homePage = {
    userName: 'userName'
    }
    import { authLocators, user, homePage } from '../support/locators'
    import { loginUserData } from '../fixtures/user'
    const { name, lastName, userName, pass } = userdescribe('login', () => {before(() => {
    cy.setItem('users', loginUserData(name.value,lastName.value,userName.value, pass.value))
    cy.visit('/login')
    })

    it('should login user page', () => {
    cy.getEl(authLocators.loginName).type(Cypress.config('username'))
    cy.getEl(authLocators.loginPass).type(Cypress.config('password'))
    cy.getEl(authLocators.loginBtn).click()
    cy.getEl(homePage.userName).should('contain.text', name.value)
    })
    })

    cy.intercept('http://example.com/settings').as(‘alias’)
    cy.wait(‘@alias’)
    import { authLocators, user, homePage } from '../support/locators'
    import { loginUserData } from '../fixtures/user'

    const { name, lastName, userName, pass } = user

    describe('login', () => {
    before(() => {
    cy.setItem('users', loginUserData(name.value, lastName.value, userName.value, pass.value))
    cy.visit('/login')
    cy.getEl(authLocators.loginName).type(Cypress.config('username'))
    cy.getEl(authLocators.loginPass).type(Cypress.config('password'))
    cy.intercept('GET', '**/posts').as('alias')
    cy.getEl(authLocators.loginBtn).click()
    })

    it('should login user page', () => {
    cy.wait('@alias').then(req => {
    const { body } = req.response
    const firstTenItems = body.splice(0, 10).map(el => el.title)

    firstTenItems.forEach((el, idx) =>
    cy.getEl(homePage.listItem)
    .eq(idx)
    .then(element => expect(element.text()).contains(el)),
    )
    cy.getEl(homePage.userName).should('contain.text', name.value)
    })
    })
    })

    Cypress.Commands.add('login', () => {
    cy.request({
    method: 'POST',
    url: '/login',
    body: {
    username: Cypress.config('username'),
    password: Cypress.config('password')
    }
    }).then((response) => {
    localStorage.setItem('token', response.body);
    });
    });

    Oauth 2.0: сценарий для веб-серверных приложений

    В целом, OAuth 2.0 Authorization code flow включает в себя:

    1. Процесс авторизации начинается, когда ваше приложение перенаправляет браузер на URL страницы авторизации стороннего сервиса. URL-адрес включает параметры запроса, которые указывают тип запрашиваемого доступа, id клиента, функциональность, права на которую запрашиваются (scope), адрес, на который будет перенаправлен пользователь после проверки (redirect url).
    2. Сторонний сервис API обрабатывает аутентификацию пользователя, выбор сессии и согласие пользователя (сonsent dialog).
    3. Результат — код авторизации, который приложение может обменять на access token и refresh token.

    Наиболее проблемная для автоматизации часть сценария — второй пункт, поскольку реализация может варьироваться в зависимости от сервиса, и мы не можем контролировать, каким способом сервис обрабатывает аутентификацию пользователя. Кроме того, здесь требуются действия пользователя.

    Testconfig

    Что нам нужно сделать. Нам нужно:
    Начнем. Для того чтобы взять Web.Config – нам нужно скопировать его в свою папку. Назовем её Sandbox. Теперь скопируем, поставим на pre-build Event в Project Properties:
    Тестируемый компонент Авторизация на е

    xcopy $(SolutionDir)LessonProjectWeb.config $(ProjectDir)Sandbox /y
    

    При каждом запуске билда мы копируем Web.config (и, если надо, то перезаписываем) к себе в Sandbox.Создадим TestConfig.cs и в конструктор будем передавать наш файл (/Tools/TestConfig.cs):

    Генерация данных

    Кроме всего прочего, мы можем и не удалять базу данных после пробегов теста. (переписать)Я добавлю GenerateData проект в папку Test, но подробно рассматривать мы его не будем, просто чтобы был. Он достаточно тривиальный. Суть его – есть некоторые наименования, и мы используем их для генерации. Например, для генерации фамилии используются фамилии американских президентов (зная их, мы сразу отличаем их от других фамилий, которые скорее будут реальными).

    Это также в будущем позволяет избежать «эффекта рыбы», когда в шаблоне тестовые данные были одной определенной, но не максимальной длины и шаблон выглядел прилично, но при использовании реальных данных всё поехало.Создадим 100 пользователей и потом посмотрим на них:

    Замена ui-авторизации на api для автотестов

    Один из важнейших вызовов в автоматизированном тестировании, по моему мнению, – это обеспечить его высокую надёжность. В решении проблемы улучшения показателей надёжности тестирования, хорошо себя зарекомендовал подход использования API интерфейса вместо UI. В данной статье мы подробно разберём простой механизм замены UI авторизации на API.

    Существует большое количество видов аутентификации – Basic, Digest, Form, OAuth 1 и OAuth 2. В качестве примера я предлагаю рассмотреть одну из простейших, а именно – Form. Основная задача статьи – это показать подход внедрения API авторизации для UI тестов. Тесты и имплементацию будем писать на Java. Из инструментов будем использовать Chrome DevTools.

    В качестве объектов тестирования используем Kanboard та DVWA. Это open source продукты с открытой лицензией, которые достаточно легко развернуть локально. По ссылкам можно прочитать больше про данные продукты и при необходимости ознакомиться с инструкциями из развёртки.

    Проект создадим с помощью maven и добавим testng, selenide, rest-assured, json-path, jsoup, maven-compiler-plugin та maven-surefire-plugin.

    Логинимся в Kanboard с открытой вкладкой Network Chrome DevTools.

    image

    image

    Проанализировав DevTools, мы можем узнать алгоритм авторизации. В данном случае, для авторизации выполняются два запроса: GET с двумя query параметрами и POST з парой логин/пароль и csrf токеном. Первый запрос необходим для того, чтобы получить KB_SID cookie. Второй – для KB_RM cookie. Установив оба этих значения в WebDriver, мы получаем доступ к главной странице.

    Первый запрос в RestAssured будет выглядеть следующим образом

    Response response01 = given()
                    .queryParam("controller", "AuthController")
                    .queryParam("action", "login")
                    .when()
                    .get(BASE_URL);
    

    Из него мы получаем KB_SID cookie

    String cookieKBSID = response01.getCookie("KB_SID");

    CSRF-токен находится в доме HTML-страницы, которую мы можем увидеть в теле респонса.

    image

    Получить его нам поможет библиотека jsoup, которая позволяет разделять документ на элементы. Поиск производится подобным же образом, как и Web елементи.

    String cSRFToken = Jsoup.parseBodyFragment(response01.body().asString())
           .getElementsByAttributeValue("name", "csrf_token").attr("value");

    Второй запрос в RestAssured будет выглядеть следующим образом:

    Response response02 = RestAssured
           .given()
           .config(RestAssured.config()
           .encoderConfig(EncoderConfig.encoderConfig()
           .encodeContentTypeAs("x-www-form-urlencoded", ContentType.URLENC)))
           .contentType("application/x-www-form-urlencoded; charset=UTF-8")
           .formParam("remember_me", "1")
           .formParam("username", "admin")
           .formParam("password", "admin")
           .formParam("csrf_token", cSRFToken)
           .queryParam("controller", "AuthController")
           .queryParam("action", "check")
           .cookie("KB_SID", cookieKBSID)
           .when()
           .post(BASE_URL);

    На этом этапе стоит обратить внимание на то, что запрос необходимо правильно заенкодить (encoderConfig, encodeContentTypeAs).

    Из него мы получаем KB_RM cookie.

    String setCookieHeaderValue = response02.header("Set-Cookie");

    Теперь, когда мы получили все необходимые элементы, нам остаётся только открыть нужную страницу з заполненными полями cookie.

    WebDriverRunner.getWebDriver()
           .manage().addCookie(new Cookie("KB_SID", cookieKBSID));
    WebDriverRunner.getWebDriver()
           .manage().addCookie(new Cookie("KB_RM", cookieKBRM));
    Selenide.open(url);

    Для DVWA все происходит аналогично, только токен будет иметь другое имя.

    Безусловно, для других видов аутентификации количество запросов и их сложность будет отличаться. Однако, базовый принцип остаётся неизменным – проанализировать полученный алгоритм и воспроизвести его с помощью RestAssured.

    Благодарю за внимание и надеюсь, что эта статья была полезной для Вас.

    Интегрированное тестирование

    Идея будет совершенно безумной, мы будем использовать и проверять уже существующий код в SqlRepository. Для этого мы через Web.config находим базу (она должна располагаться локально), дублировать ее, подключаться к дубликату БД, проходить тесты и в конце, удалять дубликат БД.

    Создаем проект LessonProject.IntegrationTest в папке Test.

    Добавляем Ninject, Moq и NUnit:

    Install-Package Ninject
    Install-Package Moq
    Install-Package NUnit
    
    

    Так же создаем папку Sandbox и в Setup наследуем UnitTestSetupFixture (/Setup/IntegrationTestSetupFixture.cs) и функцию по копированию БД:

    [SetUpFixture]
        public class IntegrationTestSetupFixture : UnitTestSetupFixture
        {
            public class FileListRestore
            {
                public string LogicalName { get; set; }
                public string Type { get; set; }
            }
    
            protected static string NameDb = "LessonProject";
    
            protected static string TestDbName;
    
            private void CopyDb(StandardKernel kernel, out FileInfo sandboxFile, out string connectionString)
            {
                var config = kernel.Get<IConfig>();
                var db = new DataContext(config.ConnectionStrings("ConnectionString"));
    
                TestDbName = string.Format("{0}_{1}", NameDb, DateTime.Now.ToString("yyyyMMdd_HHmmss"));
    
                Console.WriteLine("Create DB = "   TestDbName);
                sandboxFile = new FileInfo(string.Format("{0}\{1}.bak", Sandbox, TestDbName));
                var sandboxDir = new DirectoryInfo(Sandbox);
    
                //backupFile
                var textBackUp = string.Format(@"-- Backup the database
                BACKUP DATABASE [{0}]
                TO DISK = '{1}'
                WITH COPY_ONLY",
                NameDb, sandboxFile.FullName);
                db.ExecuteCommand(textBackUp);
    
                var restoreFileList = string.Format("RESTORE FILELISTONLY FROM DISK = '{0}'", sandboxFile.FullName);
                var fileListRestores = db.ExecuteQuery<FileListRestore>(restoreFileList).ToList();
                var logicalDbName = fileListRestores.FirstOrDefault(p => p.Type == "D");
                var logicalLogDbName = fileListRestores.FirstOrDefault(p => p.Type == "L");
    
                var restoreDb = string.Format("RESTORE DATABASE [{0}] FROM DISK = '{1}' WITH FILE = 1, MOVE N'{2}' TO N'{4}\{0}.mdf', MOVE N'{3}' TO N'{4}\{0}.ldf', NOUNLOAD, STATS = 10", TestDbName, sandboxFile.FullName, logicalDbName.LogicalName, logicalLogDbName.LogicalName, sandboxDir.FullName);
                db.ExecuteCommand(restoreDb);
    
                connectionString = config.ConnectionStrings("ConnectionString").Replace(NameDb, TestDbName);
            }
    
        }
    
    

    По порядку: В строках

                var config = kernel.Get<IConfig>();
                var db = new DataContext(config.ConnectionStrings("ConnectionString"));
    

    — получаем подключение к БД.

    TestDbName = string.Format("{0}_{1}", NameDb, DateTime.Now.ToString("yyyyMMdd_HHmmss"));
    

    Создаем наименование тестовой БД.

    //backupFile
                var textBackUp = string.Format(@"-- Backup the database
                BACKUP DATABASE [{0}]
                TO DISK = '{1}'
                WITH COPY_ONLY",
                NameDb, sandboxFile.FullName);
                db.ExecuteCommand(textBackUp);
    

    — выполняем бекап БД в папку Sandbox.

                var restoreFileList = string.Format("RESTORE FILELISTONLY FROM DISK = '{0}'", sandboxFile.FullName);
                var fileListRestores = db.ExecuteQuery<FileListRestore>(restoreFileList).ToList();
                var logicalDbName = fileListRestores.FirstOrDefault(p => p.Type == "D");
                var logicalLogDbName = fileListRestores.FirstOrDefault(p => p.Type == "L");
    

    — получаем логическое имя БД и файла логов, используя приведение к классу FIleListRestore.

                var restoreDb = string.Format("RESTORE DATABASE [{0}] FROM DISK = '{1}' WITH FILE = 1, MOVE N'{2}' TO N'{4}\{0}.mdf', MOVE N'{3}' TO N'{4}\{0}.ldf', NOUNLOAD, STATS = 10", TestDbName, sandboxFile.FullName, logicalDbName.LogicalName, logicalLogDbName.LogicalName, sandboxDir.FullName);
                db.ExecuteCommand(restoreDb);
    

    — восстанавливаем БД под другим именем (TestDbName)

     connectionString = config.ConnectionStrings("ConnectionString").Replace(NameDb, TestDbName);
    

    — меняем connectionString.

    И теперь можем спокойно проинициализировать IRepository к SqlRepository:

    protected override void InitRepository(StandardKernel kernel)
            {
                FileInfo sandboxFile;
                string connectionString;
                CopyDb(kernel, out sandboxFile, out connectionString);
                kernel.Bind<webTemplateDbDataContext>().ToMethod(c =>  new webTemplateDbDataContext(connectionString));
                kernel.Bind<IRepository>().To<SqlRepository>().InTransientScope();
                sandboxFile.Delete();
            }
    

    Итак, у нас есть sandboxFile – это файл бекапа, и connectionString – это новая строка подключения (к дубликату БД). Мы копируем БД, связываем именно с SqlRepository, но базу подсовываем не основную. И с ней можно делать всё что угодно. Файл бекапа базы в конце удаляем.И дописываем уже удаление тестовой БД, после прогона всех тестов:

    Как правильно создавать тест-кейсы для формы регистрации?

    Например, есть форма регистрации с множеством полей. Если не заполнить (или заполнить невалидными данными) все поля и нажать [Отправить], то под каждым полем высветится ошибка, если заполнить все поля валидными данными кроме одного, то только под одним полем высветится ошибка.

    Как правильней? 1) Проверить, что все ошибки высветились сразу заполнив все поля невалидными данными или оставив их пустыми? (т.е. будет максимум 2 тест-кейса) 2) Проверить, что появилась ошибка под полем n, после заполнения всех полей кроме поля n валидными данными. Повторить n раз для каждого поля. (Множество тест-кейсов)

    • Вопрос задан более двух лет назад
    • 11581 просмотр

    Если форма заполнена не полностью, то кнопка отправить должна быть неактивна. Если форма заполнена невалидными данными и/или неполностью — кнопка «Отправить» должна быть неактивна и неверно заполненые поля должны показывать подсказку. Для каждого поля нужно проверять, что разные ошибочные варианты ввода в это поле распознаются.

    Переполнение поля тоже. Если есть необязательные поля, нужно проверить, что их заполнение, незаполнение или неверное заполнение не влияет на результат. Если есть кнопки переключатели (radio buttons) можно проверить выставляется ли значение по умолчанию если должно или не выставляется если не должно. Бывает что выставляется хотя не должно.

    Кроме этого навигацию по полям табуляцией можно проверить. Можно проверить, что при перезагрузке страницы введенные в формуляр данные не сбрасываются, если они не должны сбрасываться. Если поля поддерживают автозаполнение можно проверить и это. Если формуляр многостраничный — нужно проверить навигацию между страницами, что введенные данные не теряются. Что их можно отредактировать вернувшись назад.

    Не думайте о количестве тесткейсов, думайте о том, в чем вы хотите убедиться.

    Источник

    Способы автоматизации сценария авторизации

    Отталкиваясь от требований проекта, мы можем реализовать сценарий авторизации OAuth2.0 двумя способами:

    Сценарии для тестирования login формы

    • Авторизуйтесь для ответа в теме

    Тестируемый компонент: авторизация на сайте

    Составить тест-кейсы на тестирование формы авторизации на страничке.

    Форма авторизации имеет:

    Вывод

    https://www.youtube.com/watch?v=dwYE7uLt_j4

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

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *