C# 如何为测试生成瞬态误差

C# 如何为测试生成瞬态误差,c#,asp.net-core,testing,ef-core-2.2,C#,Asp.net Core,Testing,Ef Core 2.2,我在.NETCore2.2中的API上实现了自定义重试策略。重试策略应仅适用于来自数据库(Azure SQL)的暂时错误。如何生成瞬态错误来测试此功能?我不知道您的代码,但这是一种解决方案。其思想是将重试逻辑与需要重试的实际操作分开,以便可以对每个操作进行测试 静态RetryHandler也可以工作,但这取决于您的需要 Psedo代码,不编译 public RetryHandler : IRetryHandler { private static List<Type> tra

我在.NETCore2.2中的API上实现了自定义重试策略。重试策略应仅适用于来自数据库(Azure SQL)的暂时错误。如何生成瞬态错误来测试此功能?

我不知道您的代码,但这是一种解决方案。其思想是将重试逻辑与需要重试的实际操作分开,以便可以对每个操作进行测试

静态RetryHandler也可以工作,但这取决于您的需要

Psedo代码,不编译

public RetryHandler : IRetryHandler
{
    private static List<Type> transientErros = new List<Type>
    {
        typeof(TimeoutException),
        typeof(SomeotherExceptionThatRequiresRetry),
    }

    public void RetryOnTransientError(Action action, int attempts = 3)
    {
        for (var i = 0; i < attempts; i++)
        {
            try
            {
                action();
                return;
            }
            catch(Exception e)
            {
                if(transientErros.Contains(e.GetType())
                    continue;

                throw;
            }
        }
    }
}

public class UserRepository
{
    private IMyDbContext context;
    private IRetryHandler retryHandler;

    public Repository(IMyDbContext context, IRetryHandler retryHandler)
    {
        this.context = context;
        this.retryHandler = retryHandler;
    }

    public void InsertUser(User user)
    {
        retryHandler.RetryOnTransientError(() => DoInsertUser(user));
    }

    private void DoInsertUser(User user)
    {
        // insert logic
        context.SaveChanges();
    }
}

[Test]
public void InsertUserRetriesOnTransientError()
{
    // Arrange
    var contextMock = new Mock<IMyDbContext>();
    var repository = new Repository(contextMock);
    var user = CreateUser();

    contextMock.Setup(x => x.SaveChanges()).Throws<TransientException>());

    // Act
    Assert.Throws<TransientException>(() => repository.InsertUser(user)); 

    // Assert
    // verify SaveChanges() was called 3 times
}
public RetryHandler:IRetryHandler
{
私有静态列表transienteros=新列表
{
类型(TimeoutException),
类型(其他一些例外的请求重试),
}
public void RetryOnTransientError(操作操作,int尝试次数=3)
{
对于(变量i=0;i<尝试次数;i++)
{
尝试
{
动作();
返回;
}
捕获(例外e)
{
if(transienteros.Contains)(如GetType())
继续;
投掷;
}
}
}
}
公共类用户存储库
{
私有IMyDbContext上下文;
私人IRetryHandler-retryHandler;
公共存储库(IMyDbContext上下文,IRetryHandler-retryHandler)
{
this.context=上下文;
this.retryHandler=retryHandler;
}
公共void插入器(用户)
{
retryHandler.RetryOnTransientError(()=>DoInsertUser(用户));
}
私有void DoInsertUser(用户)
{
//插入逻辑
SaveChanges();
}
}
[测试]
public void InsertUserRetriesOnTransientError()
{
//安排
var contextMock=new Mock();
var repository=新存储库(contextMock);
var user=CreateUser();
contextMock.Setup(x=>x.SaveChanges()).Throws();
//表演
Assert.Throws(()=>repository.InsertUser(用户));
//断言
//验证是否已调用SaveChanges()3次
}

我不知道您的代码,但这是一个解决方案。其思想是将重试逻辑与需要重试的实际操作分开,以便可以对每个操作进行测试

静态RetryHandler也可以工作,但这取决于您的需要

Psedo代码,不编译

