perf: optimize ExcelIO tests with fixture-based consolidation

- Add WorkbookFixtureBase and 4 concrete fixtures for shared workbooks
- Add ExcelTestHelpers with shared utility methods
- Create Integration/ folder with 7 fixture-based test classes:
  - MinimalSearchTests (5 tests)
  - SearchResultsSheetTests (5 tests)
  - MisInfoSheetTests (11 tests)
  - InvestigationSheetTests (7 tests)
  - ProtectionAndStyleTests (7 tests)
  - LegacyFormatTests (5 tests)
  - LargeDataSetTests (1 test)
- Delete redundant ExcelExportIntegrationTests.cs (26 tests)
- Delete redundant LegacyComparisonTests.cs (16 tests)
- Reduce workbook generations from ~42 to 4 fixtures
- Test runtime reduced from ~18 min to ~4 min (76% improvement)
- All 122 ExcelIO tests pass
This commit is contained in:
Joseph Doherty
2026-01-07 03:55:33 -05:00
parent 884d3a093b
commit 6952f686fa
16 changed files with 1792 additions and 1209 deletions
@@ -0,0 +1,18 @@
using ClosedXML.Excel;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public static class ExcelTestHelpers
{
public static List<string> GetHeadersFromSheet(IXLWorksheet sheet)
{
var headers = new List<string>();
var col = 1;
while (!sheet.Cell(1, col).IsEmpty())
{
headers.Add(sheet.Cell(1, col).Value.GetText());
col++;
}
return headers;
}
}
@@ -0,0 +1,35 @@
using JdeScoping.ExcelIO.Models.Reporting;
using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public class LargeDataSetFixture : WorkbookFixtureBase
{
protected override SearchModel CreateSearchModel()
{
var model = new SearchModel
{
Id = 1,
Name = "Large Data Set Test",
UserName = "testuser",
SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45),
StartDt = new DateTime(2024, 1, 15, 14, 31, 0),
EndDt = new DateTime(2024, 1, 15, 14, 35, 0),
ExtractMisData = false,
Results = []
};
for (int i = 0; i < 1000; i++)
{
model.Results.Add(new SearchResult
{
WorkOrderNumber = 10000 + i,
ItemNumber = $"ITEM-{i:D5}",
LotNumber = $"LOT-{i:D5}",
Flagged = true
});
}
return model;
}
}
@@ -0,0 +1,18 @@
using JdeScoping.ExcelIO.Models.Reporting;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public class MinimalSearchFixture : WorkbookFixtureBase
{
protected override SearchModel CreateSearchModel() => new()
{
Id = 1,
Name = "Minimal Search Test",
UserName = "testuser",
SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45),
StartDt = new DateTime(2024, 1, 15, 14, 31, 0),
EndDt = new DateTime(2024, 1, 15, 14, 35, 0),
ExtractMisData = false,
Results = []
};
}
@@ -0,0 +1,87 @@
using JdeScoping.ExcelIO.Models.Reporting;
using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult;
using MisSearchResult = JdeScoping.Core.Models.SearchResults.MisSearchResult;
using MisNonMatchSearchResult = JdeScoping.Core.Models.SearchResults.MisNonMatchSearchResult;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public class WithMisDataFixture : WorkbookFixtureBase
{
protected override SearchModel CreateSearchModel() => new()
{
Id = 1,
Name = "Search With MIS Data",
UserName = "testuser",
SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45),
StartDt = new DateTime(2024, 1, 15, 14, 31, 0),
EndDt = new DateTime(2024, 1, 15, 14, 35, 0),
ExtractMisData = true,
Results =
[
new SearchResult
{
WorkOrderNumber = 12345,
WorkOrderBranchCode = "001",
LotNumber = "LOT-001",
ItemNumber = "ITEM-001",
PlanningFamily = "PF01",
StockingType = "M",
OrderQuantity = 100,
HeldQuantity = 0,
ScrappedQuantity = 0,
ShippedQuantity = 50,
StepBranchCode = "001",
StepNumber = 10,
StepDescription = "Assembly",
FunctionOperationDescription = "Main assembly",
StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0),
StatusCode = "50",
StatusDescription = "In Progress",
Flagged = true
}
],
MisResults =
[
new MisSearchResult
{
ItemNumber = "ITEM-001",
SequenceNumber = "010",
MisNumber = "MIS-001",
RevId = "A",
ItemDescription = "Test Item",
Status = "Released",
ReleaseDate = new DateTime(2023, 12, 15),
BranchCode = "001",
JobStepSequenceNumber = 10,
MatchedSequenceNumber = 10,
RoutingMatch = true,
MasterMatch = true,
FunctionOperationDescription = "Assembly operation",
CharNumber = "001",
TestDescription = "Sample test description",
SamplingType = "100%",
SamplingValue = "1",
ToolsGauges = "Gauge A, Gauge B",
WorkInstructions = "Step 1: Do this. Step 2: Do that."
}
],
MisNonMatchResults =
[
new MisNonMatchSearchResult
{
WorkCenterCode = "WC01",
WorkOrderNumber = 12345,
WorkOrderStartDate = new DateTime(2024, 1, 8),
JobStepNumber = 10,
JobStepDescription = "Test operation",
JobStepEndDate = new DateTime(2024, 1, 10),
FunctionCode = "FC01",
WasJobStepAdded = false,
MatchedJobStepNumber = 10,
ItemNumber = "ITEM-001",
ItemDescription = "Test Item Description",
RoutingType = "M"
}
]
};
}
@@ -0,0 +1,43 @@
using JdeScoping.ExcelIO.Models.Reporting;
using SearchResult = JdeScoping.Core.Models.SearchResults.SearchResult;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public class WithResultsFixture : WorkbookFixtureBase
{
protected override SearchModel CreateSearchModel() => new()
{
Id = 1,
Name = "Search With Results",
UserName = "testuser",
SubmitDt = new DateTime(2024, 1, 15, 14, 30, 45),
StartDt = new DateTime(2024, 1, 15, 14, 31, 0),
EndDt = new DateTime(2024, 1, 15, 14, 35, 0),
ExtractMisData = false,
Results =
[
new SearchResult
{
WorkOrderNumber = 12345,
WorkOrderBranchCode = "001",
LotNumber = "LOT-001",
ItemNumber = "ITEM-001",
PlanningFamily = "PF01",
StockingType = "M",
OrderQuantity = 100,
HeldQuantity = 0,
ScrappedQuantity = 0,
ShippedQuantity = 50,
StepBranchCode = "001",
StepNumber = 10,
StepDescription = "Assembly",
FunctionOperationDescription = "Main assembly operation",
StepUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0),
StatusCode = "50",
StatusDescription = "In Progress",
StatusUpdateDt = new DateTime(2024, 1, 14, 10, 0, 0),
Flagged = true
}
]
};
}
@@ -0,0 +1,68 @@
using ClosedXML.Excel;
using JdeScoping.ExcelIO.Generators;
using JdeScoping.ExcelIO.Mapping;
using JdeScoping.ExcelIO.Mapping.Maps;
using JdeScoping.ExcelIO.Models.Reporting;
using JdeScoping.ExcelIO.Options;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
namespace JdeScoping.ExcelIO.Tests.Fixtures;
public abstract class WorkbookFixtureBase : IDisposable
{
public XLWorkbook Workbook { get; }
public SearchModel SearchModel { get; }
protected abstract SearchModel CreateSearchModel();
protected WorkbookFixtureBase()
{
SearchModel = CreateSearchModel();
var service = CreateExportService();
var bytes = service.GenerateAsync(SearchModel).GetAwaiter().GetResult();
Workbook = new XLWorkbook(new MemoryStream(bytes));
}
private static ExcelExportService CreateExportService()
{
var logger = Substitute.For<ILogger<ExcelExportService>>();
var options = Microsoft.Extensions.Options.Options.Create(new ExcelExportOptions
{
CriteriaSheetPassword = "TestCriteriaPass",
DataSheetPassword = "TestDataPass"
});
var registry = CreateTestRegistry();
var tableWriter = new FluentTableWriter(registry);
var criteriaGenerator = new CriteriaSheetGenerator(options, tableWriter);
return new ExcelExportService(logger, options, criteriaGenerator, tableWriter, registry);
}
private static ExcelMapRegistry CreateTestRegistry()
{
var registry = new ExcelMapRegistry();
registry.Register(new SearchResultMap());
registry.Register(new MisSearchResultMap());
registry.Register(new MisNonMatchSearchResultMap());
registry.Register(new TimespanFilterMap());
registry.Register(new WorkOrderFilterEntryMap());
registry.Register(new ItemNumberFilterEntryMap());
registry.Register(new ProfitCenterFilterEntryMap());
registry.Register(new WorkCenterFilterEntryMap());
registry.Register(new OperatorFilterEntryMap());
registry.Register(new ComponentLotFilterEntryMap());
registry.Register(new ItemOperationMisFilterEntryMap());
return registry;
}
public void Dispose()
{
Workbook.Dispose();
GC.SuppressFinalize(this);
}
}