現在、C#で書かれた以前にテストされていないサーバーに対してnunitを使用して統合テストを作成しています(ApiController
とEntity Frameworkを使用)。ほとんどのテストはうまくいきますが、私は2つに分かれて常にデータベースのタイムアウトを引き起こしています。エラーメッセージは次のようになります。統合テストによりEntity Frameworkのタイムアウトが発生する
System.Data.Entity.Infrastructure.DbUpdateException:エントリの更新中にエラーが発生しました。詳細については、内部例外を参照してください。
System.Data.Entity.Core.UpdateException:エントリの更新中にエラーが発生しました。詳細については、内部例外を参照してください。
System.Data.SqlClient.SqlException:タイムアウトが切れています。操作が完了する前にタイムアウト時間が経過したか、サーバーが応答していません。
System.ComponentModel.Win32Exception:[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());
}
をこれらはこれらのテストで使用されているコントローラメソッド: タイムアウトは、コントロール内のawait db.SaveChangesAsync()
への最初の呼び出しで常に発生しますエル。テスト中の他のコントローラーメソッドも、問題なくSaveChangesAsync
を呼び出します。私はまた、失敗したテストの中からSaveChangesAsync
を呼び出すことを試みました。これらのメソッドは、コントローラ内から呼び出されたときに正常に動作しますが、テストから呼び出されるとタイムアウトします。
[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();
}
}
[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);
}
// ...
}
タイムアウトを引き起こすステートメントは、最初に発生する 'await db.SaveChangesAsync()'です。 –
テストを単独で実行するとタイムアウトも発生しますか?トランザクションスコープでの統合テストでの非同期呼び出しは、デッドロックを引き起こす可能性があります。しかし、常に同じテストが失敗するのは変です。 'SaveChangesAsync'が実行するSQL文を確認してください。 –
これは役立つかもしれませんhttp://stackoverflow.com/a/17527759/1236044 – jbl