Skip to content

Commit 4045f2b

Browse files
Refactoring (#15)
1 parent 9b18a65 commit 4045f2b

8 files changed

Lines changed: 441 additions & 0 deletions

File tree

CognitiveCodeAnalysis.Tests/CognitiveCodeAnalysis.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
<Threshold>85</Threshold>
1111
<ThresholdType>line</ThresholdType>
1212
<ThresholdStat>total</ThresholdStat>
13+
<!-- Sample complexity fixture compiled into the library for manual analysis demos -->
14+
<ExcludeByFile>**/Error.cs</ExcludeByFile>
1315
</PropertyGroup>
1416

1517
<ItemGroup>

CognitiveCodeAnalysis.Tests/src/Application/AnalysisWorkflowTests.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,53 @@ public void CompareBaselineIfRequested_WhenBaselineMissing_ReturnsNull()
9393
Assert.That(comparison, Is.Null);
9494
}
9595

96+
[Test]
97+
public void CompareBaselineIfRequested_WithBaselineFile_ReturnsComparison()
98+
{
99+
var workflow = CreateWorkflow();
100+
var baselineMetrics = new CognitiveMetrics(
101+
methodName: "Run",
102+
className: "App.Service",
103+
filePath: "src/Service.cs",
104+
methodSignature: "void Run()",
105+
methodLineNumber: 5
106+
);
107+
baselineMetrics.totalScore = 1.0;
108+
109+
var snapshot = BaselineSnapshotFactory.FromMetricsCollection(new CognitiveMetricsCollection { baselineMetrics });
110+
var path = Path.Combine(Path.GetTempPath(), "workflow-baseline-" + Guid.NewGuid() + ".json");
111+
112+
try
113+
{
114+
File.WriteAllText(path, BaselineLoader.Serialize(snapshot));
115+
116+
var currentMetrics = new CognitiveMetrics(
117+
methodName: "Run",
118+
className: "App.Service",
119+
filePath: "src/Service.cs",
120+
methodSignature: "void Run()",
121+
methodLineNumber: 5
122+
);
123+
currentMetrics.totalScore = 3.0;
124+
125+
var comparison = workflow.CompareBaselineIfRequested(
126+
baselineFile: path,
127+
metricsCollection: new CognitiveMetricsCollection { currentMetrics }
128+
);
129+
130+
Assert.That(comparison, Is.Not.Null);
131+
Assert.That(comparison!.TryGetMethodComparison(currentMetrics, out MethodMetricsComparison? methodComparison), Is.True);
132+
Assert.That(methodComparison!.TotalScore.Delta, Is.EqualTo(2.0).Within(0.0001));
133+
}
134+
finally
135+
{
136+
if (File.Exists(path))
137+
{
138+
File.Delete(path);
139+
}
140+
}
141+
}
142+
96143
[Test]
97144
public void GenerateReport_DelegatesToCoordinator()
98145
{
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using CognitiveCodeAnalysis.Application;
6+
using CognitiveCodeAnalysis.CognitiveAnalysis;
7+
using CognitiveCodeAnalysis.CognitiveAnalysis.Baseline;
8+
9+
namespace CognitiveCodeAnalysis.Tests.Application;
10+
11+
public class BaselineComparisonServiceTests
12+
{
13+
[TestCase(null)]
14+
[TestCase("")]
15+
[TestCase(" ")]
16+
public void CompareIfRequested_WithoutBaselineFile_ReturnsNull(string? baselineFile)
17+
{
18+
var service = new BaselineComparisonService();
19+
var metrics = new CognitiveMetricsCollection();
20+
21+
var comparison = service.CompareIfRequested(baselineFile, metrics);
22+
23+
Assert.That(comparison, Is.Null);
24+
}
25+
26+
[Test]
27+
public void CompareIfRequested_WithBaselineFile_ReturnsComparison()
28+
{
29+
var baselineMetrics = SampleMetric(totalScore: 1.0);
30+
var snapshot = BaselineSnapshotFactory.FromMetricsCollection(new CognitiveMetricsCollection { baselineMetrics });
31+
var path = Path.Combine(Path.GetTempPath(), "baseline-svc-" + Guid.NewGuid() + ".json");
32+
33+
try
34+
{
35+
File.WriteAllText(path, BaselineLoader.Serialize(snapshot));
36+
37+
var currentMetrics = SampleMetric(totalScore: 2.0);
38+
var service = new BaselineComparisonService();
39+
40+
var comparison = service.CompareIfRequested(path, new CognitiveMetricsCollection { currentMetrics });
41+
42+
Assert.That(comparison, Is.Not.Null);
43+
Assert.That(comparison!.TryGetMethodComparison(currentMetrics, out MethodMetricsComparison? methodComparison), Is.True);
44+
Assert.That(methodComparison!.TotalScore.Delta, Is.EqualTo(1.0).Within(0.0001));
45+
}
46+
finally
47+
{
48+
if (File.Exists(path))
49+
{
50+
File.Delete(path);
51+
}
52+
}
53+
}
54+
55+
private static CognitiveMetrics SampleMetric(double totalScore)
56+
{
57+
var metrics = new CognitiveMetrics(
58+
methodName: "Alpha",
59+
className: "Demo",
60+
filePath: "src/Demo.cs",
61+
methodSignature: "void Alpha()",
62+
methodLineNumber: 10
63+
);
64+
metrics.totalScore = totalScore;
65+
return metrics;
66+
}
67+
}

CognitiveCodeAnalysis.Tests/src/Application/CognitiveConfigurationFactoryTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,14 @@ public void Load_WithOverrides_AppliesOnlySpecifiedFlags()
3333
Assert.That(configuration.ShowCyclomaticComplexity, Is.False);
3434
Assert.That(configuration.ShowCouplingMetrics, Is.False);
3535
}
36+
37+
[Test]
38+
public void Load_WithCyclomaticOverride_EnablesCyclomaticDisplay()
39+
{
40+
var overrides = new AnalysisDisplayOverrides(ShowHalstead: null, ShowCyclomatic: true, ShowCoupling: null);
41+
42+
var configuration = CognitiveConfigurationFactory.Load(configFile: null, overrides);
43+
44+
Assert.That(configuration.ShowCyclomaticComplexity, Is.True);
45+
}
3646
}

