草庐IT

从一个小 Bug,到 Azure DevOps

dino.c 2023-03-28 原文

1. 一个小Bug

最近和同事提起一个几年前的 Bug,那是一个很小很小的 Bug,没什么技术含量。那时候我刚入职,正好公司卖了一款仪器到某个国家,但是那边说配套的软件运行不起来,一打开就报错。经过排查发现出错的代码很简单,大致是这样:

public static int GetSecond(DateTime time)
{
    return Convert.ToInt32(time.ToString().Split(":")[2]);
}

当时真是哭笑不得。这段代码应该是从旧语言迁移过来,如果只在国内完全没问题,但放到国外就可能报错,因为不同地区和语言会有不同的时间格式,例如加拿大的时间显示格式就不一样,秒的后面还带了表示上午/下午的 a.m/p.m.,这一点可以通过在代码中更改 Thread.CurrentThread.CurrentCulture 来验证:

var time = new DateTime(2000, 1, 20, 1, 2, 3);
Console.WriteLine(time); \\ 输出 2000-01-20 1:02:03
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-CA");
Console.WriteLine(time); \\ 输出 2000-01-20 1:02:03 a.m.

至于测试人员,可以通过将系统设置中的“时间和语言- > 语言&区域”中的区域格式为英语(加拿大)来验证:

可是无论开发人员还是测试人员都没有发现有问题,当时这个离谱的 Bug 就这样插着翅膀,飞越高山和大海,飞到了国外客户的手上。整个团队上上下下由开发到测试都没发现整个 Bug,也许团队在技术和流程上都有问题,让我不得不怀疑我进了个大坑。

吃一堑就要长一智。虽然只是一个小 Bug,但也反映了团队技术和代码流程的欠缺。为了避免再发生这种情况,需要从团队培养及流程改善两个方面着手。团队培养是另一个话题,这篇文章只说说流程改善。当时我们已经在使用 TFS(Azure DevOps 的前身),不过只用于代码管理,很多功能都没有用到。后来 Azure DevOps 不断改善,我们也使用了它更多的功能来帮助我们改进产品质量。这篇文章以文章开头的那个小 Bug 为例,简单讲解 Azure DevOps 处理它的流程。

2. 在 Azure DevOps 上记录并开始处理这个 Bug

首先假设我已经在 Azure DevOps 管理代码,并且配置好 Pipeline 等基础设施,我现在只需要处理这个 Bug。

第一步,添加一个 Bug。除了Bug 的标题,Bug 的详细内容里还可以添加重现步骤、系统信息等,如果有错误日志的话还可以作为附件添加到这个 Bug 里。

当团队理解并同意了这个 Bug 的内容后,在 Boards 中将它从 New 拖动到 Approved,并在 ··· 的下拉菜单中选中 Add TaskAdd Test 分别添加任务和测试用例。

我随意添加了两个任务以及一个测试用例。

3. 在 Visual Studio 中修复 Bug 并添加单元测试

之后轮到团队中负责处理这个 Bug 的开发人员接手工作。打开 VisualStudio 新建分支,修复这个 Bug,并根据单元测试 Arrange、Act、Assert 的方式添加一个对应的单元测试:

[TestMethod]
public void GetSecond_DifferentCultureInfo_Succeed()
{
    var time = new DateTime(2000, 1, 20, 1, 2, 3);
    Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-CA");
    var second = DateTimeUtils.GetSecond(time);
    Assert.AreEqual(second, time.Second);

    Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("zh-CN");
    second = DateTimeUtils.GetSecond(time);
    Assert.AreEqual(second, time.Second);
}

运行单元测试,确保所有单元测试都通过后在 Git 更改 面板提交这个 Bug 的信息,并且输入关联的工作项的 ID,然后点击 全部提交并推送

推送成功后回到代码编辑器,可以看到被修改的函数上的 CodeLens 有一个待变单元测试通过的绿色图标,鼠标放上去可以显示它被哪些单元测试验证过。

在被修改的函数及相关的单元测试的 CodeLens 最右边显示“4个工作项”,鼠标放上去可以看到之前提交代码时关联的工作项。

4. 在 Pull Request 中验收代码

接下来的操作需要回到 Azure DevOps。新的代码不能随随便便就签进去主分支,需要创建一个 PullRequest 通知相关人员这个代码变动,并在这个 Pull Request 里记录关联的工作项,经过修改的代码,需要谁来 Code Review 等。听起来很多,其实提交代码的开发人员只需要点击创建 Pull Request,选择要合并的分支,然后点击创建,其它内容几乎都由 Azure DevOps 自动填充。

