Files
Amaury Levé 948a9a52ef Add exp-mock-usage-analysis skill and evaluation (#483)
* Add exp-mock-usage-analysis skill and evaluation

New experimental skill that detects unused, redundant, or unnecessary
mocks in .NET test suites. Covers Moq, NSubstitute, FakeItEasy, and
manual test doubles.

- SKILL.md with 6-step workflow: gather code, classify mocks, detect
  anti-patterns, incorporate runtime data, generate recommendations,
  report findings
- eval.yaml with 10 scenarios: trivial type mocking, unused setups,
  redundant configs, stable utility mocks, well-placed mocks (no false
  positives), NSubstitute support, FakeItEasy support, setup sprawl,
  and 2 non-activation boundary tests
- Fixture files for each scenario with production + test code
- CODEOWNERS entry for @dotnet/dotnet-testing

* Remove no-headroom scenarios, increase NSubstitute timeout

Remove 4 scenarios where baseline already scores 5.0/5 (model handles
these well without skill guidance):
- Detect mocking of DTOs, records, and enums
- Recognize well-placed mocks without inventing false positives
- Decline when asked about non-mock test anti-patterns
- Decline mock framework migration request

Increase NSubstitute scenario timeout from 120s to 180s to avoid
baseline timeouts impacting scoring.

Remaining 6 scenarios all show positive skill impact (3→5, 3→4, etc).

* Document areas the model handles well without the skill

Add a subsection under 'When Not to Use' listing mock-related tasks
where the base model already scores 5.0/5 without skill guidance:
- Mocking trivial types (DTOs, records, enums)
- Recognizing well-placed mocks (no false positives)

* Fix md

* Improve
2026-04-02 10:03:17 +00:00

84 lines
2.6 KiB
C#

using Microsoft.Extensions.Logging;
namespace ReportGen;
public interface IDataSource
{
IReadOnlyList<SalesRecord> GetSalesData(DateTime from, DateTime to);
IReadOnlyList<CustomerRecord> GetCustomers();
IReadOnlyList<ProductRecord> GetProducts();
decimal GetExchangeRate(string fromCurrency, string toCurrency);
}
public interface IReportFormatter
{
string FormatAsHtml(ReportData data);
string FormatAsPdf(ReportData data);
string FormatAsCsv(ReportData data);
}
public interface IReportStorage
{
string Save(string reportName, string content, string format);
string? GetLatest(string reportName);
}
public interface ICacheService
{
T? Get<T>(string key);
void Set<T>(string key, T value, TimeSpan expiry);
void Invalidate(string key);
}
public record SalesRecord(string ProductId, decimal Amount, string Currency, DateTime Date);
public record CustomerRecord(int Id, string Name, string Region);
public record ProductRecord(string Id, string Name, string Category, decimal Price);
public record ReportData(string Title, IReadOnlyList<ReportRow> Rows, decimal Total);
public record ReportRow(string Label, decimal Value);
public class ReportGenerator
{
private readonly IDataSource _dataSource;
private readonly IReportFormatter _formatter;
private readonly IReportStorage _storage;
private readonly ICacheService _cache;
private readonly ILogger<ReportGenerator> _logger;
public ReportGenerator(
IDataSource dataSource,
IReportFormatter formatter,
IReportStorage storage,
ICacheService cache,
ILogger<ReportGenerator> logger)
{
_dataSource = dataSource;
_formatter = formatter;
_storage = storage;
_cache = cache;
_logger = logger;
}
public string GenerateSalesReport(DateTime from, DateTime to, string format)
{
var sales = _dataSource.GetSalesData(from, to);
var rows = sales
.GroupBy(s => s.ProductId)
.Select(g => new ReportRow(g.Key, g.Sum(s => s.Amount)))
.ToList();
var data = new ReportData("Sales Report", rows, rows.Sum(r => r.Value));
var content = format switch
{
"html" => _formatter.FormatAsHtml(data),
"pdf" => _formatter.FormatAsPdf(data),
"csv" => _formatter.FormatAsCsv(data),
_ => throw new ArgumentException($"Unknown format: {format}")
};
var reportId = _storage.Save($"sales-{from:yyyyMMdd}-{to:yyyyMMdd}", content, format);
_logger.LogInformation("Generated sales report {ReportId}", reportId);
return reportId;
}
}