public RetryHandler : IRetryHandler
{
    private static List<Type> transientErros = new List<Type>
    {
        typeof(TimeoutException),
        typeof(SomeotherExceptionThatRequiresRetry),
    }

    public void RetryOnTransientError(Action action, int attempts = 3)
    {
        for (var i = 0; i < attempts; i++)
        {
            try
            {
                action();
                return;
            }
            catch(Exception e)
            {
                if(transientErros.Contains(e.GetType())
                    continue;

                throw;
            }
        }
    }
}

public class UserRepository
{
    private IMyDbContext context;
    private IRetryHandler retryHandler;

    public Repository(IMyDbContext context, IRetryHandler retryHandler)
    {
        this.context = context;
        this.retryHandler = retryHandler;
    }

    public void InsertUser(User user)
    {
        retryHandler.RetryOnTransientError(() => DoInsertUser(user));
    }

    private void DoInsertUser(User user)
    {
        // insert logic
        context.SaveChanges();
    }
}

[Test]
public void InsertUserRetriesOnTransientError()
{
    // Arrange
    var contextMock = new Mock<IMyDbContext>();
    var repository = new Repository(contextMock);
    var user = CreateUser();

    contextMock.Setup(x => x.SaveChanges()).Throws<TransientException>());

    // Act
    Assert.Throws<TransientException>(() => repository.InsertUser(user)); 

    // Assert
    // verify SaveChanges() was called 3 times
}
public RetryHandler:IRetryHandler
{
私有静态列表transienteros=新列表
{
类型(TimeoutException),
类型(其他一些例外的请求重试),
}
public void RetryOnTransientError(操作操作,int尝试次数=3)
{
对于(变量i=0;i<尝试次数;i++)
{
尝试
{
动作();
返回;
}
捕获(例外e)
{
if(transienteros.Contains)(如GetType())
继续;
投掷;
}
}
}
}
公共类用户存储库
{
私有IMyDbContext上下文;
私人IRetryHandler-retryHandler;
公共存储库(IMyDbContext上下文,IRetryHandler-retryHandler)
{
this.context=上下文;
this.retryHandler=retryHandler;
}
公共void插入器(用户)
{
retryHandler.RetryOnTransientError(()=>DoInsertUser(用户));
}
私有void DoInsertUser(用户)
{
//插入逻辑
SaveChanges();
}
}
[测试]
public void InsertUserRetriesOnTransientError()
{
//安排
var contextMock=new Mock();
var repository=新存储库(contextMock);
var user=CreateUser();
contextMock.Setup(x=>x.SaveChanges()).Throws();
//表演
Assert.Throws(()=>repository.InsertUser(用户));
//断言
//验证是否已调用SaveChanges()3次
}

生成一个随机数,如果该数字满足给定条件,则抛出一个错误?@David刚刚建议在单元测试中添加一个随机数,没有任何指导或警告。。数百人很容易错误地实现这一点,因为他们没有设置一个随机种子,最终导致不可预测的单元测试,并且不知道预期的结果应该是什么。更好的建议是:在前n次调用时模拟并返回错误,在第(n+1)次调用时返回成功。消费者应重试。N+1次。不知道您是谁实现了这个,但是当您想要进行单元测试时,为测试而设计是一个重要的概念。将重试策略代码与API客户端代码分开。通过这种方式,您可以将客户端模拟为Azure SQL,并抛出您想要的任何异常。如果你愿意,我可以给你一个代码示例。@David单元测试应该是可重复和一致的,任何一种“随机性”都是不好的。很容易将单元测试设置为一次或两次“失败”特定调用,然后是“成功”,因此测试正在进行的测试的“瞬态”性质。@Vikas您是如何实现自定义策略的?使用Polly或其他方法?生成一个随机数,如果该数字满足给定条件,则抛出一个错误?@David刚刚建议在单元测试中添加一个随机数,而无需任何指导或警告。。数百人很容易错误地实现这一点,因为他们没有设置一个随机种子,最终导致不可预测的单元测试,并且不知道预期的结果应该是什么。更好的建议是:在前n次调用时模拟并返回错误,在第(n+1)次调用时返回成功。消费者应重试。N+1次。不知道您是谁实现了这个,但是当您想要进行单元测试时,为测试而设计是一个重要的概念。将重试策略代码与API客户端代码分开。通过这种方式,您可以将客户端模拟为Azure SQL,并抛出您想要的任何异常。如果你愿意,我可以给你一个代码示例。@David单元测试应该是可重复和一致的,任何一种“随机性”都是不好的。很容易将单元测试设置为一次或两次“失败”特定调用,然后是“成功”,因此测试