gpt4 book ai didi

c# - 集成测试导致 Entity Framework 超时

转载 作者:太空狗 更新时间:2023-10-29 21:43:33 27 4
gpt4 key购买 nike

我目前正在使用 nunit 为以前未测试的服务器编写集成测试,该服务器是使用 ApiController 和 Entity Framework 用 C# 编写的。大多数测试运行得很好,但我遇到了两个总是导致数据库超时的测试。错误消息看起来像这样:

System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
System.Data.Entity.Core.UpdateException : An error occurred while updating the entries. See the inner exception for details.
System.Data.SqlClient.SqlException : Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.
System.ComponentModel.Win32Exception : The wait operation timed out

第一个超时的测试:

    [TestCase, WithinTransaction]
public async Task Patch_EditJob_Success()
{
var testJob = Data.SealingJob;

var requestData = new Job()
{
ID = testJob.ID,
Name = "UPDATED"
};

var apiResponse = await _controller.EditJob(testJob.ID, requestData);
Assert.IsInstanceOf<StatusCodeResult>(apiResponse);

Assert.AreEqual("UPDATED", testJob.Name);
}

另一个超时的测试:

    [TestCase, WithinTransaction]
public async Task Post_RejectJob_Success()
{
var rejectedJob = Data.SealingJob;

var apiResponse = await _controller.RejectJob(rejectedJob.ID);
Assert.IsInstanceOf<OkResult>(apiResponse);

Assert.IsNull(rejectedJob.Organizations);
Assert.AreEqual(rejectedJob.JobStatus, JobStatus.OnHold);

_fakeEmailSender.Verify(
emailSender => emailSender.SendEmail(rejectedJob.Creator.Email, It.Is<string>(emailBody => emailBody.Contains(rejectedJob.Name)), It.IsAny<string>()),
Times.Once());
}

这些是这些测试使用的 Controller 方法:超时总是发生在第一次调用 Controller 内的 await db.SaveChangesAsync() 时。正在测试的其他 Controller 方法也可以毫无问题地调用 SaveChangesAsync。我还尝试从失败的测试中调用 SaveChangesAsync 并且在那里工作正常。他们调用的这两个方法在从 Controller 中调用时正常工作,但在从测试中调用时超时。

    [HttpPatch]
[Route("editjob/{id}")]
public async Task<IHttpActionResult> EditJob(int id, Job job)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (id != job.ID)
{
return BadRequest();
}

Job existingJob = await db.Jobs
.Include(databaseJob => databaseJob.Regions)
.FirstOrDefaultAsync(databaseJob => databaseJob.ID == id);

existingJob.Name = job.Name;

// For each Region find if it already exists in the database
// If it does, use that Region, if not one will be created
for (var i = 0; i < job.Regions.Count; i++)
{
var regionId = job.Regions[i].ID;
var foundRegion = db.Regions.FirstOrDefault(databaseRegion => databaseRegion.ID == regionId);
if (foundRegion != null)
{
existingJob.Regions[i] = foundRegion;
db.Entry(existingJob.Regions[i]).State = EntityState.Unchanged;
}
}

existingJob.JobType = job.JobType;
existingJob.DesignCode = job.DesignCode;
existingJob.DesignProgram = job.DesignProgram;
existingJob.JobStatus = job.JobStatus;
existingJob.JobPriority = job.JobPriority;
existingJob.LotNumber = job.LotNumber;
existingJob.Address = job.Address;
existingJob.City = job.City;
existingJob.Subdivision = job.Subdivision;
existingJob.Model = job.Model;
existingJob.BuildingDesignerName = job.BuildingDesignerName;
existingJob.BuildingDesignerAddress = job.BuildingDesignerAddress;
existingJob.BuildingDesignerCity = job.BuildingDesignerCity;
existingJob.BuildingDesignerState = job.BuildingDesignerState;
existingJob.BuildingDesignerLicenseNumber = job.BuildingDesignerLicenseNumber;
existingJob.WindCode = job.WindCode;
existingJob.WindSpeed = job.WindSpeed;
existingJob.WindExposureCategory = job.WindExposureCategory;
existingJob.MeanRoofHeight = job.MeanRoofHeight;
existingJob.RoofLoad = job.RoofLoad;
existingJob.FloorLoad = job.FloorLoad;
existingJob.CustomerName = job.CustomerName;

try
{
await db.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!JobExists(id))
{
return NotFound();
}
else
{
throw;
}
}

return StatusCode(HttpStatusCode.NoContent);
}

[HttpPost]
[Route("{id}/reject")]
public async Task<IHttpActionResult> RejectJob(int id)
{
var organizations = await db.Organizations
.Include(databaseOrganization => databaseOrganization.Jobs)
.ToListAsync();

// Remove job from being shared with organizations
foreach (var organization in organizations)
{
foreach (var organizationJob in organization.Jobs)
{
if (organizationJob.ID == id)
{
organization.Jobs.Remove(organizationJob);
}
}
}

var existingJob = await db.Jobs.FindAsync(id);
existingJob.JobStatus = JobStatus.OnHold;

await db.SaveChangesAsync();

await ResetJob(id);

var jobPdfs = await DatabaseUtility.GetPdfsForJobAsync(id, db);

var notes = "";
foreach (var jobPdf in jobPdfs)
{
if (jobPdf.Notes != null)
{
notes += jobPdf.Name + ": " + jobPdf.Notes + "\n";
}
}

// Rejection email
var job = await db.Jobs
.Include(databaseJob => databaseJob.Creator)
.SingleAsync(databaseJob => databaseJob.ID == id);
_emailSender.SendEmail(
job.Creator.Email,
job.Name + " Rejected",
notes);

return Ok();
}

其他可能相关的代码:

正在使用的模型只是一个普通的代码优先 Entity Framework 类:

public class Job
{
public Job()
{
this.Regions = new List<Region>();
this.ComponentDesigns = new List<ComponentDesign>();
this.MetaPdfs = new List<Pdf>();
this.OpenedBy = new List<User>();
}

public int ID { get; set; }
public string Name { get; set; }
public List<Region> Regions { get; set; }

// etc...
}

为了在测试之间保持数据库清洁,我使用这个自定义属性将每个属性包装在一个事务中(来自 http://tech.trailmax.info/2014/03/how-we-do-database-integration-tests-with-entity-framework-migrations/ ):

public class WithinTransactionAttribute : Attribute, ITestAction
{
private TransactionScope _transaction;

public ActionTargets Targets => ActionTargets.Test;

public void BeforeTest(ITest test)
{
_transaction = new TransactionScope();
}

public void AfterTest(ITest test)
{
_transaction.Dispose();
}
}

正在测试的数据库连接和 Controller 是在每次测试之前在设置方法中构建的:

[TestFixture]
public class JobsControllerTest : IntegrationTest
{
// ...

private JobsController _controller;
private Mock<EmailSender> _fakeEmailSender;

[SetUp]
public void SetupController()
{
this._fakeEmailSender = new Mock<EmailSender>();
this._controller = new JobsController(Database, _fakeEmailSender.Object);
}

// ...
}

public class IntegrationTest
{
protected SealingServerContext Database { get; set; }
protected TestData Data { get; set; }

[SetUp]
public void SetupDatabase()
{
this.Database = new SealingServerContext();
this.Data = new TestData(Database);
}

// ...
}

最佳答案

这个错误显然是由于在 TransactionScope 中使用 await 引起的。关注 this question 的最佳答案,我在构造 TransactionScope 时添加了 TransactionScopeAsyncFlowOption.Enabled 参数,超时问题消失了。

关于c# - 集成测试导致 Entity Framework 超时,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37448269/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com