Pull Request 最起码需要两道把关,一个是自动运行的单元测试,它需要代码里所有单元测试都运行并且通过。另一个是 Code Review,Azure DevOps 可以设置各种 Code Review 策略,包括最少的 Code Review 人数、当有变更时重置所有审核等。Code Review 除了保证签入的代码质量,还是代码集体所有的一个体现。代码集体所有是敏捷中一个重要的要素,它确保团队中知识的传承,并促进能力的提升。

下图是一个已完成的 Pull Request,可以看到几个绿色的代表通过的图标,代表它通过了多少道“工序”。还可以看到它关联的工作项,由谁创建,由哪个分支合并到哪里等信息。

切换到 Files 选项卡,可以看到具体的代码变更:

5. 测试验证与测试用例

完成上面的步骤后将 Bug 从 Approved 拖动到 Committed,并且将关联的两个 Task 设为完成。代码合并到 master 后 Azure Pipeline 将自动编译并部署好最新的代码,然后通过邮件或 Teams 通知给相关测试人员。测试人员在收到通知后打开 Board,当它看到这个 Bug 的全部 Task 都已经完成(黄色图标旁边的 2/2),他就知道应该开始进行测试验收。完成测试后在测试用例右边的 ··· 下拉菜单中选择测试结果,顺利的话选择 Pass test,并且把这个 Bug 拖到 Done

到这一步 Bug 的处理已经完成。为防止错误再次发生,开发人员添加了单元测试,并且所有相关人员都通过这个流程分享了经验,无论是代码或是团队都变得更加强大。但这还不是结束,这个 Bug 里包含的测试用例是它留下的另一份宝贵财产,需要谨慎对待。打开这个 Bug,可以在右下角 Tested By 部分看到它的测试用例。

点击这个测试用例查看详细信息,可以看到它的 Steps(这里我懒得写),以及各种关联的工作项。

Azure DevOps 提供了 Test Plans 模块,用于管理测试用例和测试计划。在开发过程中产生的各种测试用例最终汇集成测试计划,由测试人员确保曾经正确运行过的功能不会再次出错。不过这部分只开放给收费用户,有机会再详细介绍它的各种功能。

6. 最后

现在公司的代码已经在 Azure DevOps 上安了家,一系列流程都运作得很畅顺(虽然还有很多很多可以改进的地方),尽管软件功能膨胀了几倍但售后反馈的问题反而更少。回过头来看看几年前那个 Bug,当时的代码生产方式和代码质量与现在真的有天壤之别,这其中 Azure DevOps 功不可没。

最后提醒一下,如果想尝试 Azure DevOps 可以不依照我写的流程。这篇文章介绍的流程只是个简化版本,实际工作起来稍微不同,而且应该根据自己团队的实际情况灵活改变,打造属于自己和自己团队的流程(幸好 Azure DevOps 可以做到相当灵活)。

关于 Azure DevOps 的更多内容请参考官方文档:

Azure DevOps documentation _ Microsoft Docs

有关从一个小 Bug,到 Azure DevOps的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  6. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  7. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  8. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

  9. ruby - Rails 关联 - 同一个类的多个 has_one 关系 - 2

    我的问题的一个例子是体育游戏。一场体育比赛有两支球队,一支主队和一支客队。我的事件记录模型如下:classTeam"Team"has_one:away_team,:class_name=>"Team"end我希望能够通过游戏访问一个团队,例如:Game.find(1).home_team但我收到一个单元化常量错误:Game::team。谁能告诉我我做错了什么?谢谢, 最佳答案 如果Gamehas_one:team那么Rails假设您的teams表有一个game_id列。不过,您想要的是games表有一个team_id列,在这种情况下

  10. ruby - 将一个超薄文件包含在另一个超薄文件中 - 2

    我在一个静态网站上工作(因此没有真正的服务器支持),我想在另一个网站中包含一个小的细长片段,可能会向它传递一个变量。这可能吗?在rails中很容易,虽然是render方法,但我不知道如何在slim上做(显然load方法不适用于slim)。 最佳答案 Slim包含Include插件,允许在编译时直接在模板文件中包含其他文件:require'slim/include'includepartial_name文档可在此处获得:https://github.com/slim-template/slim/blob/master/doc/incl

随机推荐