作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Ciprian Balea

Ciprian Balea

高级QA自动化工程师

Ciprian是一名经过认证的scrum管理员,在以各种语言建立和开发CI基础架构和测试自动化框架方面经验丰富.

Expertise

Share

作为一名测试人员,你可以做的最重要的事情之一就是让你正在测试的应用程序自动化,从而提高你的工作效率和速度. 完全依赖手动测试是不可行的,因为您需要每天运行全套测试, 有时一天好几次, 测试推送到应用代码中的每一个更改.

本文将描述我们团队的识别之旅 谷歌的EarlGrey 1.0 作为最适合我们自动化iOS的工具 Toptal Talent app. 事实上,我们正在使用EarlGrey并不意味着它是每个人最好的测试工具——它只是碰巧适合我们的需要.

为什么我们要过渡到EarlGrey

多年来,我们的团队在iOS和Android上开发了不同的移动应用程序. In the beginning, 我们考虑使用跨平台UI测试工具,这样我们就可以编写一组测试并在不同的移动操作系统上执行. 首先,我们用 Appium,是最流行的开源选项.

但随着时间的流逝, Appium limitations 变得越来越明显. 在我们的案例中,Appium的两个主要缺点是:

  • 该框架的稳定性问题导致了许多测试失败.
  • 相对缓慢的更新过程阻碍了我们的工作.

以减轻第一个Appium的缺点, 我们编写了各种各样的代码调整和hack,以使测试更稳定. 然而,对于第二个问题,我们无能为力. 每次iOS或Android发布新版本时,Appium都要花很长时间才能赶上. 通常,由于存在许多漏洞,最初的更新无法使用. As a result, 我们经常被迫在较旧的平台版本上继续执行我们的测试,或者完全关闭它们,直到可用的Appium更新.

这种做法远非理想, 因为这些问题, 还有一些我们不会详细介绍的, 我们决定寻找替代方案. 一个新的测试工具的最高标准是 增加了稳定性 and faster updates. 经过一番调查,我们决定为每个平台使用本地测试工具.

所以,我们过渡到 Espresso 用于Android项目和EarlGrey 1.0 for iOS development. 事后看来,我们现在可以说这是一个很好的决定. 由于需要编写和维护两组不同的测试而“损失”的时间, 每个平台一个, 不需要调查这么多不可靠的测试,也不需要在版本更新上有任何停机时间,这不仅仅是弥补吗.

本地项目结构

你需要将框架包含在与你正在开发的应用程序相同的Xcode项目中. 因此,我们在根目录中创建了一个文件夹来承载UI测试. Creating the EarlGrey.swift 文件在安装测试框架时是必需的,其内容是预定义的.

Toptal人才应用程序:本地项目结构

EarlGreyBase 父类是所有测试类的吗. 它包含了 setUp and tearDown 方法,从 XCTestCase. In setUp, 我们加载了大多数测试通常会使用的存根(稍后会详细介绍存根),我们还设置了一些配置标志,我们注意到这些标志可以提高测试的稳定性:

//关闭EarlGrey的网络请求跟踪,因为我们不使用它,它会阻塞测试执行

GREYConfiguration.sharedInstance().setValue([".*"], forConfigKey: kGREYConfigKeyURLBlacklistRegex)
GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeyAnalyticsEnabled)

我们使用Page Object设计模式——应用程序中的每个屏幕都有一个对应的类,其中定义了所有UI元素及其可能的交互. 这个类被称为“页面”.测试方法是根据存在于单独的文件和类中的特性进行分组的.

为了让你更好地了解所有东西是如何显示的, 这就是我们的应用程序中的登录和忘记密码屏幕的样子,以及它们是如何由页面对象表示的.

这是登录和忘记密码屏幕在我们的应用程序的外观.

在本文后面,我们将介绍Login页面对象的代码内容.

自定义实用程序方法

EarlGrey将测试动作与应用程序同步的方式并不总是完美的. For example, 它可能会尝试点击UI层次结构中尚未加载的按钮, 导致测试失败. 为了避免这个问题, 我们创建了自定义方法,以便在与元素交互之前等待元素以所需的状态出现.

下面是一些例子:

asyncWaitForVisibility(on element: GREYInteraction) {
     //默认情况下,EarlGrey阻塞测试执行
     //应用程序正在动画或在后台做任何事情.           
     //http://github.com/google/EarlGrey/blob/master/docs/api.md #同步
     GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeySynchronizationEnabled)
     element.断言(grey_sufficientlyVisible ())
     GREYConfiguration.sharedInstance().setValue(true, forConfigKey: kGREYConfigKeySynchronizationEnabled)
}


