【问题标题】:Best practice for unit test cases单元测试用例的最佳实践
【发布时间】:2021-09-08 02:54:31
【问题描述】:

我正在使用xUnit.net 测试框架,并且在每个单元测试中我都有一些我在每种情况下都在执行的步骤。我想知道是否有一种方法可以在我的单元案例开始之前调用此方法一次,并在所有单元测试案例都已执行时调用。

例如:在下面的场景中,我有两个单元案例,在每个案例中,我都创建了一个本地数据库,用数据填充它,然后运行我的测试,一旦完成,我就会调用方法来删除数据库。我在每个测试用例中都这样做。而不是多次创建,我想创建一次并填充一次,然后在执行所有测试用例后删除 db。删除我创建的内容对我来说很重要,因为如果在执行测试时未创建数据库,测试用例中的某些情况将失败。

[Fact]
public void UnitCase1()
{
   CreateDb();
   UploadData();
   ...//My set of operation to test this case
   ...//Assert
   DeleteDb()
}

[Fact]
public void UnitCase2()
{
   CreateDb();
   UploadData();
   ...//My set of operation to test this case
   ...//Assert
   DeleteDb()
}

在 Eric 回答后编辑:(我试过但它不起作用)

public class CosmosDataFixture : IDisposable
    {
        public static readonly string CosmosEndpoint = "https://localhost:8081";
        public static readonly string EmulatorKey = "Mykey";
        public static readonly string DatabaseId = "Databasename";
        public static readonly string RecordingCollection = "collectionName";
        string Root = Directory.GetParent( Directory.GetCurrentDirectory() ).Parent.Parent.FullName;
        DocumentClient client = null;

        public void ReadAllData( DocumentClient client )
        {
           //reading document code
        }

        public void ReadConfigAsync()
        {
            client = new DocumentClient( new Uri( CosmosEndpoint ), EmulatorKey,
                 new ConnectionPolicy
                 {
                     ConnectionMode = ConnectionMode.Direct,
                     ConnectionProtocol = Protocol.Tcp

                 } );
        }
 public void CreateDatabase()
        {// create db code
        }
private void DeleteDatabase()
        {
          // delete db code
        }
     public CosmosDataFixture()
        {
            ReadConfigAsync();
            CreateDatabase();
            ReadAllData( client );

        }

        public void Dispose()
        {
            DeleteDatabase();
        }
    }
