单元测试

单元测试

这是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就解决了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值