【面向对象语言系列】关于单元测试之道(C#)

什么是单元测试

定义

单元测试是编写额外代码(称为测试代码)的想法,目的是尽可能地测试最小的生产代码块,以确保这些代码块没有错误。

代码通常设计为类或对象。术语“类”和术语“对象”通常可互换使用。类由包含构造函数,方法和属性的变量和函数组成,但它们也可能包含子类。

对于代码中的每个对象,应该对该对象进行单元测试。对于对象中的每个方法,相关单元测试应该有一个测试。实际上,应该存在一个测试,确保代码的每一行都按预期运行。

(可能是)最佳实践/最佳做法和指南

  • 测试每个单元测试类的一个对象或类。
  • 在测试的类之后命名您的测试类。
  • 每个测试功能执行一次测试。
  • 单元测试应在构建和持续集成(CI)系统上运行。
  • 单元测试不应该以任何方式改变系统。
    不要连接文件,数据库,注册表,网络等…这样做的测试是功能测试,而不是单元测试。
  • 使测试功能名称自我记录。
  • 尽可能以最简单的方式进行测试。
  • 接受培训并不断学习单元测试。
    存在许多测试框架、模拟框架、包装器(例如System Wrapper)和封装问题,你会发现许多关于最佳实践的意见,但你应该知道意见的每一方以及为什么这些意见存在。

如何进行单元测试

以下使用 MSTest、xUnit以及Nunit框架实现面向C#的单元测试。

系统必备

使用MSTest进行单元测试

  • 命令行
    REM 创建并切换到名叫unit-testing-using-mstest的解决方案目录
    mkdir ./unit-testing-using-mstest
    cd ./unit-testing-using-mstest
    REM 创建新的解决方案,用于管理类库项目和单元测试项目
    dotnet new sln
    REM 创建并切换到名叫PositiveService的源项目目录
    mkdir ./PositiveService
    cd PositiveService
    REM 创建源项目并重命名源项目中的类库文件Class1.cs
    dotnet new classlib
    ren Class1.cs PositiveService.cs
    REM 修改PositiveService.cs代码(见2.PositiveService.cs代码)
    REM 切换到unit-testing-using-mstest的解决方案目录
    cd ..
    REM 向解决方案中添加源(类库项目)项目
    dotnet sln add ./PositiveService/PositiveService.csproj
    
  • 修改PositiveService.cs代码
    using System;
    
    namespace Positive.Service
    {
        public class PositiveService
        {
            public bool IsPositive(int candidate)
            {
                throw new NotImplementedException("Please create a test first");
            }
        }
    }
    
  • 目录结构
/unit-testing-using-mstest
    unit-testing-using-mstest.sln
    /PositiveService
        Source Files
        PositiveService.csproj

创建测试项目

  • 命令行
    REM 切换到名叫unit-testing-using-mstest的目录
    cd ./unit-testing-using-mstest
    REM 创建并切换到名叫PositiveService.Tests的测试库的测试项目目录
    mkdir ./PositiveService.Tests
    REM 创建名叫PositiveService.Tests的测试项目
    dotnet new mstest
    REM 将PositiveService类库作为依赖项添加到项目中
    dotnet add reference ../PositiveService/PositiveService.csproj
    REM 切换到名叫unit-testing-using-mstest的目录
    cd ..
    REM 向源项目解决方案添加测试项目
    dotnet sln add ./PositiveServiceTests/PositiveServiceTests.csproj
    
  • 查看PositiveServiceTest.csproj配置文件
<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
  <PackageReference Include="MSTest.TestAdapter" Version="1.1.18" />
  <PackageReference Include="MSTest.TestFramework" Version="1.1.18" />
</ItemGroup>
  • 目录结构
/unit-testing-using-mstest
    unit-testing-using-mstest.sln
    /PositiveService
        Source Files
        PositiveService.csproj
    /PositiveService.Tests
        Test Source Files
        PositiveServiceTests.csproj

创建第一个测试

  • 命令行
    REM 切换到新建的名叫PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 重命名UnitTest1.cs为PositiveServiceTests
    ren UnitTest1.cs PositiveServiceTests.cs
    REM 修改PositiveServiceTest.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试失败,修改PositiveService.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试成功
    
  • 修改PositiveServiceTest.cs内容
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Positive.Service;
    
    namespace Positive.UnitTests.Service
    {
        public class PositiveService_IsPositiveShould
        {
            private readonly PositiveService _positiveService;
    
            public PositiveService_IsPositiveShould()
            {
               _positiveService = new PositiveService();
            }
    
            [Fact]
            public void ReturnFalseGivenValueOfn1()
            {
                var result = _positiveService.IsPositive(-1);
    
                Assert.False(result, "1 should not be prime");
            }
        }
    }
    
  • 修改PositiveService.cs类库代码,使测试通过
    public bool IsPositive(int candidate)
    {
        if (candidate == 0)
        {
            return false;
        }
        throw new NotImplementedException("Please create a test first");
    }
    

添加更多功能

  • 命令行
    REM 切换到PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 修改测试代码(见下方修改测试代码)
    REM 运行测试
    dotnet test
    
  • 修改PositiveServiceTest.cs测试代码
    [DataTestMethod]
    [DataRow(-1)]
    [DataRow(0)]
    [DataRow(1)]
    public void ReturnFalseGivenValuesLessThan1(int value)
    {
        var result = _positiveService.IsPositive(value);
        
        Assert.False(result, $"{value} should not be positive");
    }
    
注意:
  • 使用MSUnit属性[Fact]需要编写重复测试;使用MSUnit属性[DataTestMethod]测试套件,避免重复编写
  • MSUnit属性[DataTestMethod] 执行相同代码,但具有不同输入参数的测试套件
  • MSUnit属性[DataRow] 指定这些输入的值

使用xUnit进行单元测试

创建源项目

  • 命令行
    REM 创建并切换到名叫unit-testing-using-dotnet-test的解决方案目录
    mkdir ./unit-testing-using-dotnet-test
    cd ./unit-testing-using-dotnet-test
    REM 创建新的解决方案,用于管理类库项目和单元测试项目
    dotnet new sln
    REM 创建并切换到名叫PositiveService的源项目目录
    mkdir ./PositiveService
    cd PositiveService
    REM 创建源项目并重命名源项目中的类库文件Class1.cs
    dotnet new classlib
    ren Class1.cs PositiveService.cs
    REM 修改PositiveService.cs代码(见2.PositiveService.cs代码)
    REM 切换到unit-testing-using-dotnet-test的解决方案目录
    cd ..
    REM 向解决方案中添加源(类库项目)项目
    dotnet sln add ./PositiveService/PositiveService.csproj
    
  • 修改PositiveService.cs代码
    using System;
    
    namespace Positive.Service
    {
        public class PositiveService
        {
            public bool IsPositive(int candidate)
            {
                throw new NotImplementedException("Please create a test first");
            }
        }
    }
    
  • 目录结构
/unit-testing-using-dotnet-test
    unit-testing-using-dotnet-test.sln
    /PositiveService
        Source Files
        PositiveService.csproj

创建测试项目

  • 命令行
    REM 切换到名叫unit-testing-using-dotnet-test的目录
    cd ./unit-testing-using-dotnet-test
    REM 创建并切换到名叫PositiveService.Tests的测试库的测试项目目录
    mkdir ./PositiveService.Tests
    REM 创建名叫PositiveService.Tests的测试项目
    dotnet new xunit
    REM 将PositiveService类库作为依赖项添加到项目中
    dotnet add reference ../PositiveService/PositiveService.csproj
    REM 切换到名叫unit-testing-using-dotnet-test的目录
    cd ..
    REM 向源项目解决方案添加测试项目
    dotnet sln add ./PositiveServiceTests/PositiveServiceTests.csproj
    
  • 查看PositiveServiceTest.csproj配置文件
<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
  <PackageReference Include="xunit" Version="2.2.0" />
  <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
</ItemGroup>
  • 目录结构
/unit-testing-using-dotnet-test
    unit-testing-using-dotnet-test.sln
    /PositiveService
        Source Files
        PositiveService.csproj
    /PositiveService.Tests
        Test Source Files
        PositiveServiceTests.csproj

创建第一个测试

  • 命令行
    REM 切换到新建的名叫PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 重命名UnitTest1.cs为PositiveServiceTests
    ren UnitTest1.cs PositiveServiceTests.cs
    REM 修改PositiveServiceTest.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试失败,修改PositiveService.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试成功
    
  • 修改PositiveServiceTest.cs内容
    using Xunit;
    using PositiveService;
    
    namespace PositiveService.UnitTests
    {
        public class PositiveService_IsPositiveShould
        {
            private readonly PositiveService _positiveService;
    
            public PositiveService_IsPositiveShould()
            {
               _positiveService = new PositiveService();
            }
    
            [Fact]
            public void ReturnFalseGivenValueOfn1()
            {
                var result = _positiveService.IsPositive(-1);
    
                Assert.False(result, "-1 should not be positive");
            }
        }
    }
    
  • 修改PositiveService.cs类库代码,使测试通过
    public bool IsPositive(int candidate)
    {
        if (candidate == -1)
        {
            return false;
        }
        throw new NotImplementedException("Please create a test first");
    }
    

添加更多功能

  • 命令行
    REM 切换到PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 修改测试代码(见下方修改测试代码)
    REM 运行测试
    dotnet test
    
  • 修改PositiveServiceTest.cs测试代码
    [Theory]
    [InlineData(-1)]
    [InlineData(0)]
    [InlineData(1)]
    public void ReturnFalseGivenValuesLessThan1(int value)
    {
        var result = _positiveService.IsPositive(value);
        
        Assert.False(result, $"{value} should not be positive");
    }
    
注意:
  • 使用xUnit属性[Fact]需要编写重复测试;使用xUnit属性[Theory]测试套件,避免重复编写
  • xUnit属性[Theory] 执行相同代码,但具有不同输入参数的测试套件
  • xUnit属性[InlineData] 指定这些输入的值

使用NUnit进行单元测试

  • 命令行
    REM 创建并切换到名叫unit-testing-using-nunit的解决方案目录
    mkdir ./unit-testing-using-nunit
    cd ./unit-testing-using-nunit
    REM 创建新的解决方案,用于管理类库项目和单元测试项目
    dotnet new sln
    REM 创建并切换到名叫PositiveService的源项目目录
    mkdir ./PositiveService
    cd PositiveService
    REM 创建源项目并重命名源项目中的类库文件Class1.cs
    dotnet new classlib
    ren Class1.cs PositiveService.cs
    REM 修改PositiveService.cs代码(见2.PositiveService.cs代码)
    REM 切换到unit-testing-using-nunit的解决方案目录
    cd ..
    REM 向解决方案中添加源(类库项目)项目
    dotnet sln add ./PositiveService/PositiveService.csproj
    
  • 修改PositiveService.cs代码
    using System;
    
    namespace Positive.Services
    {
        public class PositiveService
        {
            public bool IsPositive(int candidate)
            {
                throw new NotImplementedException("Please create a test first");
            }
        }
    }
    
  • 目录结构
/unit-testing-using-nunit
    unit-testing-using-nunit.sln
    /PositiveService
        Source Files
        PositiveService.csproj

创建测试项目

  • 命令行
    REM 切换到名叫unit-testing-using-nunit的目录
    cd ./unit-testing-using-nunit
    REM 创建并切换到名叫PositiveService.Tests的测试库的测试项目目录
    mkdir ./PositiveService.Tests
    REM 创建名叫PositiveService.Tests的测试项目
    dotnet new mstest
    REM 将PositiveService类库作为依赖项添加到项目中
    dotnet add reference ../PositiveService/PositiveService.csproj
    REM 切换到名叫unit-testing-using-nunit的目录
    cd ..
    REM 向源项目解决方案添加测试项目
    dotnet sln add ./PositiveServiceTests/PositiveServiceTests.csproj
    
  • 查看PositiveServiceTest.csproj配置文件
<ItemGroup>
  <PackageReference Include="nunit" Version="3.10.1" />
  <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.8.0" />
</ItemGroup>
  • 目录结构
/unit-testing-using-nunit
    unit-testing-using-nunit.sln
    /PositiveService
        Source Files
        PositiveService.csproj
    /PositiveService.Tests
        Test Source Files
        PositiveServiceTests.csproj

创建第一个测试

  • 命令行
    REM 切换到新建的名叫PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 重命名UnitTest1.cs为PositiveServiceTests
    ren UnitTest1.cs PositiveServiceTests.cs
    REM 修改PositiveServiceTest.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试失败,修改PositiveService.cs代码(见下方的修改代码)
    REM 生成测试和类库,之后运行测试
    dotnet test
    REM 测试成功
    
  • 修改PositiveServiceTest.cs内容
    using NUnit.Framework;
    using Prime.Services;
    
    namespace Positive.UnitTests.Services
    {
    	[TestFixture]
        public class PositiveService_IsPositiveShould
        {
            private readonly PositiveService _positiveService;
    
            public PositiveService_IsPositiveShould()
            {
               _positiveService = new PositiveService();
            }
    
            [Test]
            public void ReturnFalseGivenValueOfn1()
            {
                var result = _positiveService.IsPositive(-1);
    
                Assert.False(result, "1 should not be prime");
            }
        }
    }
    
  • 修改PositiveService.cs类库代码,使测试通过
    public bool IsPositive(int candidate)
    {
        if (candidate == 0)
        {
            return false;
        }
        throw new NotImplementedException("Please create a test first");
    }
    
注意
  • [TestFixture] 指示包含单元测试的类
  • [Test] 指示方法是测试方法

添加更多功能

  • 命令行
    REM 切换到PositiveService.Tests的测试项目目录
    cd ./PositiveService.Tests
    REM 修改测试代码(见下方修改测试代码)
    REM 运行测试
    dotnet test
    
  • 修改PositiveServiceTest.cs测试代码
    [TestCase(-1)]
    [TestCase(0)]
    [TestCase(1)]
    public void ReturnFalseGivenValuesLessThan1(int value)
    {
        var result = _positiveService.IsPositive(value);
        
        Assert.False(result, $"{value} should not be positive");
    }
    
注意:
  • 使用NUnit属性[Test]需要编写重复测试;使用NUnit属性[TestCase]测试套件,避免重复编写
  • NUnit属性[TestCase] 创建一套可执行相同代码但具有不同输入参数的测试

MSTest、xUnit以及NUnit方案对比

(内容待更新)

为何进行单元测试

优点

  • 比执行功能测试节省时间
    功能测试费用高。
  • 防止回归
    回归缺陷是在对应用程序进行更改时引入的缺陷。
    可执行文档
    每个测试应能够清楚地解释给定输入的预期输出。
    减少耦合代码
    为代码编写测试会自然地解耦代码。

参考文献

单元测试最佳实践和指南
.NET Core 和.NET Standard中的单元测试

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值