在.Net Core中测试
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
测试是我们需要做的,它可以增强我们对所构建产品的信心。毕竟,我们想要交付能够正常工作的软件。我们知道错误难免会发生,所以我们需要严格遵守测试规范,最好为每个错误添加一个测试,这样我们至少知道我们已经修复了那个错误。这就像一场打地鼠的游戏,我们必须保持跟进。然而,测试的方法有很多种,例如单元测试、集成测试和端到端测试。在本文中,我们将重点介绍单元测试,并确保我们采用一些良好的实践。
简而言之:这是一篇关于 .Net Core 测试的入门文章。如果您是 .Net Core 测试新手,或者完全不懂 .Net Core 测试,那么这篇文章非常适合您。之后我会写一篇更高级的测试文章,讨论对象母体、模拟测试以及其他相关内容。
在本文中,我们将介绍
- 创建测试项目并运行测试
- 不同类型的测试,如果您对测试完全陌生并且需要了解不同阶段和级别的入门知识,我们将在这里提到不同类型的测试。
- 编写测试,这里我们将介绍一些命名和安排测试的良好做法。
.Net Core 支持使用以及进行测试MsTest
,因此您有很多选择。在本文中,我们选择了。查看参考部分以获取其他框架的链接。xUnit
nUnit
MsTest
参考
-
xUnit 测试
本页介绍如何将 xUnit 与 .Net Core 结合使用 -
nUnit 测试
本页介绍如何将 nUnit 与 .Net Core 一起使用。 -
dotnet 测试,终端命令描述
本页描述了终端命令dotnet test
以及可以用来调用它的所有不同参数。 -
dotnet 选择性测试
本页介绍如何进行选择性测试以及如何设置过滤器和使用过滤器进行查询。
为什么
我们在文章开头提到,测试非常重要,尤其是在发现 bug 的情况下。我认为,测试是开发者工具箱中最重要的工具。学会检查你的工作不仅能为你在客户和同事中树立良好的声誉。你越擅长测试代码,并找到各种测试方法,对每个人都越有利。
不同类型的测试
测试有多种类型,用于软件生命周期的不同阶段。您还可以编写不同级别的测试来测试细节或大规模行为。我们通常说单元测试用于测试实现细节,而集成测试则用于测试两个或多个更大的组件是否协同工作。我们应该在各个级别和生命周期进行测试。
最后测试
我们已经完成了整个软件的编写,所谓的“快乐路径”似乎行得通。快乐路径指的是我们认为客户会用来执行任务的场景,或者运行我们软件的其他程序,也可能是其他软件 :)。在这种情况下,我们意识到如果发生意外情况,我们可能会遇到麻烦,所以我们开始在此时添加测试并记录日志,以确保我们的软件涵盖了我们认为可能发生的一些场景。在这个时候添加测试通常感觉很麻烦,坦白说,一点也不好玩。但我们必须添加它们,才能称自己是专业人士。
回归测试
嗯,我想说测试应该一直伴随着你,要时刻思考这个问题:它会不会出问题?是输入的类型问题,是内存消耗问题,还是存在一个依赖关系,它会以我们意想不到的方式给出答案?
当然,你不可能事事都考虑到,即使你尽力尝试,这种在代码编写之前、编写过程中和编写之后的深思熟虑的分析也肯定会让代码变得更好。正因为你无法事事都考虑到,所以难免会有bug。一个好的做法是尝试编写一个测试来证明bug的存在。一旦确定了,你就可以继续修复bug,并确保测试通过。至少你已经解决了那个症状/bug。
以上并非真正的回归测试,而是在发现 bug 时的一种良好实践。回归测试更多是指在进行变更(例如添加功能或修复 bug)时,重新运行一些测试以确保一切正常。回归测试与代码紧密相关,你应该了解这个术语。
重构
当然,重构还有另一种情况。我见过的大多数代码库随着时间的推移,随着越来越多的功能被添加进来,或者功能本身发生变化,变得杂乱无章。曾经易于理解的代码变成了一团乱麻。这时,你很可能想从头开始。进行测试是一个很好的方法,可以让你确信即使你修改了代码,即使你停止通过重构改进,它仍然能够正常工作。
测试驱动开发,测试优先
这是一种方法论,在实际开始编写任何生产代码之前,你需要构思并编写测试。其理念是,先从失败的测试开始,然后编写生产代码,直到测试通过。这被称为红绿测试。红色表示测试失败,绿色表示测试通过。有些人喜欢这种工作方式,而另一些人则觉得它有些限制。积极的一面是,你不会编写不必要的代码,因为你编写的所有代码都是为了通过特定的测试场景。
集成测试
这通常是一些高级测试,我们测试不同组件(系统的主要组成部分)是否能够协同工作。通常是某一层与另一层通信,例如 API层与数据层通信,甚至是一个单独的组件与另一个组件通信,这些组件共同构成了一个庞大而复杂的系统。
什么 - 创建演示
在本文中,我们的目标是在较低的级别(即单元测试级别)进行测试。我们将展示如何轻松创建测试项目、编写测试并运行它们。希望我们也能提供一些关于编写测试以及如何改进测试的指导。我们将进行以下操作:
- 搭建测试项目
- 创作测试
- 运行测试
- 改进我们的测试
搭建测试项目
本质上,我们希望创建一个解决方案,其中应用程序代码作为一个项目,测试代码则放在另一个单独的项目中。因此,整体解决方案如下:
solution
app // project containing our implementation
app-test // project containing our tests
创建解决方案
我们先创建一个目录。这里面存放着我们的解决方案。这个目录可以随意命名,但我们的命名方式TestExample
如下:
mkdir TestExample
接下来我们将通过调用以下命令来创建解决方案:
cd TestExample
dotnet new sln
创建库项目
此后,我们将创建一个库项目来保存我们的生产代码:
dotnet new classlib -o app
接下来我们要将项目引用添加到解决方案中,如下所示:
dotnet sln add app/app.csproj
创建测试项目
此后,我们要创建一个如下测试项目:
dotnet new mstest -o app-test
它应该产生类似这样的输出:
并添加对我们的解决方案的引用,如下所示:
dotnet sln add app-test/app-test.csproj
app
最后,我们需要在项目中添加对项目的引用,app-test
以便引用我们要测试的应用程序中的代码。为此,请输入以下命令:
cd app-test
dotnet add reference ../app/app.csproj
cd ..
编写测试
让我们创建一个文件CalculatorTest.cs
并赋予它以下内容:
// CalculatorTest.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace app_test
{
[TestClass]
public class CalculatorTest
{
[TestMethod]
public void Add()
{
Assert.AreEqual(1,1);
}
}
}
装饰器TestClass
意味着我们的类CalculatorTest
应该被视为测试类。TestMethod
我们添加到方法中的装饰器是Add()
,表示这是一个测试方法。
在 方法 bofy 中,Add()
我们添加了一个断言语句Assert.AreEqual(1,1)
。 的原型AreEqual()
是AreEqual(expected, actual)
。这就是我们创建测试所需的全部内容。
运行测试
现在,让我们运行它。我们可以通过以下方式之一来执行:
- 终端命令
此命令dotnet test
带有许多参数,因此请务必查看文档页面
Run Test
在 VS Code 中单击
正如您在上面看到的,我们也可以轻松地调试测试,如果我们查看类级别,将会有一个链接来调试/运行类中的所有测试。
我们有一个叫做 的东西filter
,可以用来运行我们针对的特定测试。让我们添加另一个测试,如下所示:
[TestMethod]
[Priority(1)]
public void Subtract()
{
Assert.AreEqual(1,2);
}
上面我们不仅添加了测试方法Subtract()
,还添加了装饰器Priority(1)
。我们可以通过输入以下内容进行过滤,以便只运行与此名称匹配的测试:
dotnet test --filter Priority=1
正如您在上面看到的,它只运行1
测试Subtract()
,它跳过了我们的Add()
测试。
我们可以添加另一个装饰器TestCategory
。让我们添加以下两个测试:
[TestMethod]
[TestCategory("DivideAndMultiply")]
public void Divide()
{
Assert.AreEqual(1,1);
}
[TestMethod]
[TestCategory("DivideAndMultiply")]
public void Multiply()
{
Assert.AreEqual(1, 1);
}
这两个测试具有相同的标签DivideAndMultiply
。我们可以通过输入 来过滤它dotnet test --filter TestCategory=DivideAndMultiply
。这TestCategory
使我们能够创建比 更具描述性的标签Priority
。
更真实的场景
好的,我们有一个测试类,CalculatorTest
但说实话,在生产环境中我们有多少次编写过计算器代码?是的,我没想到。我们很可能在做类似电商公司的事情。所以,让我们想象一下它是什么样子。我们来谈谈订单以及如何评估它们。
这很简单,我收到一份订单和一些商品,然后循环遍历它们,得到总数,然后添加一些运费,然后我就完成了:)
我希望它真的那么简单。刚开始的时候或许可以,但后来有人提到“折扣”这个词,当然,20% 的折扣很容易计算,但突然间它就变成了一个庞然大物。不知不觉中,你就得到了不同产品类型的折扣,买二送一的折扣,节日折扣,所有以特定方式标记的商品的折扣等等。相信我,折扣很快就会变成一个失控的庞然大物,需要一个团队来管理,你可以想象为了跟上这种局面,代码会是什么样子。
开始
让我们从小事做起。假设你刚刚启动了一个 WebShop,需要编写一些代码来支持计算订单金额。我们首先在项目Order.cs
中创建文件app
,并为其添加以下内容:
// Order.cs
using System;
using System.Collections.Generic;
namespace OrderSystem {
public enum ProductType
{
CD,
DVD,
Book,
Clothes,
Game
}
public class Product
{
public string Name { get; set; }
public string Description { get; set; }
public ProductType Type { get; set; }
}
public class OrderItem
{
public int Quantity { get; set; }
public double Price { get; set; }
public Product Product { get; set; }
}
public class Order
{
public List<OrderItem> Items { get; set; }
public DateTime Created { get; set; }
}
}
然后,我们创建一个文件OrderHelper.cs
,用于计算订单总额及相关Order
逻辑。目前,我们将其内容设置为:
namespace OrderSystem
{
public class OrderHelper
{
public OrderHelper()
{}
public static double Cost(Order order)
{
return 0;
}
}
}
好的,这没什么用,这就是重点,因为现在我们要编写一个测试。
让我们OrderHelperTest.cs
在项目中创建一个文件app-test
并赋予它以下内容:
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OrderSystem;
namespace app_test
{
[TestClass]
public class OrderHelperTest
{
[TestMethod]
public void ShouldSumCostOfOrder()
{
// Arrange
// Act
// Assert
}
}
}
创作时的良好做法
现在,我们来谈谈编写测试时的一些最佳实践。测试应该易于阅读。测试名称应该能够说明我们正在做什么以及我们希望得到的结果。命名能够ShouldSumCostOfOrder()
告诉我们发生了什么。你甚至可以更具体一些,例如描述结果ShouldSumOrderTo30()
。最终,这取决于你和你正在测试的内容。
接下来要查看的是三个A
s,Arrange
和Act
。Assert
在 中,你应该设置好所有需要的内容。在 中,Act
你应该调用你的生产代码。在最后Assert
一步,你应该验证你是否得到了预期的结果。
正如您所见,我们有一种方法ShouldSumCost()
。
编写测试
好了,我们对命名以及测试步骤有了更多的了解,接下来让我们来详细讲解一下测试。首先需要关注的是设置测试,也就是所谓的Arrange
阶段,代码如下:
// Arrange
var productCD = new Product()
{
Type = ProductType.CD,
Name = "Nirvana",
Description = "Album"
};
var productMovie = new Product()
{
Type = ProductType.DVD,
Name = "Gladiator",
Description = "Movie"
};
var order = new Order()
{
Items = new List<OrderItem>() {
new OrderItem(){
Quantity = 1,
Price = 10,
Product = productCD
},
new OrderItem(){
Quantity = 2,
Price = 10,
Product = productMovie
}
}
};
我们可以轻松地将它们移到另一个类中,以使测试更易于阅读。不过现在,让我们继续进行下一阶段Act
。
// Act
var actual = OrderHelper.Cost(order);
注意变量的命名actual
。编写测试时,使用像actual
和 这样的名称是一种很好的做法expected
,因为这些是其他开发人员熟悉的术语,这将使代码更容易阅读。
最后,我们进入Assert
检查代码是否按照我们的预期执行的阶段:
// Assert
Assert.AreEqual(30, actual);
完整文件OrderHelperTest.cs
应如下所示:
// OrderHelperTest.cs
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OrderSystem;
namespace app_test
{
[TestClass]
public class OrderHelperTest
{
[TestMethod]
public void ShouldSumCostOfOrder()
{
// Arrange
var productCD = new Product()
{
Type = ProductType.CD,
Name = "Nirvana",
Description = "Album"
};
var productMovie = new Product()
{
Type = ProductType.DVD,
Name = "Gladiator",
Description = "Movie"
};
var order = new Order()
{
Items = new List<OrderItem>() {
new OrderItem(){
Quantity = 1,
Price = 10,
Product = productCD
},
new OrderItem(){
Quantity = 2,
Price = 10,
Product = productMovie
}
}
};
// Act
var actual = OrderHelper.Cost(order);
// Assert
Assert.AreEqual(30, actual);
}
}
}
此时我们的测试将会失败
添加代码
好的,我们需要通过添加一个真正的实现来修复测试OrderHelper.cs
。将代码更改为以下内容:
using System.Linq;
namespace OrderSystem
{
public class OrderHelper
{
public static double Cost(Order order)
{
return order.Items.Sum(i => i.Price * i.Quantity);
}
}
}
重新运行测试我们得到:
实用功能
好的,我们已经带你了解了所谓的Red-Green
测试,首先编写一个测试,确保它失败,然后返回结果Red
。然后我们编写实现。最后,我们重新运行测试,如果测试通过,结果返回结果Green
。
很多时候,我们最终编写的测试都是通过代码来测试相同的流程,我们只需要改变输入以确保我们的生产代码确实有效。
以我们的CalculatorTest.cs
文件为例,首先Calculator.cs
在app
项目中给它一个真正的实现。现在给它添加以下内容:
// Calculator.cs
namespace app
{
public class Calculator
{
public static int Add(int lhs, int rhs)
{
return 0;
}
}
}
更新我们CalculatorTest.cs
现在说:
// CalculatorTest.cs
using app;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace app_test
{
[TestClass]
public class CalculatorTest
{
[TestMethod]
public void Add()
{
var actual = Calculator.Add(0,0);
Assert.AreEqual(actual,0);
}
}
}
此时运行测试意味着测试通过了。那么,我们现在可以相信代码能够正常工作了吗?我的意思是测试通过了,对吧?由于实现代码非常小,我们可以断定,如果更改输入,它将无法正常工作。但如果实现代码规模较大,可能就很难判断了。
让我们尝试找到一种方法!
将您的代码更改为以下内容:
// CalculatorTest.cs
using app;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace app_test
{
[TestClass]
public class CalculatorTest
{
[DataTestMethod]
[DataRow(0)]
public void Add(int value)
{
var actual = Calculator.Add(value, value);
var expected = value + value
Assert.AreEqual(actual, expected);
}
}
}
注意我们如何将TestMethod
其移除并替换为DataTestMethod
。另外,请注意我们如何添加DataRow
一个接受参数的 。现在,这个参数成为了输入参数value
。现在运行测试意味着它会进行计算0 + 0
,因此我们的测试仍然应该通过。让我们添加一些其他场景,使代码如下所示:
using System;
using app;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace app_test
{
[TestClass]
public class CalculatorTest
{
[DataTestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(2)]
public void Add(int value)
{
Console.WriteLine("Testing {0} + {0}", value);
var actual = Calculator.Add(value, value);
var expected = value + value;
Assert.AreEqual(actual,expected);
}
}
}
现在运行测试我们得到:
从上面我们可以看出, 可以0 + 0
通过1 + 1
,2 + 2
失败了。我们的代码无法运行,所以我们需要修复它。让我们将其更改Calculator.cs
为以下内容:
namespace app
{
public class Calculator
{
public static int Add(int lhs, int rhs)
{
return lhs + rhs;
}
}
}
现在如果我们运行测试,我们会得到:
我们刚才做的叫做数据驱动测试。文章已经够长了,就此打住吧。
概括
我们已指导您创建测试项目、编写一些测试并学习如何运行测试。此外,我们还讨论了一些命名方面的良好实践,以及如何通过数据驱动测试进一步改进您的测试。
我希望这篇文章对您有所帮助,并且您期待下一篇更深入的文章。
文章来源:https://dev.to/dotnet/testing-in-net-core-ojh