所以,您想今天将您的应用程序从旧版 1.3.7 升级到 Grails 2.4.4 !
可是等等!在此过程中等待您的挑战很少。
1.开始
思考:我们应该从哪里开始升级?对于企业应用程序来说,这是一项艰巨的任务,您的整个业务都可能在其上运行。在此过程中引入任何错误,你就 完蛋了 !但 拯救我们的 考验 来了!你可能想问,“什么样的 测试 ?”我会向他们解释的,我保证。
让我们从一些理论和即将出现的问题开始!
-
升级应用程序不仅仅是更改 application.properties 文件中的版本号。不仅如此!您有“x”个插件,其中一些是您在内部制作的,由您的存储库提供并由您集中运行的 Artificatory 实例托管!当然, Maven 的插件也有自己的故事要讲。插件还有更多;还记得就地插件吗?您还必须升级它们。
-
接下来,您意识到您的代码行为不当并且您对发生的事情一无所知!这意味着,你知道应用程序在哪个域中运行,但你不知道开发人员在对象之间建立了什么关系,你需要确保这些关系在升级后完好无损(这里要求你升级别人的应用程序).
-
您在 BuildConfig.groovy 和 Config.groovy 文件中有不同的环境设置和配置属性。他们现在需要您特别注意,即检查它们的兼容性。
-
您的应用程序正在使用 Grails 的 i18n 系统,并且用户根据他们的语言环境进行管理。您知道这里有很多工作要做,但请放心,因为 Grails 和 java.util.Locale 类会支持您。
-
您有一个非常大的 UrlMappings.groovy 文件,其中包含大量的 url 条目,如果这还不够,您还可以在不同的本地化版本中找到它们!
这些是我刚才提到的一些问题。
2.准备和思考过程
Grails 的每个新版本都提供了一些新功能改进和对旧功能 或 弃用功能的更新,即 TagLibs 、 GSP 、 控制器 在每个新版本中都会获得一些新的增强功能。其中最显着的改进是 测试 。
Grails 版本 1.3.7 和 2.4.4 之间有相当大的变化,例如 ApplicationHolder 和 ConfigurationHolder 在 2.2.4 中被弃用,但在 2.4.4 中被删除,等等。所以,而不是直接跳到当时的最新版本( 2.4.4 ),我们开始了从 1.3.7 到 2.2.4 再到 2.4.4 的一系列升级。通过这种方式,我们确保我们的应用程序不会崩溃,从而使我们免于灾难。
我们刚刚升级的应用程序具有几乎 100% 的测试覆盖率,其中包括 单元测试 、 集成测试 和 功能 测试。在处理先前的书面测试用例时,应牢记以下几点:
-
好吧,事情一直在变化,我们的测试框架也在不断变化,但是由于 抽象概念 ,它们的工作没有变化,这使我们能够继续使用某些东西,而不管内部工作和使用相同的 接口 和 语法 ,但不是 语义 ,当然。
-
您不应该 以 任何 代价改变 测试 的 断言 (或我们在 Spock 中所说的 规范 ),无论这些是 unit 、 integration 还是 functional 。如果测试失败,则说明在升级过程中引入了一些缺陷,或者以前的版本代码不再有效(稍后我将展示有关它的示例)。这一点对于升级后的应用程序来说非常重要( 你在上面标记我的话 )!
-
听说我们也有 功能 测试! “这些是什么?”你可能会问。这些是伪装成 Geb 运行的 Selenium 测试,以自动化浏览器,这样您就不必每次都手动检查它,例如,登录是否仍然有效?只需执行一些 Geb 测试,它就会为您验证。这里需要注意的是,这些测试仅适用于特定版本的浏览器。对于我们的案例,我们必须使用 Firefox 32.0.1 ,(您也可以使用 Chrome)。当然,这完全取决于 Selenium 库和 驱动程序 的版本。
3.升级
因此,是时候通过留下我们的观察结果但始终坚持它们来实际升级应用程序了。
1.要事第一
在这里,我将描述升级应用程序要采取的初始步骤。这些都是必不可少的,任何人都不应在解释中迷失方向。
-
您可能希望从调整 Config.groovy 和 DataSource.groovy 文件中的值开始。好吧,我们鼓励您提供自己的外部配置文件,例如 my-config.groovy, 并将其添加到 Config.groovy 中的 grails.config.locations 属性,而不是修改这些文件。然后,此外部文件中的配置将覆盖您在 Config.groovy 和 DataSource.groovy 文件中的配置。我推荐一个外部配置文件,因为每个人都可以在同一个文件中拥有自己的设置。当然这个文件不应该被提交到存储库并且必须在 VCS 的忽略列表中。这是个好主意。不是吗?
-
我发现最有用的一项配置是启用重新加载 GSP 更改。 Grails 通常会自动执行此操作,但在我的例子中它失败了。如果您也是这种情况,请尝试在您的外部配置文件中添加这些属性:
grails.gsp.enable.reload = true grails.reload.enabled = true
-
接下来我们应该开始升级 BuildConfig.groovy 文件中的依赖项。在与当前 Grails 版本兼容的 Maven 存储库中找到依赖项, grails 插件目录 将对此提供很大帮助。基本上,我们在这里试图实现的是,在升级应用程序时,我们将解决所有依赖项和插件,这样使用它们的任何代码都不会在编译时中断/死掉。
2.固定测试
正如我之前所说,我会谈论测试并解释为什么如此需要它们。我不会具体谈论 TDD ,因为代码和测试已经事先编写好了。
-
单元测试
它们并不多,尽管它们不能保证应用程序的整体完整性,因为它们仅用于测试工作单元,例如 方法 和 类 。我之所以这么说,是因为有一种说法 ——100% 的单元测试覆盖率并不能保证应用程序可以正常工作,但是您的模块可以单独工作 。无论如何,单元测试是 必要的 。
在 Grails 中,单元测试主要由 Spock 支持。从版本 1.3.7 到 2.4.4 的测试工作在 语法 和 语义 方面发生了很大变化。因此,要修复测试,我们应该使用测试框架提供的新 注释 和行为,你就可以开始了。例如,要测试 服务 类,请说 MyService.groovy ,您可以按如下方式进行:
grails.gsp.enable.reload = true
grails.reload.enabled = true
正如我们在这里看到的,我们将测试升级到了更新的版本。为此,我们必须改变语义,改变我们现在处理各个被测类的方式。
-
集成测试
您可以按照上面提到的相同步骤进行 单元测试 。众所周知,集成测试整体运行一个特定的功能。因此,它们的行为或多或少像单元测试。
-
功能测试
功能测试在功能和实现上都与我们的单元测试和集成测试有很大不同。我们使用一个单独的库调用 Geb 来实现这些测试。 Geb 基本上处理显示在 UI 上的页面。它帮助我们自动化页面行为,例如,如果我们在产品搜索页面上单击该按钮而不填写所需的文本框,该弹出框是否会打开其中包含某些内容?每当我们更改代码时,每次都需要手动测试/回答此类问题,而这正是功能测试大放异彩的地方。 Geb 测试需要一些额外的时间,并且需要在我们的视图逻辑发生变化时进行更新,但投资是未来的证明!
请记住,要检查功能测试是否正常工作,我们需要运行应用程序,如果测试中出现任何问题,要么是代码错误,要么是测试本身有问题。要确定故障出在哪里,这里有一些快速提示:
-
页面上是否缺少任何元素,或者它的 id 或 class 属性发生了变化,而您无法识别该元素?这可能是由于我们的 css 规则定义或重构的更改。
-
我们是否测试任何 div 或 span 的可见性?这是测试失败的常见原因,因为所需的值不会简单地填充到被测元素中。
-
有时是缺少 waitFor 方法导致测试失败。
-
您必须使用 Browser.via() 而不是 Browser.to() ,前提是您的页面使用重定向。
这些是一些指南,但它们都与我们应用程序的升级过程无关。所以,让我们现在转向他们:
-
我们通常面临的第一个问题——什么数据从每个 controller.action 传递到视图。在我们解决这个问题之前,我们应该再解决一个问题。由于可能有许多 controller.action 重定向,因此现在很难找到正确的 controller.action 执行。此外,这些 controller.action 使用 UrlMappings.groovy 文件进行了大量映射。看到问题了吗?因此,我找到的一个解决方案是创建一个 过滤器 ,该过滤器将在每个 controller.action 调用/请求上列出每个 controller 、 action 、 params 和结果 模型 。
当然,我本可以使用已经创建的过滤器,但那太 过分 了!所以,我刚刚创建了一个新的 过滤器 ,如下所示:
grails.gsp.enable.reload = true
grails.reload.enabled = true
小心!!! 确保在生产环境中部署时删除此过滤器,因为它会在日志中打印出所有细节!
2. 现在我们可以检测应用程序访问了哪个 url 。但另一点是,如果某些断言在您的测试中失败了怎么办?正如我所说,在这种情况下,您当前的代码在升级后不再有效。假设您在 GSP 中有如下情况,在我们的 Geb 测试中,我们正在根据应用的 CSS 类计算元素:
grails.gsp.enable.reload = true
grails.reload.enabled = true
你现在能猜出在 _baby.gsp 中使用 baby 会产生什么效果吗,它没有明确传递给它和 clazz 变量的值?答案是: clazz 的价值永远是 cute 。为什么?因为 baby 模型键被定义为 Baby 对象,在这种情况下它永远不会为 null 。你明白了,对吧?
那么,解决方案是什么?就像您显式传递模型一样简单,如下所示:
grails.gsp.enable.reload = true
grails.reload.enabled = true
3. 此步骤讨论配置 Geb 本身,以便您可以在浏览器打开时手动测试应用程序,因为通常浏览器在测试失败或成功后往往会关闭。所以确保永远不要添加 quit() 方法调用,如下所示:
grails.gsp.enable.reload = true
grails.reload.enabled = true
即使此 driver.quit() 行写在某处,也只需将其注释掉即可。删除此语句的重要性在于它将保留浏览器会话的状态,并且您将能够手动检查页面上的元素。您还可以通过在浏览器控制台中检查 AJAX 调用(通过单击按钮进行)来回答诸如 AJAX 调用是否出现问题之类的问题。
4. 结论/最终结果/建议
所以这一切都是关于测试和升级(好吧,不是一切!)。升级应用程序真的不是一件简单的事情,当你对代码一无所知,除了逻辑之外,情况就变得非常复杂。在这种情况下,您只依赖于测试,无论它们是 unit 、 integration 还是 functional 。
您可能认为我对测试没有什么偏见——我确实是。每次手动测试应用程序或某些功能并不是我所说的明智决定。假设我在应用程序的一部分中更改了一些代码。现在我如何确定应用程序中的其他一些模块是否不会因为此更改而受到影响,或者应用程序是否完全受到影响?只需使用测试使事情自动化,你就可以开始了。在某些情况下,我修改了一些代码以通过测试,但由于我所做的更改,其他一些测试开始失败。这样我就不必再次(手动)测试该应用程序,而是通过测试向我提供反馈。见证考验的力量!
人们通常说他们有 100% 的 单元 测试,他们还需要 集成 和 功能 测试吗?答案是 肯定的 。单元测试只能保证每个单元都能正常工作。由于应用程序与组合在一起的单个单元一起工作,因此应将它们作为一个组进行测试。这就是集成测试可以帮助我们的地方。那么为什么要进行功能测试呢?功能测试是必要的,以查看软件是否按用户要求的那样工作。集成测试只是为了确保所有单元协同工作以产生预期的结果,而功能测试只是为了确保软件按照用户要求和预期行为工作。
如果有人来这里问我,如果我通过了所有测试(单元、集成和功能),他们的应用程序是否可以正常工作,我的经验是, 是的 !
所以,这是关于 Grails 应用程序的升级。这可能不是完整的清单,但您知道在哪里解决问题并提出解决方案。
感谢您加入对话。
PS: 您可以在 https://gist.github.com/ManvendraSK/c8c9035e92cd5ec34ca2 找到本文的 Markdown 版本。