CognitiveCodeAnalysis.Tests/src/CodeCoverage/CoberturaReaderTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,62 @@ public void ReadCoverage_MissingFile_Throws()
5858
var reader = new CoberturaReader();
5959
Assert.Throws<FileNotFoundException>(() => reader.ReadCoverage(Path.Combine(Path.GetTempPath(), "missing-" + Guid.NewGuid() + ".xml")).ToList());
6060
}
61+
62+
[Test]
63+
public void AutoDetectCoverageReader_SelectsCoberturaReader_ForCoverageRoot()
64+
{
65+
var xml = """
66+
<?xml version="1.0"?>
67+
<coverage>
68+
<packages>
69+
<package name="App">
70+
<classes>
71+
<class name="Widget" filename="Widget.cs" lines-covered="1" lines-valid="1"
72+
branches-covered="0" branches-valid="0" complexity="1">
73+
<methods>
74+
<method name="Run" line-rate="1" lines-covered="1" lines-valid="1"
75+
branches-covered="0" branches-valid="0" complexity="1">
76+
<lines>
77+
<line number="1" hits="1"/>
78+
</lines>
79+
</method>
80+
</methods>
81+
</class>
82+
</classes>
83+
</package>
84+
</packages>
85+
</coverage>
86+
""";
87+
var path = Path.Combine(Path.GetTempPath(), "cog-autodetect-" + Guid.NewGuid() + ".xml");
88+
try
89+
{
90+
File.WriteAllText(path, xml);
91+
var reader = new AutoDetectCoverageReader();
92+
var list = reader.ReadCoverage(path).ToList();
93+
94+
Assert.That(list, Has.Count.GreaterThanOrEqualTo(1));
95+
Assert.That(list.Any(c => c.FullyQualifiedClassName.Contains("Widget", StringComparison.Ordinal)), Is.True);
96+
}
97+
finally
98+
{
99+
File.Delete(path);
100+
}
101+
}
102+
103+
[Test]
104+
public void AutoDetectCoverageReader_UnknownRoot_Throws()
105+
{
106+
var path = Path.Combine(Path.GetTempPath(), "cog-autodetect-bad-" + Guid.NewGuid() + ".xml");
107+
try
108+
{
109+
File.WriteAllText(path, """<?xml version="1.0"?><unknown/>""");
110+
var reader = new AutoDetectCoverageReader();
111+
var ex = Assert.Throws<InvalidOperationException>(() => reader.ReadCoverage(path).ToList());
112+
Assert.That(ex!.Message, Does.Contain("Unrecognized coverage XML format"));
113+
}
114+
finally
115+
{
116+
File.Delete(path);
117+
}
118+
}
61119
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using CognitiveCodeAnalysis.CognitiveAnalysis;
6+
7+
namespace CognitiveCodeAnalysis.Tests.CognitiveAnalysis;
8+
9+
public class ChurnCalculatorTests
10+
{
11+
[Test]
12+
public void CalculateChurnScore_WithoutCoverage_AssumesZeroCoverage()
13+
{
14+
var metrics = SampleMetrics(totalScore: 10.0);
15+
16+
double churn = ChurnCalculator.CalculateChurnScore(metrics);
17+
18+
Assert.That(churn, Is.EqualTo(10.0).Within(0.0001));
19+
}
20+
21+
[Test]
22+
public void CalculateChurnScore_UsesLineCoverageWhenBranchMissing()
23+
{
24+
var metrics = SampleMetrics(totalScore: 8.0, lineCoveragePercentage: 50.0);
25+
26+
double churn = ChurnCalculator.CalculateChurnScore(metrics);
27+
28+
Assert.That(churn, Is.EqualTo(4.0).Within(0.0001));
29+
}
30+
31+
[Test]
32+
public void CalculateChurnScore_PrefersBranchCoverageOverLineCoverage()
33+
{
34+
var metrics = SampleMetrics(
35+
totalScore: 10.0,
36+
lineCoveragePercentage: 20.0,
37+
branchCoveragePercentage: 80.0
38+
);
39+
40+
double churn = ChurnCalculator.CalculateChurnScore(metrics);
41+
42+
Assert.That(churn, Is.EqualTo(2.0).Within(0.0001));
43+
}
44+
45+
[Test]
46+
public void CalculateChurnScore_FullCoverage_YieldsZeroRisk()
47+
{
48+
var metrics = SampleMetrics(
49+
totalScore: 5.0,
50+
branchCoveragePercentage: 100.0
51+
);
52+
53+
double churn = ChurnCalculator.CalculateChurnScore(metrics);
54+
55+
Assert.That(churn, Is.EqualTo(0.0).Within(0.0001));
56+
}
57+
58+
private static CognitiveMetrics SampleMetrics(
59+
double totalScore,
60+
double? lineCoveragePercentage = null,
61+
double? branchCoveragePercentage = null
62+
)
63+
{
64+
var metrics = new CognitiveMetrics(
65+
methodName: "Risky",
66+
className: "Demo",
67+
filePath: "src/Demo.cs",
68+
methodSignature: "void Risky()",
69+
methodLineNumber: 1,
70+
lineCoveragePercentage: lineCoveragePercentage,
71+
branchCoveragePercentage: branchCoveragePercentage
72+
);
73+
metrics.totalScore = totalScore;
74+
return metrics;
75+
}
76+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/// <copyright company="Florian Krämer">
2+
/// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
/// </copyright>
4+
5+
using System.Text.Json;
6+
7+
using CognitiveCodeAnalysis.CognitiveAnalysis;
8+
using CognitiveCodeAnalysis.CognitiveAnalysis.Baseline;
9+
using CognitiveCodeAnalysis.CognitiveAnalysis.Reports;
10+
using CognitiveCodeAnalysis.Configuration;
11+
using CognitiveCodeAnalysis.CouplingAnalysis;
12+
13+
namespace CognitiveCodeAnalysis.Tests.CognitiveAnalysis.Reports;
14+
15+
public class JsonReportTests
16+
{
17+
[Test]
18+
public void RenderMetrics_WithoutBaseline_WritesBaselineSnapshotJson()
19+
{
20+
var metrics = SampleMetric(totalScore: 3.5);
21+
var collection = new CognitiveMetricsCollection { metrics };
22+
var path = Path.Combine(Path.GetTempPath(), "json-report-" + Guid.NewGuid() + ".json");
23+
24+
try
25+
{
26+
new JsonReport().RenderMetrics(path, collection, new CognitiveConfiguration());
27+
28+
using var doc = JsonDocument.Parse(File.ReadAllText(path));
29+
var root = doc.RootElement;
30+
Assert.That(root.GetProperty("schemaVersion").GetInt32(), Is.EqualTo(CognitiveBaselineSnapshot.CurrentSchemaVersion));
31+
Assert.That(root.GetProperty("methods").GetArrayLength(), Is.EqualTo(1));
32+
Assert.That(root.GetProperty("methods")[0].GetProperty("methodName").GetString(), Is.EqualTo("Alpha"));
33+
Assert.That(root.GetProperty("methods")[0].GetProperty("totalScore").GetDouble(), Is.EqualTo(3.5).Within(0.0001));
34+
}
35+
finally
36+
{
37+
if (File.Exists(path))
38+
{
39+
File.Delete(path);
40+
}
41+
}
42+
}
43+
44+
[Test]
45+
public void RenderMetrics_WithBaseline_WritesMethodsAndCouplingDeltas()
46+
{
47+
var baselineMetrics = SampleMetric(totalScore: 1.0);
48+
49+
var currentMetrics = SampleMetric(totalScore: 2.5);
50+
var current = new CognitiveMetricsCollection { currentMetrics };
51+
current.SetClassCouplingMetrics(
52+
[
53+
new ClassCouplingMetrics { ClassName = "Demo", IncomingCoupling = 4, OutgoingCoupling = 2, Stability = 0.33 },
54+
]);
55+
56+
var baselineCollection = new CognitiveMetricsCollection { baselineMetrics };
57+
baselineCollection.SetClassCouplingMetrics(
58+
[
59+
new ClassCouplingMetrics { ClassName = "Demo", IncomingCoupling = 2, OutgoingCoupling = 2, Stability = 0.5 },
60+
]);
61+
var baselineSnapshot = BaselineSnapshotFactory.FromMetricsCollection(baselineCollection);
62+
var comparison = BaselineComparer.Compare(current, baselineSnapshot);
63+
64+
var path = Path.Combine(Path.GetTempPath(), "json-report-delta-" + Guid.NewGuid() + ".json");
65+
try
66+
{
67+
new JsonReport().RenderMetrics(path, current, new CognitiveConfiguration(), comparison);
68+
69+
using var doc = JsonDocument.Parse(File.ReadAllText(path));
70+
var method = doc.RootElement.GetProperty("methods")[0];
71+
Assert.That(method.GetProperty("deltas").GetProperty("totalScore").GetDouble(), Is.EqualTo(1.5).Within(0.0001));
72+
73+
var coupling = doc.RootElement.GetProperty("classCoupling")[0];
74+
Assert.That(coupling.GetProperty("deltas").GetProperty("incomingCoupling").GetInt32(), Is.EqualTo(2));
75+
}
76+
finally
77+
{
78+
if (File.Exists(path))
79+
{
80+
File.Delete(path);
81+
}
82+
}
83+
}
84+
85+
private static CognitiveMetrics SampleMetric(double totalScore)
86+
{
87+
var metrics = new CognitiveMetrics(
88+
methodName: "Alpha",
89+
className: "Demo",
90+
filePath: "src/Demo.cs",
91+
methodSignature: "void Alpha()",
92+
methodLineNumber: 10
93+
);
94+
metrics.totalScore = totalScore;
95+
return metrics;
96+
}
97+
}

0 commit comments

Comments
 (0)