原文: CirecleCI Blog 协议: CC BY-NC-SA 4.0 功能性与非功能性软件测试 原文: https://circleci.com/blog/functional-vs-non-functional-testing/ 当你想到软件测试时,首先想到的是什么?
对于许多开发人员来说, 单元测试和集成测试 通常是他们首先想到的。
这两种软件测试方法对于编写和维护高质量的产品代码库都至关重要。
但是它们本身是不够的。
您的团队的测试实践应该评估整个应用程序,观察它在正确运行时如何运行的更大故事,并在发现偏差时发出警报。
功能性和非功能性测试都是 全面软件测试过程 的重要组成部分,在每个应用层建立额外的信心。
https://www.youtube.com/embed/NgQT7miTP9M 视频 什么是功能测试?
功能测试侧重于根据一组需求或规范检查应用程序的功能。
功能测试通常包括测试部分底层代码。
与单独测试单个模块相比,将实际输出与预期行为进行比较可以提供更清晰的总体情况。
模块之间的交互经常是错误发生的地方。
有许多类型的功能测试: 单元测试(也可以用于非功能测试) 集成测试 用户接受度测试 闭箱测试 单元测试 API 应用程序的单元测试可能会针对测试环境中部署的系统发出请求,并将响应与文档进行比较。
然而,单元测试是有限的。
当应用程序在功能上偏离或倒退时,应用程序范围的功能测试通常会检测到单元测试没有检测到的变化。
集成测试 集成测试验证软件模块如何协同工作。
当开发人员将代码编写为松散耦合的模块时——这通常是应该的——组件依赖于它们如何交互的显式契约。
集成测试验证软件的每一部分都达到了合同的末尾,并在这些交互引入回归时生成警告。
用户接受度测试 在软件测试的用户接受阶段,开发人员向最终用户或他们的代表提供部分或全部应用程序,以模拟真实世界的交互和功能。
许多健康的工程文化避免严重依赖用户验收测试,因为它不可靠,成本高,耗时。
尽管如此,对于大多数应用程序来说,一些用户接受度测试是测试过程中至关重要的一部分。
闭箱测试 功能测试还可以包括完整应用程序的封闭测试。
封闭盒测试从整体上处理应用程序的输出,而不检查其内部工作。
当编写功能测试来补充他们的非功能测试时,许多工程师倾向于它。
与最外层交互的代码被自动测试,并且只评估输出。
对于只需要 API 测试的应用程序来说,这个过程很简单,因为代码只需进行 API 调用并评估结果。
当用于有用户界面的应用程序时,封闭盒测试变得越来越复杂。
解决这种复杂性的一种方法是使用像 Selenium 这样复杂的测试工具。
这些工具允许代码与应用程序进行交互,就好像它是 web 浏览器中的用户一样。
Selenium 和类似的工具可以自动进行用户验收测试,同时提高可靠性和扩展性。
然而,对用户界面应用封闭盒测试是一个具有独特挑战的复杂问题。
它会迅速消耗您团队的工程带宽。
什么是非功能测试?
非功能性测试评估对功能性不重要但有助于最终用户体验的应用程序属性。
负载下的性能和可靠性不是软件系统的功能组件,但肯定会影响或破坏用户体验。
没有通过非功能性测试的东西并不总是导致用户会注意到的问题,但是它可以指示系统中的问题——特别是在大规模的情况下。
有许多类型的非功能性测试: 性能试验 负载测试 可用性测试 安全测试 性能试验 非功能测试类别中的一个重要过程关注性能。
性能测试 确保软件系统迅速响应请求。
糟糕的延迟会破坏用户体验,而编写良好的性能测试通常会在用户发现问题之前就发现问题。
负载测试 负载测试 是一种相关类型的非功能测试。
很少有系统能像每秒响应 10,000 个请求那样每秒响应一个请求。
负载测试验证系统能够处理峰值负载,并在缺乏处理工作负载峰值的资源时从容失败。
可用性测试 可用性测试衡量用户体验的质量。
在很大程度上,可用性测试是一个手动过程,不能很好地扩展。
不管怎样,缺乏可用性测试,尤其是在本地化应用程序时,通常会导致混乱和不直观的界面。
花一些时间将这种类型的测试整合到你的软件开发过程中是值得的。
安全测试 安全性测试包括另一组非功能性测试。
您的团队应该定期测试他们的应用程序,以确保它们是安全的,并能正确处理数据。
安全测试的范围从 自动扫描 到定期 渗透测试 ,取决于应用程序暴露于潜在威胁的程度。
许多团队没有将安全测试视为他们测试套件的一部分。
明智的做法是包含安全性测试,并像对待单元测试一样认真对待它。
比较功能测试和非功能测试 功能性测试确认代码正在做正确的事情,而非功能性测试验证代码正在以正确的方式做正确的事情。
这两种类型都包含验证前端和后端元素和行为的方法。
在开发人员可能运行的测试种类中,这两个类别之间有一些重叠。
功能测试套件是这两个类别中更为必要的。
软件解决方案必须解决他们的目标问题。
非功能测试所针对的实现细节和性能指标通常是细化的次要问题。
一个健壮的测试方法也考虑了这些因素,尤其是当扩展是一个优先事项时。
功能测试的编写和维护通常很昂贵,执行起来也很慢。
许多开发人员非常依赖单元测试,因为它们有助于 测试驱动的开发 ,并且经常导致自我记录的代码。
当代码需求发生变化时,这些测试可以快速执行并且易于修改。
即使使用像 Selenium 这样的现代工具,像封闭盒测试这样的方法也需要很长时间来运行和编写。
最中立的测试方法包括功能性和非功能性测试的综合。
许多开发人员遵循“测试金字塔”,这指导他们将大部分测试写成单元测试,因为他们编写和执行起来很快。
当他们在金字塔上前进时,开发人员实现更少的方法,如集成测试,然后向实践前进,如最终的用户验收测试。
最终,他们更保守地使用测试方法,提供相对于他们的实现成本更低的回报。
通过将功能性和非功能性测试集成到 持续集成和持续部署(CI/CD)管道 中,组织可以降低与实现和维护测试过程相关的成本。
结论 测试软件应用程序没有通用的解决方案。
说功能性方法比非功能性好或者反过来都太死板了。
非功能测试和验证功能的测试一样必要。
许多团队认为非功能测试的优先级较低,因为它提供的改进不太引人注目。
如果性能下降,用户可能会感到烦恼,但是他们仍然能够使用软件应用程序。
另一方面,当功能被破坏时,用户可能根本无法使用他们需要的功能。
这与更短的执行时间和更低的成本相结合,使得功能测试成为许多团队软件测试过程的基础。
尽管如此,非功能性测试仍然扮演着重要的角色,您的团队应该找到将它们结合起来的方法。
一个优秀的测试套件将验证广泛的非功能特性和组件,包括性能和可用性。
要了解更多关于测试以及如何进一步将良好的测试实践与您的持续集成工具结合的信息,请探索一些与 CircleCI 集成的 测试工具 。
函数式与面向对象编程| CircleCI 原文: https://circleci.com/blog/functional-vs-object-oriented-programming/ 编程既是一门科学,也是一门艺术。
个人偏好在决定你的编程风格方面起着很大的作用,所以当你发现自己在和一个同龄人争论时,你可能不会感到惊讶。
一个正在进行的争论是在两个不同的编程范例之间的选择,这两个范例被称为 函数式编程 和 面向对象编程 。
哪个更好?
你应该使用哪一个?
双方的支持者都会告诉你,他们最喜欢的范式提供了一些几乎普遍适用的明显优势。
你可以说这要视情况而定。
在本文中,我们将着眼于这两种选择,并试图从事实中提炼出神话。
目标是更好地理解这些编程方法,并准备在有意义的时候使用它们。
函数式编程 函数式编程(FP) 是最古老的编程种类之一,甚至可能是最古老的。
它定义了一个构建完全依赖于函数的软件的过程。
在 FP 中,开发人员编写函数来创建新函数,编写应用程序来避免共享状态或可变数据等问题。
为了实现这一点,开发者经常声明性地使用 FP,而不是强制性地使用。
这意味着什么呢?
函数式编程中的关键概念 首先,理解函数在 FP 中是一等公民是很重要的,这意味着它们和其他任何值一样被对待。
它们可以作为参数传递或从其他函数返回。
返回函数的函数称为 高阶函数 。
这些高阶函数使得直接从参数构造新函数成为可能。
第二个要点是不变性的概念。
不可变的值是原始值,不能改变。
像数字这样的值被认为是不可变的。
您不能将 42 更改为 14。
您只能创建一个值为 14 的新数字,并将其分配给之前使用的变量。
但是新的 14 号不会影响你之前使用的 42 号。
虽然这种值处理对于诸如数字之类的原语是有意义的,但是对于诸如对象或数组之类的组合值来说可能感觉很奇怪。
然而,FP 原则将所有的值都视为不可变的。
更改值的唯一方法是创建一个新值,可能会使用旧值作为基值或副本。
不可变数据类型的使用允许 FP 使用 纯函数 。
这些函数仅由其参数定义。
因为参数不能改变,所以保证它们的行为是可预测的。
同样的论点给出同样的结果。
其他编程方法不能保证这种可预测的行为。
函数式编程的用例 尽管 FP 最初主要用于科学应用,但它在各种领域越来越受欢迎。
例如,在 web 开发中,一个名为 React 的用户界面(UI)库利用 FP 原则变得声明性和易于处理。
该库提倡使用不可变的状态对象(值)来反映应用程序的当前状态,而不进行更改。
当开发者想要一个新的状态时,他们必须创建一个新的对象。
这种方法的美妙之处在于,您可以追溯 UI 中的每次更改。
所有先前的状态仍然未被触及并且可用。
虽然 FP 的根源当然是在计算机科学的学术方面,但是它的实际含义可以追溯到像 Lisp 或 Scheme 这样的语言。
这些特性中的一些被融入了 JavaScript 语言,这是 FP 继续使用的一个关键因素。
然而,有些语言是建立在 FP 原则之上的:F#、Clojure 和 Elixir 都是比较流行的语言。
在学术界,Haskell 几十年来一直被认为是主流语言。
面向对象编程 函数式编程已经存在很长时间了,但是一些开发人员认为面向对象编程(OOP)更加传统。
像 Smalltalk 或 Objective-C 这样的编程语言普及了 OOP,OOP 是在 20 世纪 70 年代末发明的。
后来,C++、Java 和 C#继续将这种编程风格硬植入大多数开发人员的头脑中。
请继续阅读 OOP 中重要原则的描述和一些最常见的用例。
面向对象编程中的关键概念 在 OOP 中,开发人员将软件应用程序建模为可以相互通信的 对象 的集合。
每个对象的接口都是一个 类 ,一个指示函数和值可以被任何实例访问的模板。
虽然这种通信能力最初被设想为允许网络通信或异步 IO 的消息传递,但后来它作为在相应对象上调用的简单函数而普及。
这样的功能被称为 方法 。
与 FP 中的不可变对象不同,OOP 中的对象变异是游戏的一部分。
因此,调用一个方法很可能也会改变对象的一些值。
许多开发人员仍然使用 OOP 的一个原因是,它是强制性编写的,尤其是在教授编程的时候。
这意味着开发人员很清楚在哪里发生了什么。
相反的观点是,即使使用这种命令式风格,也很难确定单个对象的当前状态。
由于可变性,这可能会很快导致不可预见的后果。
面向对象编程的用例 传统上,开发人员用面向对象的方法制作了几乎所有的用户界面。
这也相对简单,因为基于类的组件可以从另一个类似的组件继承它的基本结构(字段和方法)。
例如,日期输入字段可以从文本输入字段继承,文本输入字段从输入框继承,输入框从控件继承。
使用这种基于继承的方法,您只需要指定额外的方法,并在一些现有的方法上重新实现行为。
例如,不需要再次编写键盘或鼠标处理的逻辑。
今天,OOP 是所有通用编程语言的必备特性。
甚至基于 FP 的语言如 F#也直接支持 OOP 特性,如类或继承。
一个很好的例子是 JavaScript,它没有马上加入标准特性,比如类或继承,而是在最近的版本中加入了它们。
正如我们已经发现的那样,这两种编程方法差异很大,足以证明在适当的时候使用这两种方法是正确的。
那么如何在二者之间做出选择呢?
以下是一些论据。
函数式编程与面向对象编程:争论 正如已经讨论过的,这两种方法各有利弊。
计算机科学教授 Norman Ramsey 在著名的 T2 堆栈溢出答案 T3 中提供了一个有用的观点。
他认为,当所有对象都是已知的,但行为可能会改变时,FP 会表现出色。
相比之下,当行为已知时,OOP 是很棒的,但是实际的数据类型可能会改变。
双方的粉丝都不止这些。
例如,FP 的追随者认为以纯 FP 风格编写的设计简洁的软件易于调试,并且永远不会崩溃。
他们告诉你,FP 立刻给你带来了测试驱动开发的一些优势,严格应用了所有 T2 坚实原则的 OOP 本质上是 FP。
虽然 SOLID 确实导致了类似于 FP 的大多数的个体函数,但是它不一定非得 是 FP。
例如,没有坚实的原则禁止数据突变。
热爱 OOP 的开发人员可能会因为性能或简单性的权衡而忽略一些 FP 的好处。
当您只想更改单个字段时,为什么要将一个对象的所有字段复制到一个新对象中?
为什么有一百万个元素的数组需要一个副本来设置一个元素?
当然,您可以使用一些模式来避免制作副本,但是您仍然需要专门的数据结构。
与开发人员多年来独立于 OOP 使用的直接方法相比,这里的间接程度可能令人困惑。
虽然这些方法之间有一些非常鲜明的对比,但它们也可以是互补的。
没有法律禁止在软件应用程序中使用类。
也没有法律规定应用程序的一般状态是可变的。
展望未来 几乎所有流行的编程语言都是 多进制 。
它们都支持 FP,允许传递函数,或者拥有处理数据对象不变性的助手。
它们还附带了面向对象的特性,比如类或继承。
无论哪种方式,这种多半径方法都将继续存在。
从用户的角度来看,有更多的选择几乎总是更好。
总的来说,更倾向于 FP 友好的特性似乎是可能的。
通过引入对克隆数据对象、函数引用和函数组合的额外支持,语言变成了一个很好的伴侣,甚至超越了 FP。
在像 C#这样的语言中,FP 友好的特性为 OOP 特性提供了一个极好的补充。
这种组合甚至在 OOP 开发的应用程序中也很方便,比如当您使用某些辅助函数时。
混合 FP 和 OOP 方法的缺点是学习曲线。
有些人就是因为这个原因而避免使用 C#。
最初作为 Java 的一个原始的、设计优雅的替代品,最终感觉更像是一个复杂的 C++怪物。
OOP 当然不会消失,但最有可能的结果是它会找到自己的位置,与 FP 共存。
诚然, 副作用 自由开发的理想在实践中几乎不可能实现。
将日志消息写入控制台已经有副作用了。
开发者必须首先发现并打开 FP 的实用性。
现在开发者已经开始认识到 FP 的实用性,几乎没有理由再把 FP 的特性锁在外面。
对面向对象编程的持续支持 在这种背景下,为什么 OOP 仍然是一个可行的选择?
OOP 有价值,因为它本质上是理想的工具。
几乎没有什么是开发人员不能通过 OOP 实践建模的。
开发者甚至可以通过 OOP 对大多数 FP 特性进行建模。
例如,考虑像传递或组合函数这样的基本事情。
简而言之,这就是 仿函数 或 委托 所提供的。
它只是一个对象,对应一个类,只有一个方法。
寻找共同点 很少有开发团队会仅仅为了从一种方法切换到另一种方法而改变他们的软件或编写应用程序的方式。
对于一个团队来说,更有可能的是积极地重构他们的一些最基本的应用程序,以使用他们的编程语言的更新的特性。
不管怎样,未来看起来越来越混合。
在未来的某个项目中,你可能会发现自己在 OOP 应用程序中使用了更多受 FP 启发的风格,或者在 FP 驱动的应用程序中使用了一些 OOP 特性。
结论 某些类型的应用程序偏爱 FP(比如编译器),而其他的应用程序更适合使用 OOP 原则(比如经典的桌面应用程序)。
选择正确的编程方法取决于几个因素,比如目标编程语言、框架和您正在构建的应用程序类型。
使用你觉得最舒服的,但不要忘记继续学习增加你的工具。
解决问题时有大量的选择总是有益的。
使用 CircleCI | CircleCI 将 Gatsby 站点部署到 Netlify 原文: https://circleci.com/blog/gatsby-netlify-deploy/ Gatsby 是一个静态网站和应用程序生成器,它使得构建强大的基于 React 的前端应用程序变得简单而有效。
GitHub 上有超过五万颗星星(在撰写本文时为 51.5k),Gatsby 是使用最广泛的 React 框架之一。
Gatsby 如此受欢迎,以至于大多数托管平台都提供了对该框架的定制支持。
Netlify 就是这些平台中的一个。
虽然 Netlify 对 Gatsby 的自定义支持提供了流畅的部署,但有一个主要缺点。
Netlify 控制了大部分部署过程,这意味着您的团队不能执行像运行自动化测试这样的定制任务。
在本教程中,您将学习如何通过使用 CircleCI 作为连续部署服务器来接管部署过程。
通过使用本教程创建的设置,您将能够在最终部署到 Netlify 之前执行构建过程中所需的任何自定义步骤。
先决条件 要遵循本教程,需要做一些事情: 系统上安装的 Node.js (版本 12.13 或更新) 净收益账户 一个 圆 的账户 GitHub 的一个账户 安装并设置好所有这些之后,您就可以开始本教程了。
创建一个新的盖茨比网站 您首先需要的是一个部署到 Netlify 的 Gatsby 站点。
在系统中您选择的任何位置运行以下命令:
npm init gatsby
该命令调用 Gatsby 安装程序并打开一个交互式 CLI 会话,在该会话中,系统会提示您回答几个问题来设置您的项目。
根据下面的列表回答问题。
请注意,粗体文本是为问题选择的文本回答或选项,而斜体文本是要执行的操作。
你想把你的网站叫做什么:Netlify Gatsby 网站 您希望将创建网站的文件夹命名为什么:按 Enter 键接受默认值 你会使用 CMS 吗:不会(或者我稍后会添加它) 要不要添加一个造型系统:不要(或者我以后再添加) 您想用其他插件安装附加功能吗:选择 添加 Markdown 和 MDX 支持 ,点击 完成 ,然后按回车键 我们可以这样做吗:按回车键确认 是 选项 以下是项目反应流程的 cli 输出:
What would you like to call your site?
✔ · Netlify Gatsby Site
What would you like to name the folder where your site will be created?
✔ 2021/ netlify-gatsby-site
✔ Will you be using a CMS?
· No (or I'll add it later)
✔ Would you like to install a styling system?
· No (or I'll add it later)
✔ Would you like to install additional features with other plugins?
· Add Markdown and MDX support
Thanks! Here's what we'll now do:
🛠 Create a new Gatsby site in the folder netlify-gatsby-site
🔌 Install gatsby-plugin-mdx
? Shall we do this? (Y/n) › Yes
回答完问题后,项目创建开始。
在这个过程结束时,您将在
netlify-gatsby-site
文件夹中创建一个 Gatsby 项目。
使用以下命令运行项目:
cd netlify-gatsby-site
npm run develop
这将在本地 URL
http://localhost:8000
启动 Gatsby 站点。
在您的浏览器中输入此地址即可访问该网站。
设置 GitHub 项目 下一步是在 GitHub 上建立您的 Gatsby 项目。
GitHub 安装完成后,您就可以在 Netlify 和 CircleCI 上安装项目了。
在将您的项目推送到 GitHub 之前,您需要安装 Netlify CLI 包,作为您的 Gatsby 项目中的开发依赖项。
对于 CI 环境中的部署,建议这样做,以避免中断更改。
在 Gatsby 项目的根目录下,运行以下命令来安装依赖项:
npm install --save-dev netlify-cli
你现在可以将你的项目推送到 GitHub 了。
一旦你的项目在 GitHub 上,创建一个新的分支。
给这个分支取任何你想要的名字;对于本教程,我将把它命名为
netlify-deploy-ignore
。
您可能想知道为什么需要这个分支。
Netlify 需要一个项目分支来自动触发应用程序的部署。
当更改被推送到这个分支时,Netlify 将启动部署过程。
您希望避免 Netlify 和 CircleCI 管道并行运行两个部署进程的情况。
这个新的分支充当了 Netlify 监视的诱饵。
不要将更改推送到此分支。
其目的是“分散”Netlify 部署过程的注意力,以便您可以使用 CircleCI 进行部署。
你可以在 GitHub 上设置一个受保护的分支,这样你的团队中就没有人会误把它推上来。
创建 Netlify 应用程序 要创建新的 Netlify 应用程序,请转到您的 Netlify 仪表板,然后单击 Git 按钮中的 新站点。
接下来,选择 GitHub 作为您的提供商并搜索项目。
选择项目并转到下一步,为 Netlify 选择要部署的分支。
选择你的诱饵分支。
接下来,点击 Deploy site 按钮,这样 Netlify 将执行您站点的第一次部署。
点击链接,看起来应该是:
sitename.netlify.app
。
要在 CircleCI 管道中执行自动部署,您需要从 Netlify 应用程序和帐户中获取两个配置值。
您刚刚创建的应用程序的 应用程序 ID 可以在您的 Netlify 应用程序的站点详细信息部分找到。
个人访问令牌 允许从您的部署管道访问您的 Netlify 帐户。
在
User Settings > Applications
生成访问令牌。
将您的访问令牌保存在一个安全的地方,因为 Netlify 不允许您在创建后查看该值。
你只能改变它。
在 CircleCI 建立项目 是时候在 CircleCI 上建立你的项目了。
将
.circleci
文件夹添加到项目的根目录。
在其中,添加一个空的
config.yml
文件。
在下一节中,您将向该文件添加配置。
将您的更改推送到 GitHub 项目。
接下来,转到 CircleCI 仪表板上的 Add Projects 页面来添加项目。
点击 设置项目 。
这将加载一个对话框,CircleCI 自动检测您的配置文件。
单击 Let’s Go 首次触发您的构建管道。
构建将失败,因为您尚未向配置中添加代码。
稍后您将执行此步骤。
在编写配置之前,您需要在 CircleCI 项目中添加 Netlify 应用程序 ID 和访问令牌作为环境变量。
确保您的项目是“管道”页面上当前选定的项目。
点击 项目设置 按钮。
在设置页面上,从侧面菜单中选择 环境变量 。
在这个新页面上,点击 添加环境变量 按钮,输入以下信息:
NETLIFY_SITE_ID
是您的 Netlify 应用程序的应用程序 ID。
NETLIFY_ACCESS_TOKEN
是您的网络个人访问令牌。
编写部署配置 该过程的最后一步是编写部署配置。
打开
config.yml
文件并添加以下配置:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: cimg/node:12.16
steps:
- checkout
- run:
name: Update NPM
command: "sudo npm install -g npm"
- restore_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
- run:
name: Install Dependencies
command: npm install
- save_cache:
key: dependency-cache-{{ checksum "package-lock.json" }}
paths:
- ./node_modules
- run:
name: Gatsby Build
command: GATSBY_CPU_COUNT=2 npm run build
- save_cache:
key: gatsby-public-cache-{{ .Branch }}
paths:
- ./public
- run:
name: Deploy to Netlify
command: ./node_modules/.bin/netlify deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_ACCESS_TOKEN --prod --dir=public
workflows:
version: 2
build-deploy:
jobs:
- build:
filters:
branches:
only:
- main
在这个配置中,项目从存储库中签出,项目依赖项被安装和缓存。
缓存完依赖项后,Gatsby build 命令
npm run build
运行,在项目根目录下的
public
目录中创建应用程序的生产版本。
然后缓存该文件夹。
最后,Netlify CLI 使用
$NETLIFY_SITE_ID
和
$NETLIFY_ACCESS_TOKEN
变量部署站点。
然后,工作流配置确保只有
main
分支触发网络部署。
在为 CircleCI 推送这个配置以部署站点之前,请转到项目的
src/pages
文件夹,并将
index.js
文件中的 Gatsby 消息从
you just made a Gatsby site
更改为
you just deployed a Gatsby site with CircleCI
。
这个新消息将显示已部署的应用程序已经发生了变化。
提交你所有的修改并推送到 GitHub。
这将自动触发部署管道和成功的构建。
单击构建以查看部署详细信息。
成功构建后,再次访问您的网站以验证您部署的更改。
它应该显示新消息。
厉害!
要确认 Netlify 没有运行并行部署,请检查 Netlify 部署日志。
生产部署中只有一个部署,第一个部署由分支
netlify-deploy-ignore
触发。
其余的没有显示分支,因为它们是由 CircleCI 管道触发的。
结论 这就是使用 CircleCI 将 Gatsby 站点定制部署到 Netlify 的情况。
这种设置使您的团队能够更好地控制 Gatsby 到 Netlify 的部署,并为您提供在该过程中运行更多自定义步骤所需的灵活性。
如果你喜欢使用 Heroku 而不是 Netlify 作为你的托管平台,我们也有一个关于 T2 使用 CircleCI 管道定制部署 Heroku T3 的教程。
编码快乐!
Fikayo Adepoju 是 LinkedIn Learning(Lynda.com)的作者、全栈开发人员、技术作者和技术内容创建者,精通 Web 和移动技术以及 DevOps,拥有 10 多年开发可扩展分布式应用程序的经验。
他为 CircleCI、Twilio、Auth0 和 New Stack 博客撰写了 40 多篇文章,并且在他的个人媒体页面上,他喜欢与尽可能多的从中受益的开发人员分享他的知识。
你也可以在 Udemy 上查看他的视频课程。
阅读 Fikayo Adepoju 的更多帖子 谷歌云运行 orb | CircleCI 原文: https://circleci.com/blog/gcp-cloudrun-orb/ 这篇文章将展示如何在 CI/CD 管道中使用 Google Cloud Run 平台。
管道将测试应用程序的代码,构建 Docker 映像,并将映像作为 Google Cloud Run 服务部署在 Google 云平台上。
使用的技术 这篇文章假设读者对以下内容有基本的了解; 将项目添加到 CircleCI 本演示中使用的项目可在 本报告 中找到。
如果你想继续下去,叉回购。
然后, 注册一个免费的 CircleCI 账户 ,如果你还没有的话。
按照 在 CircleCI 上设置构建的说明,将项目连接到 CircleCI。
Google 云设置 让我们创建与 Google Cloud Run 平台交互所需的必要凭证。
这些凭证将为我们的 CI/CD 管道提供在谷歌云平台(GCP)上执行命令所需的访问权限。
创建一个 GCP 项目 在 GCP 控制台 中创建一个新项目。
为您的项目取一个容易记忆的名称。
我们想让它易于识别,以便以后容易拆除。
创建后,一定要复制
project id
,因为它不同于
project name
。
获取您项目的凭据 接下来,设置一个服务帐户密钥,您将使用它来创建和管理 GCP 项目中的资源。
按照这里的步骤 创建服务账户密钥 。
选择
JSON
作为按键类型,点击 创建 。
在本地保存这个
.json
文件。
重要安全提示: 保护你的 Google Cloud 凭证不被发布和暴露在公共的 GitHub 存储库中。
您必须非常谨慎地使用此文件中的数据,因为一旦暴露,任何拥有此信息的人都可以登录您的帐户,创建资源,并收取费用。
将凭证的.json文件名添加到项目的.gitignore文件中,作为防止意外发布这些敏感数据的保护层。
cicekci 管道设置 接下来,我们需要更新我们的管道 配置文件 ,以便在我们的 CI/CD 管道中使用 Google Cloud Run 平台。
编码 Google 服务帐户文件 服务帐户文件必须编码成一个
base64
值,以便将该数据作为一个 环境变量 存储在 CircleCI 上。
在终端中运行以下命令,对值进行编码并获得结果:
base64 cicd_demo_gcp_creds.json
该命令的结果将类似于以下内容:
ewogICJ0eXBlIjogInNlcnZpY2VfYWNjb3VudCIsCiAgInByb2plY3RfaWQiOiAiY2ljZC13b3Jrc2hvcHMiLAogICJwcml2YXRlX2tleV9pZCI6ICJiYTFmZDAwOThkNTE1ZTE0NzE3ZjE4NTVlOTY1NmViMTUwNDM4YTQ4IiwKICAicHJpdmF0ZV9rZXkiOiAiLS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tXG5NSUlFdlFJQkFEQU5CZ2txaGtpRzl3MEJBUUVGQUFTQ0JLY3dnZ1NqQWdFQUFvSUJBUURjT1JuRXFla3F4WUlTXG5UcHFlYkxUbWdWT3VzQkY5NTE1YkhmYWNCVlcyZ2lYWjNQeFFBMFlhK2RrYjdOTFRXV1ZoRDZzcFFwWDBxY2l6XG5GdjFZekRJbXkxMCtHYnlUNWFNV2RjTWw3ZlI2TmhZay9FeXEwNlc3U0FhV0ZnSlJkZjU4U0xWcC8yS1pBbjZ6XG5BTVdHZjM5RWxSNlhDaENmZUNNWXorQmlZd29ya3Nob3BzLmlhbS5nc2VydmljZWFjY291bnQuY29tIgp9Cg==
将结果复制到剪贴板。
您将在下一节中使用它。
创建项目变量 为了让这个 CI/CD 管道在 GCP 上执行命令,我们必须在 CircleCI 上创建项目级的 环境变量 。
这些环境变量将在后面的
config.yml
文件中使用。
使用 CircleCI 仪表板创建以下项目级环境变量: GOOGLE_PROJECT_ID :您的 Google Cloud 项目的 项目 ID 。
这个值可以从 Google Cloud Dashboard 中的项目卡中检索到。
GCP _ 项目 _ 密钥 :上一节的 base64 编码结果。
GOOGLE_COMPUTE_ZONE :针对您的部署的区域的 值。
Google Cloud Run(完全托管)与使用 Anthos 的 GKE Google Cloud Run Google Cloud Run 允许你在一个完全托管的环境中运行服务,或者在一个带有 Anthos 的 Google Kubernetes 引擎(GKE)集群上运行服务。
在这篇文章中,我将阐述如何运行完全托管的 Google Cloud Run,以及如何使用 Anthos 在 GKE 集群上运行 Google Cloud Run。
在这两种情况下,我将演示如何使用 Google Cloud Run orb 将 Google Cloud Run 部署集成到您的 CI/CD 管道中。
使用 Google Cloud Run(完全托管)服务创建 CI/CD 管道 现在您已经拥有了在 CircleCI 管道中使用 Google Cloud Run 平台所需的所有元素,请使用以下配置语法更新项目的
config.yml
文件。
version: 2.1
orbs:
gcp-gcr: circleci/gcp-gcr@0.6.1
cloudrun: circleci/gcp-cloud-run@1.0.0
jobs:
build_test:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- run:
name: Install Python Dependencies
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
- run:
name: Run Tests
command: |
pytest
build_push_image_cloud_run_mangaged:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build app binary and Docker image
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
echo ${GCP_PROJECT_KEY} | base64 --decode --ignore-garbage > $HOME/gcloud-service-key.json
echo 'export GOOGLE_CLOUD_KEYS=$(cat $HOME/gcloud-service-key.json)' >> $BASH_ENV
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
pyinstaller -F hello_world.py
docker build -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME:$TAG .
- gcp-gcr/gcr-auth:
gcloud-service-key: GOOGLE_CLOUD_KEYS
google-project-id: GOOGLE_PROJECT_ID
google-compute-zone: GOOGLE_COMPUTE_ZONE
- gcp-gcr/push-image:
google-project-id: GOOGLE_PROJECT_ID
registry-url: "us.gcr.io"
image: $IMAGE_NAME
- cloudrun/deploy:
platform: "managed"
image: "us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME"
service-name: "orb-gcp-cloud-run"
region: $GOOGLE_COMPUTE_ZONE
unauthenticated: true
workflows:
build_test_deploy:
jobs:
- build_test
- build_push_image_cloud_run_mangaged:
requires:
- build_test
云运行(完全受管)配置细分 让我们分解实现 Google Cloud Run orb 的管道语法,并使用 Google Cloud Run(完全托管)服务部署应用程序。
version: 2.1
orbs:
gcp-gcr: circleci/gcp-gcr@0.6.1
cloudrun: circleci/gcp-cloud-run@1.0.0
上面的代码片段将
2.1
声明为 CircleCI 平台的
version
以供使用。
orbs:
键指定了要包含在这个管道中的 orb。
在这个例子中,我将使用
circleci/gcp-gcr@0.6.1
和
circleci/gcp-cloud-run@1.0.0
球体。
我包含了
gcp-gcr
orb 来演示在您的管道中实现多个 orb。
jobs:
build_test:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- run:
name: Install Python Dependencies
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
- run:
name: Run Tests
command: |
pytest
build_push_image_cloud_run_mangaged:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build app binary and Docker image
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
echo ${GCP_PROJECT_KEY} | base64 --decode --ignore-garbage > $HOME/gcloud-service-key.json
echo 'export GOOGLE_CLOUD_KEYS=$(cat $HOME/gcloud-service-key.json)' >> $BASH_ENV
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
pyinstaller -F hello_world.py
docker build -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME:$TAG .
上面的代码片段显示了
jobs:
键,它是一个作业列表,代表了要在管道中执行的一组操作。
这个代码片段指定了两个任务:
build_test:
和
build_push_image_cloud_run_mangaged:
。
build_test
作业安装应用程序依赖项。
然后,它执行项目的单元测试,以确保应用程序在移动到管道中的下一个作业之前通过。
列出的下一个任务是
build_push_image_cloud_run_mangaged:
,创建环境变量,并构建 Docker 映像,该映像将被部署到 Google Cloud Run 服务。
- gcp-gcr/gcr-auth:
gcloud-service-key: GOOGLE_CLOUD_KEYS
google-project-id: GOOGLE_PROJECT_ID
google-compute-zone: GOOGLE_COMPUTE_ZONE
上面的代码片段使用前一节中设置的环境变量,使用
gcp-gcr/gcr-auth:
orb 向 Google 容器注册中心(GCR)进行身份验证。
- gcp-gcr/push-image:
google-project-id: GOOGLE_PROJECT_ID
registry-url: "us.gcr.io"
image: $IMAGE_NAME
在上面的代码片段中,
push-image
命令用于将新创建的图像推送到 GCR,以便在 GCP 使用。
- cloudrun/deploy:
platform: "managed"
image: "us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME"
service-name: "orb-gcp-cloud-run"
region: $GOOGLE_COMPUTE_ZONE
unauthenticated: true
上面的代码片段使用来自
cloudrun
orb 的
deploy
函数来创建和部署 Google Cloud Run 服务,该服务将提供新打包的 Docker 映像。
platform
参数被设置为
managed
,这将服务部署到 GCP 上的完全受管环境中。
下一节将演示如何将 Docker 映像部署到 Google Cloud Run for Anthos。
为 Anthos 在 GKE 上使用 Google Cloud Run 服务创建 CI/CD 管道 上一节演示了如何将应用程序部署到完全托管的 Google Cloud Run 环境中。
在这一节中,我将演示如何为 Anthos 在 GKE 上运行的 Google Cloud 部署一个应用程序。
在下面的管道示例中,
build_test:
作业与之前完全托管的 Google Cloud Run 示例相同,但是请注意名为
build_push_image_cloud_run_gke:
的新作业,它通过 Google Cloud Run orb 将应用程序部署到在 GKE 集群上运行的 Google Cloud Run 服务。
version: 2.1
orbs:
gcp-gcr: circleci/gcp-gcr@0.6.1
cloudrun: circleci/gcp-cloud-run@1.0.0
jobs:
build_test:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- run:
name: Install Python Dependencies
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
- run:
name: Run Tests
command: |
pytest
build_push_image_cloud_run_gke:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build app binary and Docker image
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
echo ${GCP_PROJECT_KEY} | base64 --decode --ignore-garbage > $HOME/gcloud-service-key.json
echo 'export GOOGLE_CLOUD_KEYS=$(cat $HOME/gcloud-service-key.json)' >> $BASH_ENV
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
pyinstaller -F hello_world.py
docker build -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME:$TAG .
- gcp-gcr/gcr-auth:
gcloud-service-key: GOOGLE_CLOUD_KEYS
google-project-id: GOOGLE_PROJECT_ID
google-compute-zone: GOOGLE_COMPUTE_ZONE
- gcp-gcr/push-image:
google-project-id: GOOGLE_PROJECT_ID
registry-url: "us.gcr.io"
image: $IMAGE_NAME
- cloudrun/create_gke_cluster:
cluster-name: $CIRCLE_PROJECT_REPONAME
machine-type: "g1-small"
zone: $GOOGLE_COMPUTE_ZONE
enable-stackdriver-kubernetes: true
scopes: "cloud-platform"
- cloudrun/deploy:
platform: "gke"
image: "us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME"
cluster: $CIRCLE_PROJECT_REPONAME
service-name: $CIRCLE_PROJECT_REPONAME
cluster-location: $GOOGLE_COMPUTE_ZONE
workflows:
build_test_deploy:
jobs:
- build_test
- build_push_image_cloud_run_gke:
requires:
- build_test
云运行 GKE 配置细分 上面显示的管道示例的第一个作业已经在完全托管部分中介绍过了,所以我将跳到新作业
build_push_image_cloud_run_gke:
。
build_push_image_cloud_run_gke:
docker:
- image: circleci/python:3.7.4
steps:
- checkout
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build app binary and Docker image
command: |
echo 'export PATH=~$PATH:~/.local/bin' >> $BASH_ENV
echo ${GCP_PROJECT_KEY} | base64 --decode --ignore-garbage > $HOME/gcloud-service-key.json
echo 'export GOOGLE_CLOUD_KEYS=$(cat $HOME/gcloud-service-key.json)' >> $BASH_ENV
echo 'export TAG=${CIRCLE_SHA1}' >> $BASH_ENV
echo 'export IMAGE_NAME=$CIRCLE_PROJECT_REPONAME' >> $BASH_ENV && source $BASH_ENV
pip install --user -r requirements.txt
pyinstaller -F hello_world.py
docker build -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME -t us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME:$TAG .
- gcp-gcr/gcr-auth:
gcloud-service-key: GOOGLE_CLOUD_KEYS
google-project-id: GOOGLE_PROJECT_ID
google-compute-zone: GOOGLE_COMPUTE_ZONE
- gcp-gcr/push-image:
google-project-id: GOOGLE_PROJECT_ID
registry-url: "us.gcr.io"
image: $IMAGE_NAME
上面的代码片段基于应用程序构建 Docker 图像,并将图像上传到 GCR。
这些操作也与本文完全管理部分演示的 Docker 操作相同。
- cloudrun/create_gke_cluster:
cluster-name: $CIRCLE_PROJECT_REPONAME
machine-type: "g1-small"
zone: "us-east1"
enable-stackdriver-kubernetes: true
scopes: "cloud-platform"
- cloudrun/deploy:
platform: "gke"
image: "us.gcr.io/$GOOGLE_PROJECT_ID/$IMAGE_NAME"
cluster: $CIRCLE_PROJECT_REPONAME
service-name: $CIRCLE_PROJECT_REPONAME
cluster-location: $GOOGLE_COMPUTE_ZONE
workflows:
build_test_deploy:
jobs:
- build_test
- build_push_image_cloud_run_gke:
requires:
- build_test
在上面的代码片段中,管道使用
cloudrun
orb 将 Google Cloud Run 应用部署到 GKE 集群。
cloudrun/create_gke_cluster:
命令创建了一个新的 GKE 集群,Google Cloud Run 应用程序将部署在这里。
接下来,
cloudrun/deploy:
命令将应用程序部署到新创建的 GKE 集群。
该应用程序正在被部署到 Kubernetes 集群,这可能需要几分钟的时间,因此在此过程中请耐心等待。
这将比其他操作花费更长的时间。
包扎 这篇文章展示了如何使用 Google Cloud Run orb 在 CI/CD 管道中自动构建、测试和部署应用程序到 Google Cloud Run 平台。
在 CircleCI 管道中使用 orb 为将应用程序部署到 Google Cloud Run 平台提供了一个干净、简洁且经过充分测试的解决方案。
感谢阅读!
Kubernetes 管理的微服务| CircleCI 原文: https://circleci.com/blog/get-an-out-of-the-box-solution-for-the-most-important-services-in-your-pipeline/ 整体架构是现代应用规模和复杂性持续增长的结果。
当应用程序很小的时候,诸如消除 bug 或添加新功能、更新 UI 或 UX,或者实现额外的安全性等事情都是由同一个团队完成的。
将会有一个中央存储库,在一个地方存放满足所有业务需求的所有代码。
随着应用程序的增长,不同的职责开始被分配给具有特定角色和功能的各个团队。
这些单独的团队有特定的角色,但是他们仍然需要在同一个集中的代码库上操作,这导致了跨团队的依赖性和代码不能合并的可能性。
微服务的引入有望通过将应用程序分成独立的服务来消除团队之间的交叉工作,每个服务提供一个特定的功能。
这种架构最有益的方面是快速扩展、增加可靠性和更快的开发时间。
管理您的微服务 容器用于在微服务之间提供必要的隔离层。
随着应用程序的复杂性及其在云中的服务之间的交互的增长,为控制多个服务如何在多个容器中运行而开发的编排工具成为了一种需求。
截至目前,Kubernetes 是唯一一个可以从亚马逊(EKS)、微软(AKS)和谷歌(GKE)这三大云服务提供商那里获得的容器编排解决方案。
由于这些项目提供的灵活性、可靠性和透明性,越来越依赖云服务来部署应用程序的同时,OSS 的使用也在增加。
Kubernetes 是开源标准组织 CNCF 最受欢迎的项目,并且已经证明是编排工具的领导者。
微服务方法为您的架构带来的好处是有代价的。
一个应用程序可能需要很多很多服务,包括来自第三方和/或 OSS 项目的专有服务。
单独来说,这些服务可能更容易管理,或者在第三方和 OSS 的情况下,可能由其他人来管理它们,但是所有这些服务的行为和操作的互联方式可能非常复杂。
Kubernetes 解决方案不仅难以实现,而且难以更新和调试。
这就是您选择使用哪种 CI 工具如此重要的原因。
您需要一个 CI 工具,它允许您将 Kubernetes 与您已经使用的所有服务一起使用。
面临一个共同的问题 使用 CircleCI 的 orbs,您可以为您管道中最重要的服务获得开箱即用的解决方案。
orb 是 CircleCI config 的可重用、可共享的开源包,支持这些服务的即时集成。
它们允许针对常见问题的众包解决方案。
我们这里关注的是执行主要操作的 orb,比如部署到 GKE,但是其他的也允许发送关于您的项目的定制 Slack 通知。
关键的好处是,您可以访问所有这些服务,而不必自己学习集成它们。
要在登录 GCP、构建和部署 Docker 映像,然后将该映像部署到 GKE 集群之后向 USERID1 发送自定义 Slack 通知,只需通过导入 Slack 和 GCP-GKE orb 来更新它们的
.circleci/config.yml
:
orbs:
slack: circleci/slack@x.y.z
gke: circleci/gcp-gke@x.y.z
然后运行
gke/publish-and-rollout-image
后的
job
中的
slack/notify
命令,并带有所需的参数:
jobs:
- build
- gke/publish-and-rollout-image:
deployment: k8s-deployment-name
container: container-name
image: image-name
- slack/notify:
mentions: ‘USERID1’
message: ‘deployment to GKE’
虽然一个精明的高级 DevOps 工程师可能能够将此功能编写到他们选择的 CI 工具的配置中,但 CircelCI 的 orbs 消除了这种开发,并以一个正在工作并经过测试的社区支持的集成来取代它。
slack/notify
命令本身超过 125 行代码。
整个松弛球体几乎有 500 行。
没有一个使用 CircleCI 的团队需要自己编写代码。
CircleCI 球体是使用 Kubernetes 的最简单方法。
你是只想安装
kubectl
还是
kops
?
Kubernetes orb 可以帮你安装。
您是否使用 Kubernetes 部署到 GKE?
在上面的例子中,我展示了如何用 GCP-GKE 球体 轻松实现这一点。
也许你正在使用 Helm 来部署你的 Kubernetes。
我们有 头盔球 来做这件事。
无论您如何使用 Kubernetes,CircleCI 是唯一可以让您快速启动并运行的 CI 工具,这样您就可以将时间花在开发项目上,而不是部署管道上。
你能做什么 你还想从当前的 Kubernetes orb 中看到其他的特性吗?
由于 orb 是开源的,您可以通过批准和合并您的 PR 来向现有 orb 添加功能。
你也可以制造一个问题,看看是否有社区支持开发。
你有没有一个用例让你觉得与当前的 Kubernetes orbs 不同?
您可以 自己创作一个 并将其贡献给社区。
我们甚至发布了为 orb 创建自动化构建、测试和部署管道的最佳实践( 第 1 部分 和 第 2 部分 )来帮助您。
查看 orbs 注册表 中所有可用的 Orbs。
微服务允许您的团队利用第三方服务和 OSS,消除了内部开发这些常用工具和资源的需要。
有了 orb,您的团队只需要知道如何使用这些服务,而不需要知道如何集成或管理它们。
Google bin auth-Kubernetes | circle ci 原文: https://circleci.com/blog/get-started-with-google-binary-authorization/ CircleCI 的二进制授权 orb 的演练 在 Next’19 上,Google 宣布 二进制授权的正式发布,这是一种针对部署在 Google Kubernetes 引擎上的容器图像的安全控制,CircleCI 是其发布合作伙伴。
我们的 二进制授权 orb 简化了使用 CircleCI 构建、测试和部署的映像的验证过程,确保只有那些在 CI/CD 过程中由可信机构签名的映像才能在 GKE 上运行。
二进制授权,像 Grafeas 的 Kritis ,它的开源、云不可知的对等物,使用一组不同的 RESTful 资源来管理和认证软件发布过程: Policies :描述在被特定证明者授权后,哪些容器映像可以被部署到特定的 Kubernetes 集群的规则集; 证明者 :通过创建和签署证明来验证容器映像的部署准备状态的指定方,机器或人; 证明 :证明者证明单个映像满足部署所需的所有条件的声明。
这些概念与 CircleCI 的功能相吻合,如 受限上下文 ,它将秘密和环境变量的集合传递给特定的用户组,以及 手动批准 工作。
总之,这些资源允许开发-运营团队根据他们的需求和规范,使用自动和手动检查的组合来平衡部署时间和可靠性/安全性,从而精心设计全面的软件供应链安全流程。
CircleCI 的 orb 让二进制授权很容易上手。
一个作业
create-attestation
可以引导您完成创建策略、证明者和证明的整个过程。
使用 orb 构建一个全新的 GKE 集群,或者直接引用一个已经存在的集群。
即时制定政策,或者自己制定政策。
orb 甚至将生成并存储 PGP 密钥对,由证明者用来签署证明(通过 Google 的云密钥管理服务对非对称密钥的支持是 orb 路线图的下一步)。
唯一的先决条件是谷歌云平台中的单个项目,或者对于一个 多项目设置 ,三个独立的 GCP 项目(部署者、证明者、证明)。
与 CircleCI 的 Google Container Registry orb 配对,二进制授权 orb 可以在 YAML 的几行代码中提供完整的部署解决方案,如这个
simple-deploy-attested-image
示例 所示:
version: 2.1
orbs:
gcp-gcr: circleci/gcp-gcr@x.y.z
bin-authz: circleci/gcp-binary-authorization@x.y.z
workflows:
push_sign_deploy:
jobs:
- gcp-gcr/build_and_push_image:
context: your-context # context containing any required env vars
image: your-image # your image name
registry-url: gcr.io # default value, here for clarity
tag: your-tag # default value
- bin-authz/create-attestation:
context: your-context
attestor: $CIRCLE_USERNAME # default value
keypair-email: email.address@used.to.generate.keypair.com
gke-cluster-name: your-GKE-cluster-name
use-note-file: true
note-filepath: your-container-analysis-note.json
use-policy-file: true
policy-filepath: your-binauthz-policy-file.yaml
image-path: gcr.io/$GOOGLE_PROJECT_ID/your-image
image-tag: your-tag
requires: [gcp-gcr/build_and_push_image]
deployment-steps:
- run: |
kubectl run your-server \
--image gcr.io/$GOOGLE_PROJECT_ID/your-image@$YOUR_IMAGE_DIGEST \
--port 8080
二进制授权 orb 有大量的参数,但是不要被淹没——它们被设计为合理的默认值,在大多数用例中最大限度地减少了样板文件。
如需进一步指导,请参见 orb 的其他 使用示例 (其 GitHub 库 也有额外的说明)。
最后,由于所有 CircleCI orbs 都是开源的,如果你想在这个 orb 中看到其他东西,我们总是欢迎 问题 和 拉请求 — 我们活跃的 orb 开发者和用户社区 可以帮助解决任何关于这个或任何其他 orb 的问题。
CircleCI - CircleCI 上的黄瓜入门 原文: https://circleci.com/blog/getting-started-with-cucumber-on-circleci/ **来自出版商的说明:**您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。
尝试在 我们的文档 或 博客 中搜索最新信息。
Cucumber 是一个行为驱动开发(BDD)工具,用于开发 web 应用程序的验收测试。
出于本文的目的,我将假设您的 CircleCI 设置运行顺利,并将重点放在【Cucumber 是什么,如何很好地使用它,以及如何将其与您的 CircleCI 设置集成。
黄瓜到底是什么?
Cucumber 是一个 BDD 工具,你可以用它来验证你的应用程序的功能。
cumber 测试是用一种叫做“ Gherkin 的语言编写的,它用一种自然的、类似语言的语法来表达期望的行为和软件的预期结果。
BDD 不同于传统的自动化测试,因为测试既不是代码级的单元测试,也不是与 UI 交互的应用程序级脚本。
BDD 测试表达了测试的一组先决条件,然后表达了测试本身,然后是预期的结果。
结合一些文档(可以很容易地以一种小黄瓜友好的方式格式化),一组 Cucumber BDD 测试也可以作为一组全面的文档,供产品所有者或开发人员/QA 人员在将来审查。
对于主要语言不是英语的团队,小黄瓜支持 多种语言 。
当时给定的 黄瓜测试的基本结构是“给定-何时-然后”格式。
“给定”是测试的设置。
您可以使用 GIVEN 来设置测试所需的数据类型。
您使用 GIVEN 来设置状态。
“何时”是测试的动作。
WHEN 描述了您希望针对在测试的给定部分中设置的状态执行的操作。
“然后”是预期的结果。
你用 THEN 来陈述你希望测试的结果是什么。
请注意,GIVEN、WHEN 和 THEN 都支持使用“and”关键字的多个步骤。
这里有一个例子: 鉴于简有活跃的档案 她的资料显示萨姆是她的朋友 她的档案里没有朋友比尔 当她输入新的公共状态更新时 然后,Sam 应该会看到她新的公共状态更新 比尔应该看不到她新的公开状态更新 功能和场景 一大堆“给定时间”的步骤很快就会变得笨拙。
Gherkin 提供了一种将测试组织成特性和场景的方法。
一个特性是一个高层次的类别,它很好地映射到一个用户故事或者你的团队用于基本开发单元的任何东西。
一个特性由一个或多个场景组成,一个场景由给定的 When Then 步骤组成。
(上面的给定时间示例是一个场景。
)你可以把一个场景想象成一个测试用例,把一个特性想象成一组逻辑测试用例。
步骤定义和挂钩 该工具需要一些后端编码,以便正确运行其测试。
Given-When-Then 中的每一步都需要一个步骤定义才能执行。
您可以使用与您的步骤相匹配的正则表达式,以及当正则表达式匹配时要执行的 Ruby 代码块来设置步骤定义。
步骤定义可以跨场景重用。
钩子是您在测试运行之前和之后设置和拆除测试状态的方式,特别是为了保持测试数据库的良好状态。
与 CircleCI 集成 将您的黄瓜测试与 CircleCI 集成非常简单。
Cucumber 有一些用于测试结果输出的格式化程序插件。
使用 JUnit 格式化程序并配置 Cucumber 输出到
$CIRCLE_TEST_REPORTS/cucumber
目录。
CircleCI 文档页面 建议将以下 YAML 添加到您的 circle.yml:
test:
override:
- mkdir -p $CIRCLE_TEST_REPORTS/cucumber
- bundle exec cucumber --format junit --out $CIRCLE_TEST_REPORTS/cucumber/junit.xml
很简单。
如果您喜欢 JSON 格式化程序,文档页面还显示了如何配置 circle.yml 文件。
这个配置允许 CircleCI 查看您的黄瓜测试是通过还是失败,并且您可以配置 CircleCI 如何响应失败的黄瓜测试。
Cucumber 是一个强大的工具,用来表达软件系统的期望行为,并将其集成到 CircleCI 构建中,可以为您的开发过程带来很多价值。
Kubernetes 入门:如何设置您的第一个集群 原文: https://circleci.com/blog/getting-started-with-kubernetes-how-to-set-up-your-first-cluster/ Kubernetes 是部署和扩展复杂分布式系统的优秀工具,但众所周知,开始使用 Kubernetes 是一个挑战。
大多数 Kubernetes 教程使用类似于 Minikube 这样的工具来帮助您入门,但它并没有教您多少关于配置生产就绪集群的知识。
在本文中,我们将采用一种不同的方法,并向您展示如何使用亚马逊弹性 Kubernetes 服务(亚马逊 EKS)和 Terraform 建立一个真实的、生产就绪的 Kubernetes 集群。
介绍 Terraform Hashicorp 的 Terraform 是一个基础设施即代码(IaC)解决方案,允许您以声明方式定义您的云基础设施的所需配置。
使用 Terraform CLI,您可以在本地或作为自动化 CI/CD 管道的一部分提供此配置。
Terraform 类似于云平台提供的配置工具,如 AWS CloudFormation 或 Azure Resource Manager ,但它的优势是与提供商无关。
如果你不熟悉 Terraform,我们建议你首先阅读他们的 AWS 入门指南,了解最重要的概念。
定义基础设施 让我们构建一个 Terraform 配置,逐步配置亚马逊 EKS 集群和 AWS 虚拟私有云(VPC)。
创建包含以下内容的
main.tf
文件:
provider "aws" {
region = "eu-west-1"
}
data "aws_availability_zones" "azs" {
state = "available"
}
locals {
cluster_name = "eks-circleci-cluster"
}
在这个文件中,我们首先设置 AWS 提供者,将区域设置为
eu-west-1
。
请随意将其更改为任何其他 AWS 区域。
AWS 提供者将检查各个地方是否有有效的 凭证 可以使用,所以一定要设置这些凭证。
然后,我们获取可以在该区域使用的可用性区域。
这使得更改区域变得更加容易,而不需要任何其他更改。
我们还将集群名称设置为一个变量,因为我们将多次使用它。
您也可以更改该值。
让我们来配置 VPC。
将以下内容添加到文件中:
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 2.48"
name = "eks-circleci-vpc"
cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.azs.names, 0, 2)
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.3.0/24", "10.0.4.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
tags = {
"kubernetes.io/cluster/${local.cluster_name}" = "shared"
}
public_subnet_tags = {
"kubernetes.io/cluster/${local.cluster_name}" = "shared"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"kubernetes.io/cluster/${local.cluster_name}" = "shared"
"kubernetes.io/role/internal-elb" = "1"
}
}
通过使用 AWS VPC 模块 ,我们极大地简化了 VPC 的创建。
我们将 VPC 配置为使用部署 Terraform 模板的区域中的前两个 az。
这是 EKS 要求的 az 的最小数量。
为了节约成本,我们只配置一个 NAT 网关。
AWS VPC 模块将正确地设置路由表,以通过这个单一 NAT 网关路由所有内容。
我们还为 VPC 启用了 DNS 主机名 ,因为这是 EKS 的 要求。
最后,我们设置 EKS 所需的标记,以便它可以发现其子网,并知道在哪里放置公共和私有负载平衡器。
接下来,让我们使用 AWS EKS 模块 来配置 EKS 集群。
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 12.2"
cluster_name = local.cluster_name
cluster_version = "1.17"
subnets = module.vpc.private_subnets
vpc_id = module.vpc.vpc_id
worker_groups = [
{
instance_type = "t3.large"
asg_max_size = 1
}
]
}
我们将集群配置为使用我们已经创建的 VPC,并使用一个
t3.large
实例定义一个单独的 worker 组。
这将足以在集群中创建一个简单的测试资源,同时最小化成本。
最后,将以下配置添加到文件中:
data "aws_eks_cluster" "cluster" {
name = module.eks.cluster_id
}
data "aws_eks_cluster_auth" "cluster" {
name = module.eks.cluster_id
}
provider "kubernetes" {
version = "~> 1.9"
host = data.aws_eks_cluster.cluster.endpoint
cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
token = data.aws_eks_cluster_auth.cluster.token
load_config_file = false
}
output "kubectl_config" {
description = "kubectl config that can be used to authenticate with the cluster"
value = module.eks.kubeconfig
}
我们从亚马逊 EKS 集群配置中获取一些数据,并将 Terraform Kubernetes 提供者 配置为通过集群进行身份验证。
当 AWS EKS 模块在集群中创建 configmap 时,该提供程序也在其中使用,这将授予当前通过身份验证的 AWS 用户对集群的管理权限。
这是一个 EKS 特有的 方法,用于授权 AWS 实体访问集群。
最后,
output
块将显示向群集进行身份验证所需的信息——在我们调配完群集后会有更多相关信息。
我们现在有了一个地形配置,完全可以启动 EKS 集群。
让我们应用这个配置,并在集群中创建一个测试资源。
供应 Kubernetes 集群 配置您的 shell 以 认证 terra form AWS 提供商。
您的工作目录与我们刚刚创建 Terraform 文件的目录相同,初始化工作空间:
$ terraform init
Initializing modules...
Downloading terraform-aws-modules/eks/aws 12.2.0 for eks...
- eks in .terraform/modules/eks/terraform-aws-eks-12.2.0
- eks.node_groups in .terraform/modules/eks/terraform-aws-eks-12.2.0/modules/node_groups
Downloading terraform-aws-modules/vpc/aws 2.48.0 for vpc...
- vpc in .terraform/modules/vpc/terraform-aws-vpc-2.48.0
Initializing the backend...
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "random" (hashicorp/random) 2.3.0...
- Downloading plugin for provider "aws" (hashicorp/aws) 3.3.0...
- Downloading plugin for provider "kubernetes" (hashicorp/kubernetes) 1.12.0...
- Downloading plugin for provider "local" (hashicorp/local) 1.4.0...
- Downloading plugin for provider "null" (hashicorp/null) 2.1.2...
- Downloading plugin for provider "template" (hashicorp/template) 2.1.2...
Terraform has been successfully initialized!
[...]
terraform init
命令下载配置文件中包含的所有提供程序。
我们现在可以应用和调配 VPC 和集群。
注意 : 这将需要 10 到 15 分钟才能完成。
$ terraform apply
[...]
Plan: 40 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
[...]
Apply complete! Resources: 40 added, 0 changed, 0 destroyed.
Outputs:
[...]
就这样:我们现在有了一个完全启动并运行的亚马逊 EKS 集群。
让我们部署一个 pod,并通过负载平衡器公开它,以确保我们的集群按预期工作。
要使用集群进行身份验证,您需要安装 kubectl 和 aws-iam-authenticator 。
在
terraform apply
末尾的
Outputs
部分包含了你可以添加到你的 kubeconfig 文件(或者一个临时的新文件)中的细节。
完成后,让我们创建一个新的部署并公开它:
$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl expose deployment/nginx --port=80 --type=LoadBalancer
service/nginx exposed
$ kubectl get service nginx
从最终输出中获取
EXTERNAL-IP
值——这是 AWS 负载平衡器的 DNS 条目。
DNS 传播可能需要几分钟时间。
当它打开时,你会看到“欢迎使用 nginx!
”页面。
成功!
更简单的方法:CircleCI aws-eks orb 您已经学习了一种构建亚马逊 EKS 集群的方法。
然而,组装配置并使其保持最新是——并将继续是——相当多的(手动)工作。
幸运的是,CircleCI aws-eks orb 可以帮上忙。
CircleCI orbs 包含预打包的配置代码,使其更容易与其他开发工具集成。
这只是 宝珠注册表 中众多可用宝珠中的一个。
aws-eks orb 可以自动启动、测试和拆除亚马逊 eks 集群。
您可以使用它来创建强大的工作流,在干净、完全隔离的临时 EKS 集群中测试应用程序。
让我们试一试。
CircleCI 帐户是开始使用 orb 的主要先决条件。
注册 CircleCI 并在 CircleCI 项目中将 Git 存储库连接到您的帐户。
在这个项目中,转到 设置 下的 环境变量 选项卡,添加以下变量,以便项目可以通过 AWS 进行身份验证。
确保 AWS IAM 用户拥有创建 EKS 集群及其依赖项所需的权限。
将 region 变量的值设置为要预配集群的区域。
接下来,在 Git 存储库中创建
.circleci/config.yml
文件,并向其中添加以下内容:
version: 2.1
orbs:
aws-eks: circleci/aws-eks@1.0.0
kubernetes: circleci/kubernetes@0.11.1
jobs:
test-cluster:
executor: aws-eks/python3
parameters:
cluster-name:
description: |
Name of the EKS cluster
type: string
steps:
- kubernetes/install
- aws-eks/update-kubeconfig-with-authenticator:
cluster-name: << parameters.cluster-name >>
- run:
command: |
kubectl get services
name: Test cluster
workflows:
deployment:
jobs:
- aws-eks/create-cluster:
cluster-name: my-first-cluster
- test-cluster:
cluster-name: my-first-cluster
requires:
- aws-eks/create-cluster
- aws-eks/delete-cluster:
cluster-name: my-first-cluster
requires:
- test-cluster
该文件配置了一个包含三个作业的工作流: 使用来自 aws-eks orb 的
create-cluster
命令,使用 eksctl 实用程序创建集群及其依赖项 运行一个简单的测试来验证集群是否按预期工作 破坏集群 将这个文件提交到您的存储库中,CircleCI 将自动启动工作流,这将需要 15 到 20 分钟。
当然,您可以在集群创建之后添加所有需要的步骤,比如提供资源和运行测试。
与我们前面讨论的创建亚马逊 eks 集群的 Terraform 方法相比,aws-eks orb 大大简化并加快了管理 EKS 集群生命周期的过程。
EKS 集群本身的复杂性,以及它的依赖项(比如 VPC)的配置,都被完全抽象掉了。
这是一个低维护的解决方案,它允许您将精力集中在构建具有自动化测试的有价值的持续集成工作流上。
后续步骤 您已经了解到创建您的第一个 Kubernetes 集群并不困难或可怕。
Terraform 提供了一种定义集群基础设施的简单方法。
通过简单的 CLI 命令,您可以轻松调配已定义的基础架构。
CircleCI orb 进一步简化了这一过程。
无需编写自己的 Terraform 代码或运行任何命令,您也可以达到相同的最终结果。
学习这个的最好方法就是自己动手。
首先使用 Terraform 代码及其 CLI 手动创建集群。
然后,尝试 CircleCI,看看使用 aws-eks orb 创建一个集群是多么容易和快速。
Sander 是一名对自动化充满热情的云工程师。
他喜欢解决技术和组织方面的挑战,以帮助公司在云中用更少的资源做更多的事情。
这包括支持开发人员自治、平台工程和云原生解决方案等主题。
他主要通过整合概念和工具来实现这一点,如代码基础设施、监控和日志记录、安全性和 CI/CD。
Nest.js APIs | CircleCI 持续集成入门 原文: https://circleci.com/blog/getting-started-with-nestjs-and-automatic-testing/ 本教程涵盖: 设置和连接 Nest.js 应用程序 使用 Nest.js 应用程序创建产品 编写、运行和自动化测试 Nest.js 是一个用 TypeScript 构建的可伸缩、高效的服务器端 Node.js 框架。
创建 Nest.js 是为了向 Node.js 开发环境提供一种结构化设计模式。
它的灵感来自于 Angular.js ,并在引擎盖下使用了 Express.js 。
Nest.js 与大多数 Express.js 中间件兼容。
在本教程中,我将带领您使用 Nest.js 构建一个 RESTful API。
本教程将让您熟悉 Nest.js 的基本原理和构建块。
我还将演示为每个 API 端点编写测试的推荐方法。
我将通过向您展示如何使用 CircleCI 自动化测试过程来结束本教程。
先决条件 要想从本教程中获得最大收益,您需要具备以下几点: 我们的教程是平台无关的,但是使用 CircleCI 作为例子。
如果你没有 CircleCI 账号,请在 注册一个免费的 。
我们在本文中构建的 RESTful API 将提供端点来创建带有名称、描述和价格的产品。
我们将编辑、删除和检索单个产品,还将检索保存在数据库中的整个产品列表。
本教程使用 MySQL 作为首选的关系数据库,并将其与 TypeORM 结合使用。
但是 Nest.js 是数据库不可知的,所以您可以选择使用您喜欢的任何数据库。
你可以在这里找到更多关于数据库和 Nest.js 的细节。
设置 Nest.js 应用程序 运行以下命令创建新的应用程序:
nest new nest-starter-testing
运行
nest
命令后,会提示您选择一个包管理器。
选择
npm
并按回车键开始安装 Nest.js。
该过程在
nest-starter-testing
文件夹中创建一个新项目,并安装其所需的所有依赖项。
在运行应用程序之前,使用
npm
安装一个 验证库 ,您将在本教程的后面使用它。
npm install class-validator --save
进入应用程序文件夹,使用命令启动应用程序:
// move into the project
cd nest-starter-testing
// start the server
npm run start:dev
这将在默认的
3000
端口上启动应用程序。
在您喜爱的浏览器中导航至
http://localhost:3000
进行查看。
配置 Nest.js 并将其连接到数据库 TypeORM 是一个流行的对象关系映射器(ORM ),用于 TypeScript 和 JavaScript 应用程序。
为了便于与 Nest.js 应用程序集成,您需要为它安装一个附带的包,以及 MySQL 的 Node.js 驱动程序。
为此,通过按 CTRL + C 停止应用程序运行,然后运行以下命令:
npm install --save @nestjs/typeorm typeorm mysql
当安装过程完成时,您可以将
TypeOrmModule
导入到应用程序的根目录中。
更新 TypeScript 根模块 Nest.js 的构建块,模块是用
@Module
修饰的 TypeScript 文件。
模块提供了 Nest.js 用来组织应用程序结构的元数据。
./src/app.module.ts
中的根模块是顶层模块。
Nest.js 建议将一个大型应用程序分解成多个模块。
它有助于维护应用程序的结构。
要创建与数据库的连接,打开
./src/app.module.ts
文件并用以下代码替换其内容:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: DB_USER,
password: DB_PASSWORD,
database: 'test_db',
entities: [join(__dirname, '**', '*.entity.{ts,js}')],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
注 : 用你的证件 代替
DB_USER
和
DB_PASSWORD
。
我们通过将
TypeOrmModule
导入到根
AppModule
中并指定连接选项,建立了与数据库的连接。
其中包括数据库详细信息和实体文件的存储目录。
我将在下一节更详细地介绍实体文件。
配置数据库连接 在本教程开始的先决条件中,我提到了 MySQL 下载页面。
下载后,您需要配置数据库,使其适用于此应用程序。
在您的终端中,通过运行以下命令登录 MySQL:
mysql -u root -p
输入您在 MySQL 安装过程中设置的密码。
现在运行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
用您的密码替换“密码”。
该命令为 MySQL 的 Node.js 驱动程序设置首选身份验证。
要创建数据库,请运行:
CREATE DATABASE test_db;
为 Nest.js 应用程序创建产品模块、服务和控制器 现在您已经配置了数据库连接,我们将开始为应用程序创建更多的结构。
生成模块 首先为
Product
生成一个模块。
这将是一个新模块,用于对与产品相关的所有项目进行分组。
首先运行以下命令:
nest generate module product
上面的命令将在
src
目录下创建一个新的
product
文件夹,在
product.module.ts
文件中定义
ProductModule
,并通过导入新创建的
ProductModule
自动更新
app.module.ts
文件中的根模块。
./src/product/product.module.ts
文件暂时为空,如下所示:
import { Module } from '@nestjs/common';
@Module({})
export class ProductModule {}
创建实体 为了为 Nest.js 应用程序创建合适的数据库模式,TypeORM 支持创建实体。
实体是映射到特定数据库表的类。
在这种情况下,它是产品表。
按照 Nest.js 应用程序的正确结构,在
src/product
文件夹中创建一个新文件,并将其命名为
product.entity.ts
。
然后将这段代码粘贴到其中:
import { PrimaryGeneratedColumn, BaseEntity, Column, Entity } from 'typeorm';
@Entity()
export class Product extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
description: string;
@Column()
price: string;
}
使用从
typeorm
模块导入的装饰器,我们为产品表创建了四列。
其中包括唯一标识产品的主键列。
创建数据传输对象 数据传输对象(DTO)有助于为进入应用程序的数据创建和验证正确的数据结构。
例如,当您从前端向 Node.js 后端发送 HTTP POST 请求时,您需要从表单中提取发布的内容,并将其解析为后端代码可以轻松使用的格式。
DTO 帮助指定从请求体中提取的对象的形状,并提供了一种轻松插入验证的方法。
要为该应用程序设置 DTO,请在
src/product
目录中创建一个新文件夹,并将其命名为
dto
。
接下来,在新创建的文件夹中创建一个文件,并将其命名为
create-product.dto.ts
。
使用以下内容:
import { IsString } from 'class-validator';
export class CreateProductDTO {
@IsString()
name: string;
@IsString()
description: string;
@IsString()
price: string;
}
这里,我们定义了一个表示
CreateProductDTO
的类,还添加了一些验证来确保字段的数据类型是 字符串 。
接下来,我们将创建一个存储库来帮助将数据直接保存到应用程序数据库中。
创建自定义存储库 通常,像 TypeORM 这样的 ORM 中的存储库主要作为持久层。
它包含的方法有: 这有助于与应用程序的数据库进行通信。
在本教程中,我们将为我们的产品实体创建一个扩展 TypeORM 基本存储库的自定义存储库,并为特定查询创建一些自定义方法。
首先导航到
src/product
文件夹,创建一个名为
product.repository.ts
的新文件。
完成后,将以下内容粘贴到其中:
import { Repository, EntityRepository } from 'typeorm';
import { Product } from './product.entity';
import { CreateProductDTO } from './dto/create-product.dto';
@EntityRepository(Product)
export class ProductRepository extends Repository {
public async createProduct(
createProductDto: CreateProductDTO,
): Promise {
const { name, description, price } = createProductDto;
const product = new Product();
product.name = name;
product.description = description;
product.price = price;
await product.save();
return product;
}
public async editProduct(
createProductDto: CreateProductDTO,
editedProduct: Product,
): Promise {
const { name, description, price } = createProductDto;
editedProduct.name = name;
editedProduct.description = description;
editedProduct.price = price;
await editedProduct.save();
return editedProduct;
}
}
从上面的代码中,我们定义了两个方法:
createProduct()
:该方法将
createProductDto
类作为参数,该类将用于提取 HTTP 请求的主体。
然后我们析构
createProductDto
,用这些值创造一个新产品。
editProduct
:在这里,需要编辑的产品的详细信息被传递给这个方法,根据客户端的新值,指定的详细信息将被相应地更新并保存在数据库中。
生成 Nest.js 服务 服务,也称为提供者,是 Nest.js 中的另一个构建块,被归类到关注点分离原则下。
它旨在处理和抽象复杂的业务逻辑,并返回适当的响应。
Nest.js 中的所有服务都用
@Injectable()
decorator 修饰,这使得将服务注入任何其他文件变得容易,比如控制器和模块。
使用以下命令为产品创建服务:
nest generate service product
运行上面的命令后,您将在终端上看到以下输出:
CREATE /src/product/product.service.spec.ts (467 bytes)
CREATE /src/product/product.service.ts (91 bytes)
UPDATE /src/product/product.module.ts (167 bytes)
nest
命令在
src/product
文件夹中创建了两个新文件。
这些是:
product.service.spec.ts
文件将用于为将在产品服务文件中创建的方法编写单元测试。
product.service.ts
文件保存了应用程序的所有业务逻辑。
nest
命令还导入了新创建的服务,并将其添加到了
product.module.ts
文件中。
接下来,您将使用创建和检索所有产品的方法填充
product.service.ts
文件,以及获取、更新和删除特定产品的细节。
打开文件并用以下内容替换其内容:
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Product } from './product.entity';
import { CreateProductDTO } from './dto/create-product.dto';
import { ProductRepository } from './product.repository';
@Injectable()
export class ProductService {
constructor(
@InjectRepository(ProductRepository)
private productRepository: ProductRepository,
) {}
public async createProduct(
createProductDto: CreateProductDTO,
): Promise {
return await this.productRepository.createProduct(createProductDto);
}
public async getProducts(): Promise {
return await this.productRepository.find();
}
public async getProduct(productId: number): Promise {
const foundProduct = await this.productRepository.findOne(productId);
if (!foundProduct) {
throw new NotFoundException('Product not found');
}
return foundProduct;
}
public async editProduct(
productId: number,
createProductDto: CreateProductDTO,
): Promise {
const editedProduct = await this.productRepository.findOne(productId);
if (!editedProduct) {
throw new NotFoundException('Product not found');
}
return this.productRepository.editProduct(createProductDto, editedProduct);
}
public async deleteProduct(productId: number): Promise {
await this.productRepository.delete(productId);
}
}
在这里,我们导入了应用程序所需的模块,并创建了单独的方法来: 创建新产品:
createProduct()
获取所有创建的产品:
getProducts()
检索单个产品的详细信息:
getProduct()
编辑特定产品的详细信息:
editProduct()
删除单个产品:
deleteProduct()
值得注意的是,我们将之前创建的
ProductRepository
注入到这个服务中,以便轻松地与数据库进行交互和通信。
以下是显示这一点的文件片段:
...
constructor(
@InjectRepository(ProductRepository)
private productRepository: ProductRepository,
) {}
...
只有当我们也将
ProductRepository
导入到产品模块中时,这才起作用。
我们将在教程的后面做这件事。
生成 Nest.js 控制器 Nest.js 中控制器的职责是接收和处理来自应用程序客户端的传入 HTTP 请求,并根据业务逻辑返回适当的响应。
路由机制由附着在每个控制器顶部的装饰器
@Controller()
控制,通常决定哪个控制器接收哪个请求。
要为我们的项目创建新的控制器文件,请从终端运行以下命令:
nest generate controller product --no-spec
您将看到以下输出。
CREATE /src/product/product.controller.ts (103 bytes)
UPDATE /src/product/product.module.ts (261 bytes)
因为我们不会为这个控制器编写测试,所以我们使用了
--no-spec
选项来指示
nest
命令不要为控制器生成
.spec.ts
文件。
打开
src/product/product.controller.ts
文件,将其代码替换为:
import {
Controller,
Post,
Body,
Get,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { ProductService } from './product.service';
import { CreateProductDTO } from './dto/create-product.dto';
import { Product } from './product.entity';
@Controller('product')
export class ProductController {
constructor(private productService: ProductService) {}
@Post('create')
public async createProduct(
@Body() createProductDto: CreateProductDTO,
): Promise {
const product = await this.productService.createProduct(createProductDto);
return product;
}
@Get('all')
public async getProducts(): Promise {
const products = await this.productService.getProducts();
return products;
}
@Get('/:productId')
public async getProduct(@Param('productId') productId: number) {
const product = await this.productService.getProduct(productId);
return product;
}
@Patch('/edit/:productId')
public async editProduct(
@Body() createProductDto: CreateProductDTO,
@Param('productId') productId: number,
): Promise {
const product = await this.productService.editProduct(
productId,
createProductDto,
);
return product;
}
@Delete('/delete/:productId')
public async deleteProduct(@Param('productId') productId: number) {
const deletedProduct = await this.productService.deleteProduct(productId);
return deletedProduct;
}
}
在这个文件中,我们导入了必要的模块来处理 HTTP 请求,并将之前创建的
ProductService
注入到控制器中。
这是通过构造函数使用已经在
ProductService
中定义的函数来完成的。
接下来,我们创建了这些异步方法:
createProduct()
方法用于处理客户端发送的 POST HTTP 请求,以创建新产品并将其保存在数据库中。
getProducts()
方法从数据库中获取完整的产品列表。
getProduct()
方法将
productId
作为参数,并使用它从数据库中检索具有唯一
productId
的产品的详细信息。
editProduct()
方法用于编辑特定产品的详细信息。
deleteProduct()
方法也接受惟一的
productId
来标识特定的产品并从数据库中删除它。
这里需要注意的另一件重要事情是,我们定义的每个异步方法都有一个元数据装饰器作为 HTTP 动词。
它们接受一个前缀,Nest.js 使用这个前缀来进一步标识和指向应该处理请求并做出相应响应的方法。
例如,我们创建的
ProductController
有一个前缀
product
和一个名为
createProduct()
的方法,该方法接受前缀
create
。
这意味着任何指向
product/create
(
http://localhost:3000/product/create
)的
GET
请求都将由
createProduct()
方法处理。
这个过程对于在这个
ProductController
中定义的其他方法也是一样的。
更新产品模块 现在已经创建了控制器和服务,并使用
nest
命令将其自动添加到
ProductModule
中,我们需要更新
ProductModule
。
打开
./src/product/product.module.ts
并用以下代码更新其内容:
import { Module } from '@nestjs/common';
import { ProductController } from './product.controller';
import { ProductService } from './product.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductRepository } from './product.repository';
@Module({
imports: [TypeOrmModule.forFeature([ProductRepository])], // add this
controllers: [ProductController],
providers: [ProductService],
})
export class ProductModule {}
这里,我们将
ProductRepository
类传递给了
TypeOrm.forFeature()
方法。
这将允许使用
ProductRepository
类。
应用程序现在已经准备好了,我们可以运行它来测试到目前为止创建的所有端点。
从终端运行以下命令:
npm run start:dev
这将在
http://localhost:3000
启动应用程序。
此时,您可以使用类似于 Postman 的工具来测试 API。
Postman 是一个测试工具,用于在部署到生产环境之前确认和检查 API 的行为。
使用 Nest.js 应用程序创建产品
用产品的
name
、
description
和
price
创建一个到
http://localhost:3000/product/create
端点的 POST HTTP 请求。
获取所有产品
对
http://localhost:3000/product/all
进行 GET HTTP 请求调用,以检索创建的产品的完整列表。
获取产品
为了检索单个产品的详细信息,我们向
http://localhost:3000/product/2
端点发送了一个 GET HTTP 请求。
请注意,
2
是我们感兴趣的产品的唯一
productId
。
你也可以尝试其他值。
编辑产品 向
http://localhost:3000/product/edit/2
端点发送补丁 HTTP 请求,更新用
2
的
productId
标识的产品详情。
为 Nest.js 应用程序编写测试 既然我们的 API 如预期的那样工作,在这一节中,我们将把重点放在为之前创建的
ProductService
类中定义的方法编写测试上。
感觉只测试应用程序的这一部分比较合适,因为它处理大部分业务逻辑。
Nest.js 带有内置的测试基础设施,这意味着我们不必在测试方面设置太多配置。
尽管 Nest.js 与测试工具无关,但它提供了开箱即用的集成。
Jest 将提供 assert 函数和 test-double 工具来帮助模仿。
目前,
product.service.spec.ts
文件的代码是:
import { Test, TestingModule } from '@nestjs/testing';
import { ProductService } from './product.service';
describe('ProductService', () => {
let service: ProductService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ProductService],
}).compile();
service = module.get(ProductService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
我们将添加更多的测试,使其完全覆盖所有在
ProductService
中定义的方法。
为“创建”和“获取”产品方法编写测试 请记住,我们并没有使用测试驱动的开发方法来启动这个项目。
因此,我们将编写测试来确保
ProductService
中的所有业务逻辑接收到适当的参数并返回预期的响应。
首先,打开
product.service.spec.ts
文件并用以下内容替换其内容:
import { Test, TestingModule } from '@nestjs/testing';
import { ProductService } from './product.service';
import { ProductRepository } from './product.repository';
import { NotFoundException } from '@nestjs/common';
describe('ProductService', () => {
let productService;
let productRepository;
const mockProductRepository = () => ({
createProduct: jest.fn(),
find: jest.fn(),
findOne: jest.fn(),
delete: jest.fn(),
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ProductService,
{
provide: ProductRepository,
useFactory: mockProductRepository,
},
],
}).compile();
productService = await module.get(ProductService);
productRepository = await module.get(ProductRepository);
});
describe('createProduct', () => {
it('should save a product in the database', async () => {
productRepository.createProduct.mockResolvedValue('someProduct');
expect(productRepository.createProduct).not.toHaveBeenCalled();
const createProductDto = {
name: 'sample name',
description: 'sample description',
price: 'sample price',
};
const result = await productService.createProduct(createProductDto);
expect(productRepository.createProduct).toHaveBeenCalledWith(
createProductDto,
);
expect(result).toEqual('someProduct');
});
});
describe('getProducts', () => {
it('should get all products', async () => {
productRepository.find.mockResolvedValue('someProducts');
expect(productRepository.find).not.toHaveBeenCalled();
const result = await productService.getProducts();
expect(productRepository.find).toHaveBeenCalled();
expect(result).toEqual('someProducts');
});
});
});
首先,我们从
@nestjs/testing
模块导入了
Test
和
TestingModule
包。
这提供了方法
createTestingModule
,它创建了一个测试模块,该模块将作为测试中前面定义的模块。
在这个
testingModule
中,
providers
数组由
ProductService
和一个
mockProductRepository
组成,用来模仿使用工厂定制的
ProductRepository
。
然后,我们创建了测试套件的两个不同组件,以确保我们可以创建产品并检索产品列表。
让我们再添加几个脚本来测试在应用程序中检索和删除单个产品的功能。
仍然在
product.service.spec.ts
文件中,通过在我们现有的测试脚本下面添加以下代码来更新它:
import { Test, TestingModule } from '@nestjs/testing';
import { ProductService } from './product.service';
import { ProductRepository } from './product.repository';
import { NotFoundException } from '@nestjs/common';
describe('ProductService', () => {
...
describe('getProduct', () => {
it('should retrieve a product with an ID', async () => {
const mockProduct = {
name: 'Test name',
description: 'Test description',
price: 'Test price',
};
productRepository.findOne.mockResolvedValue(mockProduct);
const result = await productService.getProduct(1);
expect(result).toEqual(mockProduct);
expect(productRepository.findOne).toHaveBeenCalledWith(1);
});
it('throws an error as a product is not found', () => {
productRepository.findOne.mockResolvedValue(null);
expect(productService.getProduct(1)).rejects.toThrow(NotFoundException);
});
});
describe('deleteProduct', () => {
it('should delete product', async () => {
productRepository.delete.mockResolvedValue(1);
expect(productRepository.delete).not.toHaveBeenCalled();
await productService.deleteProduct(1);
expect(productRepository.delete).toHaveBeenCalledWith(1);
});
});
});
为了获得特定的产品,我们简单地创建了一个带有一些默认细节的
mockProduct
,并验证了我们可以检索和删除产品。
查看 GitHub 上的 获取完整的测试脚本。
在本地运行测试 在运行测试之前,您应该删除为位于
src/app.controller.spec.ts
中的
AppController
创建的测试文件,如果您希望为其编写测试,您可以稍后手动创建该文件。
现在,继续运行测试,使用:
npm run test
输出将是这样的:
> nest-starter-testing@0.0.1 test /Users/dominic/workspace/personal/circleci-gwp/nest-starter-testing
> jest
PASS src/app.controller.spec.ts
PASS src/product/product.service.spec.ts
Test Suites: 2 passed, 2 total
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 3.148 s
Ran all test suites.
自动化测试 现在,您已经用 Nest.js 构建了一个完整的 RESTful API,并对其业务逻辑进行了测试。
接下来,您需要添加配置文件来设置 与 CircleCI 的持续集成 。
持续集成有助于确保代码的更新不会破坏任何现有的功能。
一旦测试被推送到 GitHub 存储库,它就会自动运行。
首先,创建一个名为
.circleci
的文件夹,并在其中创建一个名为
config.yml
的新文件。
打开新文件并将以下代码粘贴到其中:
version: 2.1
orbs:
node: circleci/node@3.0.0
jobs:
build-and-test:
executor:
name: node/default
steps:
- checkout
- node/install-packages
- run:
command: npm run test
workflows:
build-and-test:
jobs:
- build-and-test
这里,我们指定了要使用的 CircleCI 版本,并使用 CircleCI Node orb 来设置和安装 Node.js。
最后一个命令是实际的测试命令,它运行我们的测试。
在 CircleCI 建立项目 通过将 导航到此页面 ,在 CircleCI 上创建一个帐户。
接下来,如果您是任何组织的一部分,您将需要选择您希望工作的组织来用 CircleCI 设置您的存储库。
一旦你进入项目页面,找到我们之前在 GitHub 上创建的项目,点击 设置项目 。
这将显示一个配置页面,允许您选择想要使用的 CircleCI 配置文件。
这默认为位于主分支
.circleci/config.yaml
的配置。
现在点击 设置项目 。
按照提示,点击 手动添加 ,因为我们已经包含了配置文件。
您将看到您的管道开始自动运行并通过。
这个构建只有一个任务:
build-and-test
。
作业中的所有步骤都在一个单元中执行,要么在一个新容器中执行,要么在一个虚拟机中执行。
您也可以点击工单查看步骤。
步骤是在作业期间运行的可执行命令的集合
单击这些步骤会显示更多详细信息。
例如,点击
npm run test
步骤。
所有测试都成功运行,并且显示出与本地运行测试时类似的输出。
随后,你要做的就是给你的项目增加更多的特性,编写更多的测试,推送到 GitHub。
持续集成管道将自动运行,测试将被执行。
使用 Nest.js 构建自己的 RESTful API Nest.js 鼓励并加强 web 应用程序的优秀结构。
它帮助您的团队组织工作并遵循最佳实践。
在本教程中,我们学习了如何使用 Nest.js 构建 RESTful APIs,并使用 Postman 测试功能。
最后,我们编写了几个测试,并使用 CircleCI 自动化了它们。
在本教程中,我们重点测试了
ProductService
。
如果您想进一步探索,您可以将获得的知识应用到应用程序的其他部分。
该应用程序的完整源代码 在 GitHub 上。
要了解如何向 NestJS GraphQL 项目添加单元和集成测试,并使用 CircleCI 自动化测试过程,请访问 NestJS graph QL 项目的持续集成 博客文章。
Oluyemi 是一名拥有电信工程背景的技术爱好者。
出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。
Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。
作为技术专家,他的爱好包括尝试新的编程语言和框架。
阅读更多 Olususi Oluyemi 的帖子 充分利用 Docker 和工作流,第 2 部分:关于工作流的一切 原文: https://circleci.com/blog/getting-the-most-out-of-docker-and-workflows-part-2-all-about-workflows/ **来自出版商的说明:**您已经找到了我们的一些旧内容,这些内容可能已经过时和/或不正确。
尝试在 我们的文档 或 博客 中搜索最新信息。
在 的上一期 中,我们看到了 Docker 映像如何为构建过程增加功能和定制。
在这一期中,我们将向您展示如何通过使用 CircleCI 2.0 Workflows 特性来增强这种能力。
详细的工作流程 简单来说, Workflows 在作业之间增加了一个简单的协调层。
让我们从想象一个简单的工作流程开始:
工作流 DAG 工作流 配置 节和实时工作流 运行 (需要登录 CircleCI):
workflows:
version: 2
blog-demo-1:
jobs:
- bundle_dependencies
- rake_test:
requires:
- bundle_dependencies
- precompile_assets:
requires:
- bundle_dependencies
- deploy:
requires:
- rake_test
- precompile_assets
blog-demo-1
工作流程由 4 个任务组成。
bundle_dependencies
作业更新和缓存依赖关系。
然后我们“扇出”成两个并行的作业-
rake_test
和
precompile_assets.
,它们中的每一个都将恢复依赖关系并完成自己的工作。
如果
rake_test
和
precompile_assets
都成功,我们就“扇入”到
deploy
工作中。
利益 我们可以轻松地完成一个内联所有运行步骤的单一作业。
通过引入工作流,我们获得了什么?
显式并行=更快的构建 当它们并行发生时,事情发生得更快。
工作流提供的显式“扇出”并行性可以显著减少构建时间,特别是当项目随着时间的推移变得越来越复杂,并且引入了越来越多的独立可并行化任务,如 代码覆盖 。
在上面的例子中,
rake_test
和
precompile_assets
受益于这种并行性。
每个作业可能在不同的环境中运行 在我们上面的例子中,
deploy
作业运行在“机器”中,而不是 Docker 容器中。
您有机会为每个作业指定不同的 Docker 图像和 resource_class 。
为了降低成本和最大限度地减少构建时间,用户可能希望使用具有强大资源的功能丰富的容器来运行一些常见的前驱步骤,然后分散到许多轻量级作业中,以实现快速并行构建。
重复性-从失败重新运行 如果某项工作因暂时性问题(如不稳定的文本或外部服务的临时中断)而在工作流程中失败,您 不必 从头开始重新启动工作流程。
“重新运行失败的作业”功能允许工作流以失败的作业为起点继续运行。
如果没有工作流,您将总是不得不重新运行整个构建。
向您的 VCS 提供商报告详细状态 通过工作流,您的 VCS 提供商(例如 Github)将获得一个状态列表——每个作业一个状态——这样可以更容易地一眼看出故障发生在哪里,并且您可以直接导航到失败的作业。
结尾部分 在这篇博文中,我们简要介绍了 Circle 2.0 的工作流特性。
在下一期中,我们将逐步添加一些高级特性,包括: 工作区转发 手动审批作业 分支和标签过滤 Golang Gin-gonic RESTful API 的自动化测试 原文: https://circleci.com/blog/gin-gonic-testing/ 本教程涵盖: 使用 Gin-gonic 框架用 Golang 构建 RESTful API 为端点编写和运行测试 自动化测试 Gin 是一个用 Golang 编写的高性能 HTTP web 框架。
它包含路由和现成中间件等特性和功能。
这有助于减少样板代码,提高生产率,并简化构建微服务的过程。
在本教程中,我将指导你使用 Gin-gonic 框架构建一个 RESTful API。
我还将带领您构建一个 API 来管理公司的基本细节。
这个 API 将允许您创建、编辑、删除和检索公司列表。
为了简单起见,我不会在本教程中讨论数据持久性。
相反,我们将使用一个虚拟的公司列表,我们可以相应地更新或删除。
这听起来可能很简单,但足以让您开始用 Golang 构建健壮的 API 和单元测试。
先决条件 您将需要以下内容来充分利用本教程: 我们的教程是平台无关的,但是使用 CircleCI 作为例子。
如果你没有 CircleCI 账号,请在 注册一个免费的 。
入门指南 首先,通过终端导航到您的开发文件夹,并使用以下命令为项目创建一个新文件夹:
mkdir golang-company-api
cd golang-company-api
前面的命令创建了一个名为
golang-company-api
的文件夹,并导航到其中。
接下来,通过发出以下命令初始化项目中的一个 Go 模块 :
go mod init golang-company-api
这将创建一个
go.mod
文件,其中将列出项目的依赖项以供跟踪。
安装项目的依赖项 如上所述,这个项目将使用 Gin 框架 作为外部依赖。
从项目的根目录发出以下命令,安装最新版本的 Gin 和其他依赖项:
go get -u github.com/gin-gonic/gin github.com/stretchr/testify github.com/rs/xid
一旦安装过程成功,您就可以访问 Gin 和应用程序中的以下软件包: evidence 是 Golang 最受欢迎的测试包之一 XID 是一个全球唯一的 ID 生成器库。
创建主页 现在,在项目的根目录下创建一个名为
main.go
的文件。
这将是应用程序的入口点,也将包含负责所有功能的大多数功能。
打开新文件,并使用以下内容:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func HomepageHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message":"Welcome to the Tech Company listing API with Golang"})
}
func main() {
router := gin.Default()
router.GET("/", HomepageHandler)
router.Run()
}
这段代码导入了 Gin 和一个提供 http 客户端和服务器实现的 net/http 包。
它创建了一个
HomepageHandler()
方法来处理应用程序主页上的响应。
最后,
main()
函数初始化一个新的 Gin 路由器,为主页定义 HTTP 动词,并通过调用 Gin 实例的
Run()
在默认端口
8080
上运行一个 HTTP 服务器。
运行项目 运行项目:
go run main.go
该命令在默认端口
8080
上运行应用程序。
去
http://localhost:8080
复习一下。
既然应用程序按预期工作,您就可以开始实现 API 端点所需的逻辑了。
现在,使用 CTRL+C 停止应用程序的运行,然后按下 Enter 。
创建 REST APIs 在继续之前,您需要定义一个保存公司信息的数据结构。
这将包含公司的属性和字段。
每个公司都有一个
ID
、
Name
、
CEO
的名字和
Revenue
——公司预计的年收入。
定义公司模式 使用 Go 结构 来定义这个模型。
在
main.go
文件中,声明以下结构:
type Company struct {
ID string `json:"id"`
Name string `json:"name"`
CEO string `json:"ceo"`
Revenue string `json:"revenue"`
}
要轻松地将每个字段映射到特定的名称,请使用反勾号指定每个字段上的标签。
这允许您发送符合 JSON 命名约定的适当响应。
定义全局变量 接下来,定义一个全局变量来表示公司,并用一些虚拟数据初始化该变量。
在
main.go
文件中,在
Company
结构之后添加:
var companies = []Company{
{ID: "1", Name: "Dell", CEO: "Michael Dell", Revenue: "92.2 billion"},
{ID: "2", Name: "Netflix", CEO: "Reed Hastings", Revenue: "20.2 billion"},
{ID: "3", Name: "Microsoft", CEO: "Satya Nadella", Revenue: "320 million"},
}
创建新公司 接下来,定义创建新公司所需的逻辑。
在
main.go
文件中创建一个新方法,将其命名为
*NewCompanyHandler*
,并使用以下代码:
func NewCompanyHandler(c *gin.Context) {
var newCompany Company
if err := c.ShouldBindJSON(&newCompany); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
newCompany.ID = xid.New().String()
companies = append(companies, newCompany)
c.JSON(http.StatusCreated, newCompany)
}
这个代码片段将传入的请求体绑定到一个
Company
struct 实例中,然后指定一个惟一的
ID
。
它将
newCompany
添加到公司列表中。
如果有错误,则返回错误响应,否则返回成功响应。
获取公司列表 要检索公司列表,定义一个
*GetCompaniesHandler*
方法:
func GetCompaniesHandler(c *gin.Context) {
c.JSON(http.StatusOK, companies)
}
这使用了
c.JSON()
方法将
companies
数组映射到 JSON 并返回它。
更新公司 要更新现有公司的详细信息,请使用以下内容定义一个名为
*UpdateCompanyHandler*
的方法:
func UpdateCompanyHandler(c *gin.Context) {
id := c.Param("id")
var company Company
if err := c.ShouldBindJSON(&company); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
index := -1
for i := 0; i < len(companies); i++ {
if companies[i].ID == id {
index = 1
}
}
if index == -1 {
c.JSON(http.StatusNotFound, gin.H{
"error": "Company not found",
})
return
}
companies[index] = company
c.JSON(http.StatusOK, company)
}
这个代码片段使用
c.Param()
方法从请求 URL 中获取公司的惟一
id
。
它检查该记录是否存在于公司列表中,然后相应地更新指定的公司。
删除公司 使用以下内容创建一个
*DeleteCompanyHandler*
方法:
func DeleteCompanyHandler(c *gin.Context) {
id := c.Param("id")
index := -1
for i := 0; i < len(companies); i++ {
if companies[i].ID == id {
index = 1
}
}
if index == -1 {
c.JSON(http.StatusNotFound, gin.H{
"error": "Company not found",
})
return
}
companies = append(companies[:index], companies[index+1:]...)
c.JSON(http.StatusOK, gin.H{
"message": "Company has been deleted",
})
}
与
*UpdateCompanyHandler*
类似,这个代码片段中的方法使用惟一标识符来定位需要从列表中删除的公司的详细信息。
它删除公司详细信息,并返回一个成功的响应。
设置 API 路由处理程序 接下来,注册所有适当的端点,并将它们映射到前面定义的方法。
如下所示更新
main()
:
func main() {
router := gin.Default()
router.GET("/", HomepageHandler)
router.GET("/companies", GetCompaniesHandler)
router.POST("/company", NewCompanyHandler)
router.PUT("/company/:id", UpdateCompanyHandler)
router.DELETE("/company/:id", DeleteCompanyHandler)
router.Run()
}
如果您使用的是支持包自动导入的代码编辑器或 IDE,那么这将会得到更新。
如果您没有使用该类型的编辑器或 IDE,请确保
import
与下面的代码片段匹配:
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/rs/xid"
)
测试应用程序 在定义的所需方法和注册的单个端点中,返回到终端并使用
go run main.go
再次运行应用程序。
这将在端口
8080
上启动应用程序 创建新公司 使用 Postman 或您喜欢的 API 测试工具对此进行测试。
向
http://localhost:8080/
公司发送 HTTP POST 请求。
使用以下数据作为请求有效负载:
{
"name":"Shrima Pizza",
"ceo": "Demo CEO",
"revenue":"300 million"
}
正在检索公司列表 要检索公司列表,将 HTTP
GET
请求设置为
http://localhost:8080/companies
。
为端点编写测试 既然您的应用程序正在按预期工作,那么就专注于为所有为处理 API 端点的逻辑而创建的方法编写单元测试。
Golang 开箱即用,安装了一个测试包,使得编写测试更加容易。
首先,创建一个名为
main_test.go
的文件,并用以下内容填充它:
package main
import "github.com/gin-gonic/gin"
func SetUpRouter() *gin.Engine{
router := gin.Default()
return router
}
这是一个返回 Gin 路由器实例的方法。
在测试每个端点的其他功能时,它会很方便。
注意:
项目中的每个测试文件都必须以_test.go结尾,每个测试方法都必须以Test开头。
这是有效测试的标准命名约定。
测试主页响应 在
main_test.go
文件中,定义一个
*TestHomepageHandler*
方法并使用以下代码:
func TestHomepageHandler(t *testing.T) {
mockResponse := `{"message":"Welcome to the Tech Company listing API with Golang"}`
r := SetUpRouter()
r.GET("/", HomepageHandler)
req, _ := http.NewRequest("GET", "/", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
responseData, _ := ioutil.ReadAll(w.Body)
assert.Equal(t, mockResponse, string(responseData))
assert.Equal(t, http.StatusOK, w.Code)
}
这个测试脚本使用 Gin 引擎设置一个服务器,并向主页
/
发出一个
GET
请求。
然后,它使用 evident 包中的
assert
属性来检查状态代码和响应有效负载。
测试创建新的公司端点 要为您的 API 测试
/company
端点,创建一个
*TestNewCompanyHandler*
方法并使用以下代码:
func TestNewCompanyHandler(t *testing.T) {
r := SetUpRouter()
r.POST("/company", NewCompanyHandler)
companyId := xid.New().String()
company := Company{
ID: companyId,
Name: "Demo Company",
CEO: "Demo CEO",
Revenue: "35 million",
}
jsonValue, _ := json.Marshal(company)
req, _ := http.NewRequest("POST", "/company", bytes.NewBuffer(jsonValue))
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusCreated, w.Code)
}
这个代码片段发出一个带有示例负载的
POST
请求,并检查返回的响应代码是否是
201
StatusCreated
。
测试获取公司端点 接下来是测试
GET /companies
资源的方法。
用下面的代码定义
*TestGetCompaniesHandler*
方法:
func TestGetCompaniesHandler(t *testing.T) {
r := SetUpRouter()
r.GET("/companies", GetCompaniesHandler)
req, _ := http.NewRequest("GET", "/companies", nil)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
var companies []Company
json.Unmarshal(w.Body.Bytes(), &companies)
assert.Equal(t, http.StatusOK, w.Code)
assert.NotEmpty(t, companies)
}
这段代码向
/companies
端点发出一个
GET
请求,并确保返回的有效负载不为空。
它还声明状态代码是
200
。
测试更新公司端点 最后一个测试是针对负责更新公司详细信息的 HTTP 处理程序。
在
main_test.go
文件中使用以下代码片段:
func TestUpdateCompanyHandler(t *testing.T) {
r := SetUpRouter()
r.PUT("/company/:id", UpdateCompanyHandler)
company := Company{
ID: `2`,
Name: "Demo Company",
CEO: "Demo CEO",
Revenue: "35 million",
}
jsonValue, _ := json.Marshal(company)
reqFound, _ := http.NewRequest("PUT", "/company/"+company.ID, bytes.NewBuffer(jsonValue))
w := httptest.NewRecorder()
r.ServeHTTP(w, reqFound)
assert.Equal(t, http.StatusOK, w.Code)
reqNotFound, _ := http.NewRequest("PUT", "/company/12", bytes.NewBuffer(jsonValue))
w = httptest.NewRecorder()
r.ServeHTTP(w, reqNotFound)
assert.Equal(t, http.StatusNotFound, w.Code)
}
这向
company/:id
端点发送了两个 HTTP
PUT
请求。
一个具有有效负载和有效公司 id,另一个具有不存在的 ID。
有效调用将返回成功响应码,无效调用将返回
StatusNotFound
。
更新
main_test.go
文件中的导入部分:
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/rs/xid"
"github.com/stretchr/testify/assert"
)
在本地运行测试 现在,通过发出以下命令来运行测试:
go test
要禁用 Gin 调试日志并启用详细模式,请运行带有
-V
标志的命令:
GIN_MODE=release go test -v
自动化测试 通过在 CircleCI 上创建一个持续集成管道来自动化测试。
要添加所需的配置,创建一个名为
.circleci
的文件夹,并在其中创建一个名为
config.yml
的新文件。
打开新文件并将以下代码粘贴到其中:
version: 2.1
orbs:
go: circleci/go@1.7.1
jobs:
build:
executor:
name: go/default
tag: "1.16"
steps:
- checkout
- go/load-cache
- go/mod-download
- go/save-cache
- run:
name: Run tests
command: go test -v
这个脚本为 CircleCI 拉入 Go orb。
这个 orb 允许执行常见的 Go 相关任务,如安装 Go、下载模块和缓存。
然后,它检查远程存储库,并发出运行我们的测试的命令。
接下来,在 GitHub 上建立一个存储库,并将项目链接到 CircleCI。
查看 将您的项目推送到 GitHub 以获取指导。
连接到 CircleCI 登录您的 CircleCI 帐户。
如果你注册了你的 GitHub 账户,你所有的库都可以在你项目的仪表盘上看到。
点击 设置项目 按钮。
将提示您是否已经在项目中定义了 CircleCI 的配置文件。
输入分支名称(对于本教程,我们使用
main
)。
点击 设置项目 按钮完成该过程。
通过单击工作流程中的作业,您可以查看所有步骤。
您可以通过点击作业来获得作业的更多详细信息,例如
Run tests
作业。
你有它!
结论 GitHub 上有超过 50k 颗星,令人惊讶的是 Gin 越来越受欢迎,并逐渐成为 Golang 开发人员构建高效 API 的首选。
在本教程中,我向您展示了如何使用 Golang 和 Gin 构建 REST API。
我带领您为每个端点编写了一个单元测试,并使用 GitHub 和 CircleCI 为它建立了一个持续集成管道。
希望你能将所学应用到团队的项目中。
点击 GitHub 上的 查看示例项目的代码。
Oluyemi 是一名拥有电信工程背景的技术爱好者。
出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他解决问题的技能用于构建 web 和移动软件。
Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。
由于精通技术,他的爱好包括尝试新的编程语言和框架。
Oluyemi 是一名拥有电信工程背景的技术爱好者。
出于对解决用户日常遇到的问题的浓厚兴趣,他冒险进入编程领域,并从那时起将他的问题解决技能用于构建 web 和移动软件。
Oluyemi 是一名热衷于分享知识的全栈软件工程师,他在世界各地的几个博客上发表了大量技术文章和博客文章。
作为技术专家,他的爱好包括尝试新的编程语言和框架。
阅读更多 Olususi Oluyemi 的帖子 从 Git 分离头状态| CircleCI 中恢复 原文: https://circleci.com/blog/git-detached-head-state/ 2005 年 Git 作为源代码管理系统的引入从根本上改变了软件开发的过程。
Git 允许开发人员维护代码更改或提交的历史,如果出现问题,可以在几秒钟内恢复到以前的提交。
它通过允许分支来保持不同特性的代码分离,并无缝合并不同人的提交,从而使协作变得更加容易。
它速度快,可伸缩,比旧版本控制工具如 Apache Subversion (SVN)和 Concurrent Versions System (CVS)拥有更多的命令和灵活性。
然而,学习 Git 比学习 SVN 或 CVS 更复杂。
复杂的命令和不太直观的用户界面有时会导致不需要的状态,包括称为分离头的状态。
在本文中,我们将探讨什么是 Git 分离头状态以及导致它的一些情况。
然后,我们将演示如何在一个分离的头中保存或放弃更改,以便您可以从这种情况中快速恢复。
头分离是什么意思?
在 Git 中,HEAD 指的是当前签出分支的最新提交。
然而,在一个 分离的 HEAD 状态中,HEAD 不指向任何分支,而是指向一个特定的提交或远程存储库。
下图是正常状态下的 Git 头,指向主分支中的最新提交。
在此图中,HEAD 指向当前分支中每个提交的最新提交和当前签出分支。
头也是多分支工作时的常见状态。
在这种情况下,您有两个分支,主分支和功能分支。
因为您被检出到特征分支,所以 HEAD 指向那里。
您可以使用以下 Git 命令创建这个场景:
git branch feature
git checkout feature
git commit -m “checked out to feature”
git checkout Main
git commit
git commit -m “Latest commit”
git checkout feature
在上面的两个图中,HEAD 指向当前签出分支中最近的提交,这是它的正常状态。
在 Git 中,您还可以检查特定的提交,这将导致分离的 HEAD 状态。
继续上一个场景,如果您检查主分支上的第一个提交,您将看到 HEAD 现在被分离。
在这种情况下,HEAD 不指向任何分支—它引用提交。
可能导致分离磁头状态的场景 你会发现自己处于一种超然的状态,主要是通过两种情况: 签出特定的安全哈希算法 1 (SHA-1)提交哈希 不先提取就签出到远程分支 我们已经演示过,如果您签出 SHA-1 提交散列,您将处于分离头状态。
导致分离头的另一种情况是检查远程分支。
如果您检出到只读的源(主)分支,您将处于分离的 HEAD 状态。
其他一些情况也可能导致头部分离。
例如,检查一个特定的标签名或在任何给定的分支上添加
^0
会导致分离的 HEAD 状态。
如何在分离的头中保存更改 如果你发现自己是一个超脱的头部状态,并且很快意识到,那么你可以通过检查之前的分支来快速恢复。
但是,如果您错误地处于分离的 HEAD 状态,然后执行 commits over commits,该怎么办呢?
如果您在分离的 HEAD 状态下提交,这是否意味着您的更改没有被保存?
一点也不。
这只是意味着你目前没有附属于任何分支,因此,你的头是分离的。
如果您想保留您在分离的 HEAD 状态下所做的更改,您可以通过三个简单的步骤来解决这个问题:创建一个新的分支,提交更改,以及合并更改。
创建新分支 要保存在分离的 HEAD 状态下提交的更改,首先需要创建一个新的分支。
从上面描述的场景继续,您创建一个名为
temp-branch
的新分支。
当您创建分支并检查到它时,头部不再分离。
提交更改 在签出到新的分支之后,您可以提交更改,Git 将保留这些更改。
合并更改 现在,您检查到您想要更改的分支。
在这种情况下,您希望将更改合并到主分支。
因此,您首先需要检查主分支,然后合并来自
temp-branch
的更改并添加最终的提交消息。
通过这些简单的步骤,您已经成功地保存了您的更改并从 Git 分离头状态中恢复。
如何放弃分离头中的更改 如果要放弃分离的 HEAD 状态中的更改,只需签出到现有的或以前的分支。
分离的 HEAD 状态上的提交不会影响您现有的分支,Git 会将它们存档。
下图显示了一种情况,在进入分离头状态后,您进行了两次不想保留的提交。
然后,你去总分行结账。
虚线圆圈表示这些提交不再是任何分支的一部分,Git 将删除它们。
注意,一旦 Git 删除了分离的 HEAD 状态提交,就没有办法恢复它们了。
但是,如果它们没有被删除,您可以检查到 SHA-1 提交散列,创建一个分支,并将其合并到所需的分支以保留更改。
结论 Git 是一个有价值的开发工具,比 CVS 和 Subversion 等老版本工具更受欢迎。
也就是说,掌握它可能会更加复杂和具有挑战性,有时会导致混乱的情况,例如分离的头部状态。
如果您发现自己处于分离的 HEAD 状态,请记住,您总是可以通过创建并检出到一个新的分支,然后提交并合并所需分支中的更改来保存您的更改。
如果您不想保存更改,您可以简单地签出到任何分支,Git 会删除这些提交。
还有,Git 2.23 有一个新命令,
git switch
。
这不是一个新特性,而是
git checkout
的替代命令,因此您可以在分支之间切换并创建一个新分支。
要从一个分支切换到另一个分支,使用
git switch branchName
创建一个新分支,然后使用
git switch -c branchName
命令切换到该分支。
尽管在分离的 HEAD 状态下找到您的代码并不理想,但是您可以使用这些方法来移动或删除您的提交,并快速使您的项目回到正轨。
