单元测试
这是MSTest的微软官方的教程:使用 MSTest 进行 C# 单元测试
这是MSTest的Fakes的教程:用 Microsoft Fakes 隔离测试代码
这是xunit的微软官方教程:使用 dotnet test 和 xUnit 在 .NET Core 中进行 C# 单元测试
本次使用的是xunit,首先nuget引用xunit,如果发现VS无法运行测试的话,请nuget引用xunit.runner.visualstudio。
测试驱动开发(TDD)
TDD的原理是在开发功能代码之前,先编写单元测试用例代码,测试代码确定需要编写什么产品代码。
可测试性驱动开发
这是什么呢。我是看老赵的博客看到的。我的TDD实践:可测试性驱动开发(上)。看完之后我陷入了思考,于是马上动手开始写一个测试用例。其中,老赵的示例中那个InternalsVisibleTo的用法如下列所示。
InternalsVisibleTo的用法
大多数情况下单元测试都应该是针对类的行为进行测试的,也就是public方法。当然也存在不同的观点。
如果想要对private方法进行测试的话,是有很多缺点的:
- 首先需要修改方法的访问限制需要从private改为public,这就破坏了面向对象的封装性。
- 再者,这其实测试的是类的具体实现细节,而不是类的行为。如果我们想要对类的内部进行重构的话,就会破坏测试,导致测试也必须重构。
如果必须对private方法进行测试,那么首先建议您把private修饰符改成internal,然后修改该项目(project)的AssemblyInfo.cs,它在项目的Debug或者Release文件夹下。代码如下:
[assembly: InternalsVisibleTo(“MyClass.Tests”)]
这表示MyClass.Tests这个测试项目可以访问该项目生产代码(production code)的internal方法。
我的测试示例
修改前
public class TestFactory : ITestFactory
{
private ITest test = null;
public void GetTestProperty(Property property, double[] value)
{
test = new TestThickness(value);
test.PropertyID = property.PropertyID;
test.TestID = TestID;
test.Time = DateTime.Now;
property.Tests.Add(test);
test.CheckLimit(property);
List<System.Data.SQLite.SQLiteParameter> paras = new List<System.Data.SQLite.SQLiteParameter>();
string values = string.Empty;
foreach (double dd in test.Value)
{
values += dd + ",";
}
values = values.Remove(values.Length - 1);
using (SQLiteConnection con = DBHelper.GetDBConnection())
{
string sqlStr = "insert into Test(ID,TestID,PropertyID,Name,Value,IsMore,IsLess) Values(@ID,@TestID,@PropertyID,@Name,@Value,@IsMore,@IsLess)";
con.Execute(sqlStr, new
{
ID = test.ID.ToString("N"),
TestID = test.TestID.ToString("N"),
PropertyID = test.PropertyID.ToString("N"),
Name = test.Name,
Value = values,
IsMore = test.IsMore,
IsLess = test.IsLess,
Time = test.Time
});
}
}
}
这有什么问题呢。1、该方法调用了数据库,单元测试是不应该依赖外部资源,比如数据库的,否则就变集成测试了,TDD也是一样啊。2、每个方法的职责应该很单一,示例方法中他有两个步骤,先把这个ITest接口给实例化了,然后根据这个对象写入数据库。很明显,这个方法可以分成两个方法。
修改后
public class TestFactory : ITestFactory
{
private readonly ITestSaveInDBFactory _testSaveInDBFactory;
private ITest test = null;
public Guid TestID { get; set; }
public TestFactory() : this(new TestSaveInDbFactory()) { }
internal TestFactory(ITestSaveInDBFactory testSaveInDBFactory)
{
this._testSaveInDBFactory = testSaveInDBFactory;
}
public void GetTestProperty(Property property, double[] value)
{
test = new TestThickness(value);
test.PropertyID = property.PropertyID;
test.TestID = TestID;
test.Time = DateTime.Now;
property.Tests.Add(test);
test.CheckLimit(property);
_testSaveInDBFactory.TestSaveInDB(test);
}
}
public interface ITestSaveInDBFactory
{
void TestSaveInDB(ITest test);
}
public class TestSaveInDbFactory : ITestSaveInDBFactory
{
public void TestSaveInDB(ITest test)
{
List<System.Data.SQLite.SQLiteParameter> paras = new List<System.Data.SQLite.SQLiteParameter>();
string values = string.Join(',',test.Value);
using (SQLiteConnection con = DBHelper.GetDBConnection())
{
string sqlStr = "insert into Test(ID,TestID,PropertyID,Name,Value,IsMore,IsLess) Values(@ID,@TestID,@PropertyID,@Name,@Value,@IsMore,@IsLess)";
con.Execute(sqlStr, new
{
ID = test.ID.ToString("N"),
TestID = test.TestID.ToString("N"),
PropertyID = test.PropertyID.ToString("N"),
Name = test.Name,
Value = values,
IsMore = test.IsMore,
IsLess = test.IsLess,
Time = test.Time
});
}
}
}
单元测试,其中例子中的Mock看Moq的使用方法
[Fact(DisplayName = "ITestFactory_GetTestProperty")]
public void ITestFactory_GetTestProperty()
{
var mock = new Mock<ITestSaveInDBFactory>();
var test = new Mock<ITest>();
var testFactory = new TestFactory(mock.Object);
mock.Setup(x => x.TestSaveInDB(test.Object));
var property = new Mock<Property>();
testFactory.GetTestProperty(property.Object, new double[1]);
Assert.True(property.Object.Tests.Count == 1);
Assert.True(property.Object.Tests[0] is TestThickness);
}
遇到一个问题
后来,我把ITestSaveInDBFactory接口弄成抽象类了,因为有其他操作数据库的地方,如果也弄成接口有点烦,索性我就弄个抽象类,然后弄好之后,这个方法我就在抽象类中直接实现了,因为在几个派生类中都是同样的实现方法,然后运行单元测试发现出错了,报“System.NotSupportedException invalid setup on a non-virtual member”的错。
mock.Setup(x => x.TestSaveInDB(test.Object));//这一行出错了
我把TestSaveInDB方法增加一个 virtual就解决了。