等待元素:GREYInteraction, timeout: Double = 15.0) -> Bool {
        GREYCondition(name: "等待元素出现",block: {
            var error: NSError?
            element.断言(grey_notNil()错误: &error)
            返回错误== nil
        }).wait(withTimeout: timeout, pollInterval: 0).5)
        if !elementVisible(元素){
            XCTFail(“元素没有出现”)
        }
        return true
}

EarlGrey本身没有做的另一件事是滚动屏幕,直到所需的元素变得可见. 我们可以这样做:

static func elementVisible(_ element: GREYInteraction) -> Bool {
	var error: NSError?
	element.断言(grey_notVisible()错误: &error)
	if error != nil {
		return true
	} else {
		return false
	}
}

静态函数scrollUntilElementVisible(_ scrollDirection: GREYDirection, _ speed: String, _ searchedElement: GREYInteraction, _ actionElement: GREYInteraction) -> Bool {
        var swipes = 0
        while !elementVisible (searchedElement) && swipes < 10 {
            如果speed == "slow" { 	
            actionElement.执行(grey_swipeSlowInDirection (scrollDirection))
            } else {             
            actionElement.执行(grey_swipeFastInDirection (scrollDirection))
            }
            swipes += 1
        }
        if swipes >= 10 {
            return false
        } else {
            return true
        }
}

我们发现EarlGrey的API中缺少的其他实用程序方法是计数元素和读取文本值. 这些工具的代码可以在GitHub上找到: here and here.

Stubbing API Calls

为确保避免后端服务器问题导致的错误测试结果,我们使用 OHHTTPStubs library 模拟服务器调用. 他们主页上的文档非常简单明了, 但我们将展示如何在应用程序中存根响应, 它使用GraphQL API.

类StubsHelper {
	静态让testURL = URL(字符串:"http://[our后端服务器]")!
	静态函数setupOHTTPStub(对于请求:StubbedRequest,延迟:Bool = false) {
		存根(条件:isHost (testURL.host!) && hasJsonBody(请求.bodyDict()) {_ in
			let fix = appFixture(forRequest: request)
			if delayed {
				return fix.requestTime(0.1、responseTime: 7.0)
			} else {
				return fix
			}
		}
	}
	让stubbedEmail = "fixture@email.com"
	让stubbedPassword = "password"
	enum stubberequest {
		case login
		func bodyDict() -> [String: Any] {
			switch self {
				case .login:
					返回EmailPasswordSignInMutation (
						邮箱:stubbedTalentLogin,密码:stubbedTalentPassword
						).makeBodyIdentifier ()
			}
		}
		func statusCode() -> Int32 {
			return 200
		}
		func jsonFileName() -> String {
			让文件名:字符串
			switch self {
				case .login:
					fileName = "login"
			}
			返回“\(文件名).json"
		}
	}
	私有扩展graphqoperation {
		func makeBodyIdentifier () -> [String: Any] {
			let body: GraphQLMap = [
				“查询”:queryDocument,
				“变量”:变量,
				“operationName”:operationName
			]
        //对这里的enum等值进行规范化,否则主体比较将失败
        让normalizedBody = body.jsonValue as? [String: Any] else {
        	fatalError()
        }
        返回normalizedBody
    }
}

加载存根是通过调用 setupOHTTPStub method:

StubsHelper.setupOHTTPStub (: .login)

把所有东西放在一起

本节将演示如何使用上面描述的所有原则来编写实际的端到端登录测试.

import EarlGrey

最终类LoginPage {

    func login() -> HomePage {
        fillLoginForm()
        loginButton().执行(grey_tap ())
        return HomePage()
    }

    函数fillLoginForm() {  
	ElementsHelper.waitElementVisibility (emailField ()) 
    	emailField().执行(grey_replaceText (StubsHelper.stubbedTalentLogin))
        passwordField().执行(grey_tap ())
        passwordField().执行(grey_replaceText (StubsHelper.stubbedTalentPassword))
    }

    函数clearAllInputs() {
        if ElementsHelper.elementVisible (passwordField ()) {
            passwordField().执行(grey_tap ())
            passwordField().执行(grey_replaceText (" "))
        }
        emailField().执行(grey_tap ())
        emailField().执行(grey_replaceText (" "))
    }
}

私有扩展LoginPage {
    func emailField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction {
        return EarlGrey.selectElement(with: grey_accessibilityLabel("Email"), File:文件,line:行)
    }

    func passwordField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction {
        return EarlGrey.selectElement(
            with: grey_allOf([
                    grey_accessibilityLabel(“密码”),
                    grey_sufficientlyVisible (),
                    grey_userInteractionEnabled ()
                ]),
            File:文件,line:行
        )
    }

    func loginButton(file: StaticString = #file, line: UInt = #line) -> GREYInteraction {
        return EarlGrey.selectElement(with: grey_accessbilityid ("login_button"), File:文件,line:行)
    }
}


类BBucketTests: EarlGreyBase {
    func testLogin() {
        StubsHelper.setupOHTTPStub (: .login)
        LoginPage().clearAllInputs()
        let主页= LoginPage().login()
        GREYAssertTrue(
            homePage.assertVisible(),
            原因:"登录成功后没有显示主界面"
        )
    }
}

在CI中运行测试

我们使用Jenkins作为我们的持续集成系统, 我们为每个拉取请求中的每个提交运行UI测试.

We use fastlane scan 在CI中执行测试并生成报告. 在这些报告中附上失败测试的屏幕截图是很有用的. Unfortunately, scan 不提供这个功能,所以我们必须定制它.

In the tearDown() 函数,我们检测测试是否失败,并保存iOS模拟器的屏幕截图.

import EarlGrey
import XCTest
进口UIScreenCapture

重载函数tearDown() {
        if testRun!.failureCount > 0 {
            // name是XCTest实例的属性
            / / http://developer.apple.com/documentation/xctest/xctest/1500990-name
            takeScreenshotAndSave(名称):
        }
        super.tearDown()
}

函数takeScreenshotAndSave(作为testCaseName: String) {
        let imageData = UIScreenCapture.takeSnapshotGetJPEG ()
        让路径= nssearchpathfordirectoresindomains.documentDirectory, .userDomainMask,真的)
        let filePath = "\(paths[0])/\(testCaseName) ".jpg"

        do {
            try imageData?.write(to: URL.init (fileURLWithPath: filePath))
        } catch {
            截图未写.")
        }
}

屏幕截图保存在模拟器文件夹中, 您需要从那里获取它们,以便将它们附加为构建工件. We use Rake 来管理我们的CI脚本. 这就是我们收集测试工件的方式:

defgather_test_artifacts (booted_sim_id, destination_folder)
  App_container_on_sim = ' xrun simctl get_app_container #{booted_sim_id}[你的bundle id] data '.strip
  FileUtils.cp_r“#{app_container_on_sim}/Documents”,destination_folder
end

Key Takeaways

如果您正在寻找一种快速可靠的方法来自动化您的 iOS 测试,去找格雷伯爵吧. 它是由谷歌开发和维护的(需要我多说)?),而且在许多方面,它都优于目前可用的其他工具.