public class CosmosDataTests : IClassFixture<CosmosDataFixture>
    {
        CosmosDataFixture fixture;

        public CosmosDataTests( CosmosDataFixture fixture )
        {
            this.fixture = fixture;
        }

        [Fact]
        public async Task CheckDatabaseandCollectionCreation()
        {          
            List<string> collectionName = new List<string>();
            var uri = UriFactory.CreateDatabaseUri(DatabaseId);// don't get DatabaseId or client says does not exist in current context
            var collections = await client.ReadDocumentCollectionFeedAsync( uri );
            foreach( var collection in collections )
            {
                collectionName.Add( collection.Id);
            }
                
        }

【问题讨论】:

  • 如果您正在修改数据库以运行单元测试,那么您正在运行的不是单元测试。模拟你的数据库。
  • 你有没有看过 xunit 的文档? xunit.net/docs/comparisons
  • @gunr2171 在某些时候您需要测试数据库交互代码,如果没有真正的数据库就无法测试它。模拟对于测试调用数据访问代码的代码很有用,这里显然不相关。不是“技术上”的单元测试,而是集成测试,一种对现实生活没有影响的学术区别。
  • @gunr2171 如果您听说过 azure 中的 cosmos db,我们有来自 Microsoft cosmos db 模拟器的本地 db 支持,我在这里使用它来使用虚拟数据运行我的测试用例。它在在没有真实数据库的情况下测试行为。现在回到我原来的问题,有没有办法?由于我对编写单元测试用例非常陌生,因此任何小的代码示例都会有所帮助
  • 这里对单元测试和集成测试有一些误解。

标签: c# .net unit-testing tdd xunit


【解决方案1】:

根据我的测试经验,我在这里看到了 2 点: 1-如果您正在检查从数据库到程序中另一个点的数据是否正确传输,即集成测试,并且它应该超出单元测试计划的范围,请确保单元测试员的职责很清楚你在哪里工作,因为有些公司会通过假设如果功能测试“OK”,那么集成也应该是这样来避免集成测试级别。

2- 你在最后提到

删除我创建的内容对我来说很重要,因为测试用例有某些案例,如果未创建数据库 执行测试时会失败

但是

我想创建 once 并填充 once 然后删除 db once 所有测试用例都已执行。

如果我理解正确,您需要为每个测试用例执行此操作,因为并非所有测试用例都在检查相同的场景,所以看起来这些语句才是真正的问题。

要回答您的问题,因为您似乎希望以最少的维护为下一个版本自动化流程,而且我也知道工作环境如何倾向于让您做一些不应该做的事情,我可以想想一个前置条件函数和一个后置条件函数,你只做一次就可以了。

如果由于某种原因无法做到这一点,请尝试在开始时创建另一个测试用例(如测试用例 0),在其中创建和填充数据库(如果适用,或在需要时将其分开)并在最后创建另一个在哪里删除它。

我不熟悉您使用的框架,但我在测试、开放测试关卡和自动化任务方面有丰富的经验,希望我的回答能对您有所帮助。

【讨论】:

  • 您对测试用例 0 的最后陈述和最后的另一个陈述。我不确定我们是否可以在 xunit 框架中定义哪个测试用例首先运行以及哪个测试用例最后运行。如果可能的话..它肯定会回答我的问题。我将尝试前面答案中提到的构造函数部分
【解决方案2】:

这就是 [SetUp][TearDown] 在 NUnit 中的用途。它们分别在每个测试用例之前和之后运行。在 xUnit 中,您通常会实现默认构造函数和 IDisposable

例如:


public TestClass()
{
   CreateDb();
   UploadData();
}


public void Dispose()
{
   DeleteDb()
}

[Fact]
public void UnitCase1()
{
   ...//My set of operation to test this case
   ...//Assert
}

[Fact]
public void UnitCase2()
{
   ...//My set of operation to test this case
   ...//Assert
}

【讨论】:

  • 我按照你的方式做了,它似乎正在工作。唯一的问题是 Dispose 部分不起作用,即 DeleteDb() 不起作用。我做错什么了吗?我刚刚在构造函数之后创建了一个 public void Dispose() 并在其中调用了 deletedb。
  • 你需要实现IDisposable!
  • 我确实遵循了链接并按照 suggessted 实现了,但是当我尝试定义测试用例时,我没有得到我在单元测试用例中在我的类中定义的对象。看看我现在已经更新了我现有的代码的问题,我在这里做错了什么?
  • 你需要在测试类中实现IDisposable。你现在实现它的方式,我会在CosmosDataTests 中实现IDisposable,然后从那里调用fixture.Dispose()
【解决方案3】:

正如其他人所指出的,按照主流说法,此类测试不是单元测试,而是集成测试。 xUnit.net 是用于此类测试的一个很好的框架,因此除了语义上的区别之外,它几乎没有技术差异。

除了在测试类的构造函数中设置数据库并在Dispose 中拆除它,如 Eric Sc​​haefer 所述,您还可以使用 xUnit.net 的BeforeAfterTestAttribute。然后,您将覆盖 Before 以设置数据库,并覆盖 After 以将其拆除:

public class UseDatabaseAttribute : BeforeAfterTestAttribute
{
    public override void Before(MethodInfo methodUnderTest)
    {
        CreateDb();
        UploadData();

        base.Before(methodUnderTest);
    }

    public override void After(MethodInfo methodUnderTest)
    {
        base.After(methodUnderTest);
        DeleteDb();
    }
}

然后您可以使用该属性注释每个测试方法或整个测试类。我通常只是注释类:

[UseDatabase]
public class DbTests
{
    // Tests go here...
}

由于使用数据库的测试与共享资源(数据库)交互,因此它们不能轻松地并行运行。默认情况下,xUnit.net 并行运行测试,因此您可能希望禁用它。您可以通过添加xunit.runner.json 文件来做到这一点:

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
  "parallelizeTestCollections": false
}

最后,至少如果您使用的是 SQL Server,连接池将阻止您删除数据库。你可以为你的测试关闭连接池,或者forcibly close other connections before teardown

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多