您是新用户的 Visual Studio 应用程序生命周期管理 (ALM) 和 Team Foundation Server (TFS) 吗? 您想知道如何您和您的团队可以获得最大受益的这些工具来生成您的应用程序的最新版本?
然后花几分钟就可以走逐步完成该两个章节教程,并按照 Peter 和朱丽亚在 Fabrikam 纤程的两个开发人员的生活的一天 — — 虚构的公司,提供有线电视和相关的服务。 您将看到如何使用 Visual Studio 和 TFS 签出并更新代码、 暂停工作时被打断、 请求代码评审、 签入您的更改,并执行其他任务的示例。
Peter 简要介绍了他积压工作,并选择他目前从事的任务。 他写他计划开发的代码的单元测试。 通常情况下,他将在一小时内多次运行测试,则逐渐编写更详细的测试,然后编写代码,使其传递。 他经常与将使用的方法写入他的同事讨论他的代码的接口。
说明 |
本主题讨论了我的工作和代码覆盖率功能都只能在Visual Studio 高级专业版和 Visual Studio 旗舰版. |
本主题中
- 查看个人的积压工作,并准备好开始工作的任务
- 创建第一个单元测试
- 创建新代码的存根 (stub)
- 运行第一个测试
- 同意该 API
- 红、 绿、 重构命令。.
- 代码覆盖率
- 何时我们完成?
- 签入更改
在团队资源管理器,Peter 打开 我的工作页。 团队已同意在当前冲刺 (sprint) 期间 Peter 将从事评估发票状态,产品积压工作中的最高优先级别项。 王建军决定着手实施的数学函数,子任务的最高优先级别的积压工作项。 他将此任务从可用的工作项 列出到 在进度的工作项 & 更改列表。
若要查看个人的积压工作,并准备好开始工作的任务
- 在团队资源管理器:
- 如果您已经不连接到团队项目,您想要在中,然后连接到团队项目。
- 选择主页,然后选择我的工作。
- 在我的工作 页上,拖动任务从 可用的工作项 列表到 中进行工作项部分。
您还可以选择在任务可用工作项 列表,然后选择 开始。
草拟增量工作计划
Peter 通常会制定一系列的小步骤中的代码。 每个步骤通常时间不会长于一个小时,并且可能需要短短十分钟。 在每个步骤中,他将新的单元测试,并更改,以便它可以通过新的测试,除了他已写入的测试,他正在开发的代码。 有时他写之前更改该代码,新的测试,他有时会在编写测试之前更改的代码。 有时他重构。 也就是说,他只是提高代码而无需添加新的测试。 他永远不会更改通过,测试,除非他决定它正确地表示一项要求。
在每一小步结束时,他将负责相关的代码的这一区域的所有单元测试。 他不会考虑该步骤完成之前,每个测试通过。
但是,他不会检查该代码到 Team Foundation Server 直到他完成整个任务。
Peter 写下这一系列小幅度的大致规划。 他知道准确的详细信息和最新的顺序可能会更改为他工作。 这是他最初的列表为此特定任务的步骤:
- 创建测试方法存根 (stub) — — 即只的方法的签名。
- 满足特定的一个典型事例。
- 测试范围广泛。 请确保代码正确响应大范围值。
- 负的异常。 正常处理不正确的参数。
- 代码覆盖率。 请确保至少 80%的代码被执行单元测试。
一些他的同事们在他们的测试代码中的注释编写这种计划。 其他人只需记住他们的计划。 Peter 查找有用的任务工作项的描述字段中写入他列出的步骤。 如果他认为暂时转用更紧迫的任务,他知道在哪里可以找到列表,在他能够返回到它。
Peter 首先创建单元测试。 他开始与单元测试因为他要编写使用他的新类的代码的示例。
这是他正在测试,该类库的第一个单元测试,因此他创建了一个新的单元测试项目。 他打开新项目 对话框并选择 Visual C#, 测试,然后 单元测试项目。
单元测试项目提供了 C# 文件到其中,他可以编写他的示例。 在此阶段中,他只是要说明他新方法之一调用方式:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Fabrikam.Math.UnitTest { [TestClass] public class UnitTest1 { [TestMethod] // Demonstrates how to call the method. public void SignatureTest() { // Create an instance: var math = new Fabrikam.Math.LocalMath(); // Get a value to calculate: double input = 0.0; // Call the method: double actualResult = math.SquareRoot(input); // Use the result: Assert.AreEqual(0.0, actualResult); } } }
他在测试方法中会写入示例是因为他希望他写入了他的代码时,本示例才有效。
若要创建一个单元测试项目和方法
通常会创建新的测试项目,每个项目正在进行测试。 如果已存在一个测试项目,您可以只是添加新的测试方法和类。
此过程使用 Visual Studio 的单元测试框架,但您还可以使用来自其他提供商的框架。 测试资源管理器的工作原理与其他框架,很好,只要您安装相应的适配器。
- 如果不存在,请创建一个测试项目。
- 在新项目 对话框中,选择一种语言如 Visual Basic, Visual C++ 或 Visual C#。 选择测试 和 单元测试项目。
- 将测试添加到测试类提供。 每个单元测试是一种方法。
必须为每个单元测试将前缀TestMethod特性,并且单元测试方法应具有无参数。 您可以使用任何所需的单元测试方法的名称:
[TestMethod] public void SignatureTest() {...}
- 每个测试方法应调用的方法Assert类,以指示它是否已通过或失败。 通常情况下,您验证操作的预期和实际结果相等:
Assert.AreEqual(expectedResult, actualResult);
- 测试方法可以调用其他普通的方法不具有TestMethod特性。
- 可以将测试组织到多个类。 每个类的前面必须有TestClass特性。
[TestClass] public class UnitTest1 { ... }
有关如何在 c + + 编写单元测试的详细信息,请参阅 用 Microsoft 适用于 C++ 的单元测试框架编写 C/C++ 单元测试.
接下来,Peter 将为他的新代码创建一个类库项目。 目前正在开发的代码项目和单元测试项目。 他从测试项目中添加引用的项目正在开发的代码。
在新项目中,他将添加新类,并将至少允许要成功生成的测试方法的最低版本。 要做到这一点的最快方法是从测试中的调用生成的类和方法的存根。
public double SquareRoot(double p) { throw new NotImplementedException(); }
若要从测试生成的类和方法
首先,创建要添加新的类,除非它已存在的项目。
若要生成的类
- 将光标放在要从中生成,例如,类的示例 LocalMath. 在快捷菜单中,选择生成代码, 新型。
- 在新型 对话框中,将 项目的类库项目。 在此示例中,它是 Fabrikam.Math。
若要生成方法
- 例如,将光标放在该方法中,调用 SquareRoot. 在快捷菜单中,选择生成代码, 方法存根 (stub)。
Peter 生成并运行测试,通过按 CTRL + R、 t。 测试结果显示了一个红色的失败指标和出现在列表中的下一个测试失败测试。
对代码进行简单的更改:
public double SquareRoot(double p) { return 0.0; }
他再次运行该测试并将它传递:
运行单元测试
- 在测试 菜单上,选择 运行, 的所有测试。
-或者- - 如果测试资源管理器中打开,请选择所有运行。
-或者- - 请将光标置于某个测试代码文件,然后按 CTRL + R、 T。
- 如果下一个测试出现失败测试:
测试,例如,双击以打开该名称。
显示测试未通过的点。
若要测试的完整列表,请参阅 选择 显示所有。 若要返回到的摘要,请选择主页视图。
若要查看详细信息的测试结果后, 测试资源管理器中选择该测试。
若要定位到代码的测试, 中双击该测试中测试资源管理器中,或者选择打开测试在快捷菜单上。
若要调试测试, 打开的一个或多个测试,快捷菜单,然后选择调试选中的测试。
若要在后台中运行测试时生成解决方案,
切换生成后运行的测试。 第一个运行以前失败的测试。
Peter 调用 Lync,他的同事朱丽亚和共享他的屏幕。 她将用自己的组件。 他显示了他最初的示例。
朱丽亚认为该示例是确定,但注释,"大量的功能将通过该测试"。
Peter 答复,"是第一次测试只是为了确保名称和函数的参数正确。 现在我们可以编写捕获此函数的主要要求的测试。"
在一起他们编写下面的测试:
[TestMethod] public void QuickNonZero() { // Create an instance to test: LocalMath math = new LocalMath(); // Create a test input and expected value: var expectedResult = 4.0; var inputValue = expectedResult * expectedResult; // Run the method: var actualResult = math.SquareRoot(inputValue); // Validate the result: var allowableError = expectedResult/1e6; Assert.AreEqual(expectedResult, actualResult, allowableError, "{0} is not within {1} of {2}", actualResult, allowableError, expectedResult); }
提示 |
此函数中,彼得使用测试第一个开发,他首先将写入的特征的单元测试,然后将满足测试的代码。 在其他情况下,他发现这种做法是不现实的所以相反,他在编写代码后,他会写测试。 但他认为编写单元测试非常重要 — — 是否前面或后面的代码 — — 因为它们使代码保持稳定。 |
Peter 遵循的周期中他反复写一个测试并确认它无法正常工作,在编写代码以使该测试通过,然后考虑重构和 — — 即提高代码,而无需更改测试。
红色
Peter 按下 CTRL + R T 运行 Julia 用他创建的新测试。 他将所有测试之后,他始终运行它,以确保它出现故障之前他都写的代码,使其通过。 这是他了解到他忘了将断言放在他写了一些测试后一种做法。 查看失败的结果,他可以放心当他通过,测试结果正确指示要求已得到满足。
另一种有益的做法是将设置生成后运行的测试。 此选项将运行测试在后台每次生成解决方案,以便您具有连续的测试代码的状态报告。 Peter 已开始时它可能会使 Visual Studio 的缓慢响应,但他发现这很少发生可疑。
绿色
Peter 写他第一次尝试的方法,他正在开发的代码:
public class LocalMath { public double SquareRoot(double x) { double estimate = x; double previousEstimate = -x; while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000) { previousEstimate = estimate; estimate = (estimate * estimate - x) / (2 * estimate); } return estimate; }
Peter 将再次运行测试并通过了所有测试:
重构
现在,该代码将执行它的主要作用,Peter 探讨寻找方法使它更好,或以使其更易于在将来更改的代码。 他意识到他可以减少在循环中执行计算的数量:
public class LocalMath { public double SquareRoot(double x) { double estimate = x; double previousEstimate = -x; while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000) { previousEstimate = estimate; estimate = (estimate + x / estimate) / 2; //was: estimate = (estimate * estimate - x) / (2 * estimate); } return estimate; }
他核实仍然通过了测试:
提示 |
每次更改您进行开发代码时应重构或扩展:
如果要更新现有代码已更改的要求,您将同时删除旧不再代表当前要求的测试。 应避免更改的已通过测试。 而是添加新的测试。 仅编写表示实际要求的测试。 每次更改后运行的测试。 |
...,然后重复
Peter 继续他系列的扩展和重构的步骤,作为一个粗略的使用他的小步骤的列表。 他始终不在每个扩展之后, 执行重构的步骤,他有时连续执行重构的多个步骤。 但他始终对代码运行单元测试在每次更改后。
有时,他补充道,不需要任何更改的代码,但到他确信他的代码能够正常工作,它将一个测试。 例如,他要确保该函数可以通过输入一系列工作。 他将更多的测试,这样:
[TestMethod] public void SqRtValueRange() { LocalMath math = new LocalMath(); for (double expectedResult = 1e-8; expectedResult < 1e+8; expectedResult = expectedResult * 3.2) { VerifyOneRootValue(math, expectedResult); } } private void VerifyOneRootValue(LocalMath math, double expectedResult) { double input = expectedResult * expectedResult; double actualResult = math.SquareRoot(input); Assert.AreEqual(expectedResult, actualResult, expectedResult / 1e6); }
此测试通过第一次运行:
只是为了确保这个结果不是一个错误,他暂时引入一个小错误他测试,以使其失败。 后出现故障,他修复了它再次。
提示 |
始终进行一次测试之前使其通过失败。 |
例外情况
彼得现在移到优异的输入为编写测试:
[TestMethod] public void RootTestNegativeInput() { LocalMath math = new LocalMath(); try { math.SquareRoot(-10.0); } catch (ArgumentOutOfRangeException) { return; } catch { Assert.Fail("Wrong exception on negative input"); return; } Assert.Fail("No exception on negative input"); }
此测试将代码放到一个循环。 他又要使用取消测试资源管理器中的按钮。 这将在 10 秒内终止代码。
Peter 想要确保无限循环可能会生成服务器上发生。 虽然服务器施加在完成超时运行,它是非常长的超时值,并会导致长时间延迟。 因此,他将显式超时添加到此测试:
[TestMethod, Timeout(1000)] public void RootTestNegativeInput() {...
显式超时使测试失败。
Peter 然后更新代码以处理此种例外情况:
public double SquareRoot(double x) { if (x <= 0.0) { throw new ArgumentOutOfRangeException(); }
回归
新测试通过,但没有回归。 现在将失败的测试,用来传递:
Peter 查找并修复错误:
public double SquareRoot(double x) { if (x < 0.0) // not <= { throw new ArgumentOutOfRangeException(); }
它固定后,所有测试都通过:
提示 |
确保每个测试阶段后对代码所做的每项更改。 |
按时间间隔期间他的工作,并最后他在签入代码之前,Peter 获取代码覆盖率报告。 此图显示代码中有多少已经执行他测试。
Peter 先生的团队的目标是至少 80%的覆盖率。 由于可能很难实现高覆盖率为此类型的代码,它们可以放宽对生成的代码,此要求。
良好的覆盖范围并不保证该组件的全部功能都已经过测试,并不保证该代码将用于每个输入值的范围。 然而,没有任何相当关闭关系之间的代码行的覆盖范围和覆盖范围的组件的行为的空间。 因此,良好的覆盖范围可增强团队的信心,他们正在测试大部分应有的行为。
若要获取代码覆盖率报表中,在测试
菜单上,选择 运行, 分析所有测试的代码覆盖率。 然后再次运行所有测试。
Peter 获取总没有覆盖到的 86%。 当他在扩展中报表的合计时,它表明他正在开发的代码覆盖率 100%。 由于接受测试的代码是重要的分数,这是非常令人满意。 未覆盖的部分实际上是在自己的测试。 通过切换显示代码覆盖率突出显示按钮,Peter 可以看到尚未测试的测试代码中的哪些部分。 但是,他决定这些部分是覆盖范围并不重要,因为它们所测试的代码中,如果检测到错误,则将只使用。
若要验证特定测试达到到代码的特定分支,您可以设置显示代码覆盖率突出显示 ,然后通过运行单个测试 运行在其快捷菜单上的命令。
Peter 继续更新中少量的代码步骤,直到他是满意的:
- 所有可用的单元测试通过。
具有非常大的一组单元测试的项目,可以是为开发人员等待他们所有运行不切实际。 相反,该项目运行封闭的签入服务,所有运行自动的测试的每个签入的搁置集之前它将合并到源树。 在签入被拒绝在运行失败时。 这样,开发人员可以在自己的计算机上运行的最少量的单元测试,然后继续其他工作,而不会中断生成的风险。 有关详细信息,请参阅 使用封闭签入生成过程以验证更改. - 代码覆盖率满足团队的标准。 75%是典型的项目的要求。
- 他的单元测试模拟是必需的其中包括典型和优秀输入行为的各个方面。
- 他的代码易于理解和扩展。
所有这些条件都满足,Peter 已准备就绪,他代码签入到源代码管理。
使用单元测试的代码开发的原则
彼得在开发代码时适用以下原则:
- 开发单元测试和代码,并经常在开发过程中运行它们。 单元测试表示组件的规格。
- 除非需求已更改或测试了错误,则不会更改单元测试。 添加新测试逐渐扩展代码的功能。
- 目标至少 75%的被测试所覆盖的代码。 时间间隔以及签入源代码之前查看代码覆盖率结果。
- 签入代码和单元测试,以便它们可以通过连续或定期运行服务器构建。
- 在实际应用,对于每个功能中,编写单元测试第一次。 开发满足它的代码之前执行此操作。
之前他将更改签入,Peter 再次使用 Lync 与他的同事 Julia 共享他的屏幕,这样她可以非正式的方式,并以交互方式查看与他他已创建。 测试仍然是他们讨论的重点,因为朱丽亚是主要感兴趣的代码不会,没有它的工作原理。 朱丽亚同意 Peter 已写的内容能满足她的需要。
Peter 签入所有他具有所做的更改,包括测试和代码,并将它们与完成他的任务相关联。 在签入团队的团队自动的生成系统来验证使用该小组的 CI 生成的生成过程他更改进行排队。 此生成过程,从而帮助最小化中的错误团队及其基本代码的生成和测试 — 在干净环境中独立的开发计算机 — — 团队取得的每项更改。
当生成完成时,将通知 Peter。 在生成结果窗口、 成功生成的他看到和所有测试传递。
若要签入的更改
- 在菜单栏中,选择查看, 团队资源管理器。
- 在团队资源管理器,选择 主页 ,然后选择 我的工作。
- 在我的工作 页面上,选择 签入。
- 检查的内容挂起的更改页面,以确保:
- 中列出了所有相关的更改包含更改
- 所有相关的工作项中列出相关的工作项。
- 指定注释若要帮助您的团队了解这些更改的目的,当他们考虑的已更改的文件和文件夹的版本控制历史记录。
- 选择签入。
持续集成代码
有关如何定义一个持续集成生成过程的详细信息,请参阅
设置 CI 生成.
您已经设置了此生成过程后,您可以选择要通知的团队生成的结果。
有关详细信息,请参阅 运行、监视和管理生成.
博客转自:《ALM 开发人员生活中的一天:为用户情景编写新代码》