您将需要对框架进行一些修改,以准备实用方法来提高测试稳定性. 为此,您可以从我们的自定义实用程序方法示例开始.

我们建议对存根数据进行测试,以确保您的测试不会因为后端服务器没有您期望它拥有的所有测试数据而失败. Use OHHTTPStubs 或者一个类似的本地web服务器来完成工作.

在CI中运行测试时, 确保提供失败案例的屏幕截图,以便于调试.

您可能想知道为什么我们没有迁移到EarlGrey 2.还没有,这里有一个简短的解释. 新版本于去年发布,它承诺比v1有一些增强.0. 不幸的是,当我们采用EarlGrey时,v2.0不是特别稳定. 所以我们没有变换到v2.0 just yet. However, 我们的团队急切地等待着新版本的bug修复,这样我们就可以在未来迁移我们的基础设施.

Online Resources

EarlGrey的入门指南 GitHub homepage is the 如果您正在为您的项目考虑测试框架,您希望从哪里开始. There, 您将找到一个易于使用的安装指南, 该工具的API文档, 还有一个方便的备忘单,以一种在编写测试时直接使用的方式列出了框架的所有方法.

有关为iOS编写自动化测试的更多信息,您也可以查看 我们之前的博客文章.

了解基本知识

  • UI测试是功能测试吗?

    功能测试是验证系统功能的过程. 因此,UI测试是通过用户界面进行的功能测试.

  • 为什么UI测试很重要?

    UI测试验证应用程序的多个层是否应该一起工作. 低级别测试, 例如单元测试或API测试, 我们不能像UI测试那样撒下一张大网去寻找漏洞.

  • 哪些测试自动化工具可以用于UI测试自动化?

    根据需要测试的系统的性质,您可以使用特定的工具. 一些著名的UI自动化测试工具是Selenium、Appium、Ranorex或AutoIt.

聘请Toptal这方面的专家.
Hire Now

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.