Skip to content

Commit 0105cdc

Browse files
authored
Improve test coverage (#737)
* Move generated test base classes to TestsBase folder * Move tests to code generator To get N tests for N quantities and its units
1 parent 5f13303 commit 0105cdc

File tree

105 files changed

+15306
-1415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+15306
-1415
lines changed

CodeGen/Generators/UnitsNetGen/UnitTestBaseClassGenerator.cs

Lines changed: 162 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,76 @@
44

55
namespace CodeGen.Generators.UnitsNetGen
66
{
7+
/// <summary>
8+
/// Generates base class for each quantity test class, with stubs for testing conversion functions and error tolerances that the developer must complete to fix compile errors.
9+
/// </summary>
10+
/// <example>
11+
/// <list type="bullet">
12+
/// <item><description>UnitsNet.Tests\GeneratedCode\AccelerationTestsBase.g.cs</description></item>
13+
/// <item><description>UnitsNet.Tests\GeneratedCode\LengthTestsBase.g.cs</description></item>
14+
/// </list>
15+
/// </example>
716
internal class UnitTestBaseClassGenerator : GeneratorBase
817
{
18+
/// <summary>
19+
/// The quantity to generate test base class for.
20+
/// </summary>
921
private readonly Quantity _quantity;
22+
23+
/// <summary>
24+
/// Base unit for this quantity, such as Meter for quantity Length.
25+
/// </summary>
1026
private readonly Unit _baseUnit;
27+
28+
/// <summary>
29+
/// Example: "LengthUnit"
30+
/// </summary>
1131
private readonly string _unitEnumName;
1232

33+
/// <summary>
34+
/// Example: "m" for Length quantity.
35+
/// </summary>
36+
private readonly string _baseUnitEnglishAbbreviation;
37+
38+
/// <summary>
39+
/// Example: "LengthUnit.Meter".
40+
/// </summary>
41+
private readonly string _baseUnitFullName;
42+
43+
/// <summary>
44+
/// Constructors for decimal-backed quantities require decimal numbers as input, so add the "m" suffix to numbers when constructing those quantities.
45+
/// </summary>
46+
private readonly string _numberSuffix;
47+
1348
public UnitTestBaseClassGenerator(Quantity quantity)
1449
{
1550
_quantity = quantity;
1651
_baseUnit = quantity.Units.FirstOrDefault(u => u.SingularName == _quantity.BaseUnit) ??
1752
throw new ArgumentException($"No unit found with SingularName equal to BaseUnit [{_quantity.BaseUnit}]. This unit must be defined.",
1853
nameof(quantity));
1954
_unitEnumName = $"{quantity.Name}Unit";
55+
_baseUnitEnglishAbbreviation = GetEnglishAbbreviation(_baseUnit);
56+
_baseUnitFullName = $"{_unitEnumName}.{_baseUnit.SingularName}";
57+
_numberSuffix = quantity.BaseType == "decimal" ? "m" : "";
2058
}
2159

60+
private string GetUnitFullName(Unit unit) => $"{_unitEnumName}.{unit.SingularName}";
61+
62+
/// <summary>
63+
/// Gets the first en-US abbreviation for the unit -or- empty string if none is defined.
64+
/// </summary>
65+
private static string GetEnglishAbbreviation(Unit unit) => unit.Localization.First(l => l.Culture == "en-US").Abbreviations.FirstOrDefault() ?? "";
66+
2267
public override string Generate()
2368
{
2469
var baseUnitVariableName = _baseUnit.SingularName.ToLowerInvariant();
2570

2671
Writer.WL(GeneratedFileHeader);
2772
Writer.WL($@"
2873
using System;
74+
using System.Globalization;
2975
using System.Linq;
76+
using System.Threading;
3077
using UnitsNet.Units;
3178
using Xunit;
3279
@@ -57,22 +104,58 @@ public void Ctor_WithUndefinedUnit_ThrowsArgumentException()
57104
{{
58105
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(({_quantity.BaseType})0.0, {_unitEnumName}.Undefined));
59106
}}
107+
108+
[Fact]
109+
public void DefaultCtor_ReturnsQuantityWithZeroValueAndBaseUnit()
110+
{{
111+
var quantity = new {_quantity.Name}();
112+
Assert.Equal(0, quantity.Value);
113+
Assert.Equal({_baseUnitFullName}, quantity.Unit);
114+
}}
115+
60116
");
61117
if (_quantity.BaseType == "double") Writer.WL($@"
62118
[Fact]
63119
public void Ctor_WithInfinityValue_ThrowsArgumentException()
64120
{{
65-
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.PositiveInfinity, {_unitEnumName}.{_baseUnit.SingularName}));
66-
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.NegativeInfinity, {_unitEnumName}.{_baseUnit.SingularName}));
121+
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.PositiveInfinity, {_baseUnitFullName}));
122+
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.NegativeInfinity, {_baseUnitFullName}));
67123
}}
68124
69125
[Fact]
70126
public void Ctor_WithNaNValue_ThrowsArgumentException()
71127
{{
72-
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.NaN, {_unitEnumName}.{_baseUnit.SingularName}));
128+
Assert.Throws<ArgumentException>(() => new {_quantity.Name}(double.NaN, {_baseUnitFullName}));
129+
}}
130+
131+
[Fact]
132+
public void Ctor_NullAsUnitSystem_ThrowsArgumentNullException()
133+
{{
134+
Assert.Throws<ArgumentNullException>(() => new {_quantity.Name}(value: 1.0, unitSystem: null));
73135
}}
74136
"); Writer.WL($@"
75137
138+
[Fact]
139+
public void {_quantity.Name}_QuantityInfo_ReturnsQuantityInfoDescribingQuantity()
140+
{{
141+
var quantity = new {_quantity.Name}(1, {_baseUnitFullName});
142+
143+
QuantityInfo<{_unitEnumName}> quantityInfo = quantity.QuantityInfo;
144+
145+
Assert.Equal({_quantity.Name}.Zero, quantityInfo.Zero);
146+
Assert.Equal(""{_quantity.Name}"", quantityInfo.Name);
147+
Assert.Equal(QuantityType.{_quantity.Name}, quantityInfo.QuantityType);
148+
149+
var units = EnumUtils.GetEnumValues<{_unitEnumName}>().Except(new[] {{{_unitEnumName}.Undefined}}).ToArray();
150+
var unitNames = units.Select(x => x.ToString());
151+
152+
// Obsolete members
153+
#pragma warning disable 618
154+
Assert.Equal(units, quantityInfo.Units);
155+
Assert.Equal(unitNames, quantityInfo.UnitNames);
156+
#pragma warning restore 618
157+
}}
158+
76159
[Fact]
77160
public void {_baseUnit.SingularName}To{_quantity.Name}Units()
78161
{{
@@ -84,10 +167,19 @@ public void Ctor_WithNaNValue_ThrowsArgumentException()
84167
}}
85168
86169
[Fact]
87-
public void FromValueAndUnit()
170+
public void From_ValueAndUnit_ReturnsQuantityWithSameValueAndUnit()
88171
{{");
89-
foreach (var unit in _quantity.Units) Writer.WL($@"
90-
AssertEx.EqualTolerance(1, {_quantity.Name}.From(1, {_unitEnumName}.{unit.SingularName}).{unit.PluralName}, {unit.PluralName}Tolerance);");
172+
int i = 0;
173+
foreach (var unit in _quantity.Units)
174+
{
175+
var quantityVariable = $"quantity{i++:D2}";
176+
Writer.WL($@"
177+
var {quantityVariable} = {_quantity.Name}.From(1, {GetUnitFullName(unit)});
178+
AssertEx.EqualTolerance(1, {quantityVariable}.{unit.PluralName}, {unit.PluralName}Tolerance);
179+
Assert.Equal({GetUnitFullName(unit)}, {quantityVariable}.Unit);
180+
");
181+
182+
}
91183
Writer.WL($@"
92184
}}
93185
");
@@ -111,7 +203,7 @@ public void As()
111203
{{
112204
var {baseUnitVariableName} = {_quantity.Name}.From{_baseUnit.PluralName}(1);");
113205
foreach (var unit in _quantity.Units) Writer.WL($@"
114-
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, {baseUnitVariableName}.As({_unitEnumName}.{unit.SingularName}), {unit.PluralName}Tolerance);");
206+
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, {baseUnitVariableName}.As({GetUnitFullName(unit)}), {unit.PluralName}Tolerance);");
115207
Writer.WL($@"
116208
}}
117209
@@ -125,9 +217,9 @@ public void ToUnit()
125217

126218
Writer.WL("");
127219
Writer.WL($@"
128-
var {asQuantityVariableName} = {baseUnitVariableName}.ToUnit({_unitEnumName}.{unit.SingularName});
220+
var {asQuantityVariableName} = {baseUnitVariableName}.ToUnit({GetUnitFullName(unit)});
129221
AssertEx.EqualTolerance({unit.PluralName}InOne{_baseUnit.SingularName}, (double){asQuantityVariableName}.Value, {unit.PluralName}Tolerance);
130-
Assert.Equal({_unitEnumName}.{unit.SingularName}, {asQuantityVariableName}.Unit);");
222+
Assert.Equal({GetUnitFullName(unit)}, {asQuantityVariableName}.Unit);");
131223
}
132224
Writer.WL($@"
133225
}}
@@ -303,6 +395,67 @@ public void BaseDimensionsShouldNeverBeNull()
303395
{{
304396
Assert.False({_quantity.Name}.BaseDimensions is null);
305397
}}
398+
399+
[Fact]
400+
public void ToString_ReturnsValueAndUnitAbbreviationInCurrentCulture()
401+
{{
402+
var prevCulture = Thread.CurrentThread.CurrentUICulture;
403+
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(""en-US"");
404+
try {{");
405+
foreach (var unit in _quantity.Units)
406+
{
407+
Writer.WL($@"
408+
Assert.Equal(""1 {GetEnglishAbbreviation(unit)}"", new {_quantity.Name}(1, {GetUnitFullName(unit)}).ToString());");
409+
}
410+
Writer.WL($@"
411+
}}
412+
finally
413+
{{
414+
Thread.CurrentThread.CurrentUICulture = prevCulture;
415+
}}
416+
}}
417+
418+
[Fact]
419+
public void ToString_WithSwedishCulture_ReturnsUnitAbbreviationForEnglishCultureSinceThereAreNoMappings()
420+
{{
421+
// Chose this culture, because we don't currently have any abbreviations mapped for that culture and we expect the en-US to be used as fallback.
422+
var swedishCulture = CultureInfo.GetCultureInfo(""sv-SE"");
423+
");
424+
foreach (var unit in _quantity.Units)
425+
{
426+
Writer.WL($@"
427+
Assert.Equal(""1 {GetEnglishAbbreviation(unit)}"", new {_quantity.Name}(1, {GetUnitFullName(unit)}).ToString(swedishCulture));");
428+
}
429+
Writer.WL($@"
430+
}}
431+
432+
[Fact]
433+
public void ToString_SFormat_FormatsNumberWithGivenDigitsAfterRadixForCurrentCulture()
434+
{{
435+
var oldCulture = CultureInfo.CurrentUICulture;
436+
try
437+
{{
438+
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
439+
Assert.Equal(""0.1 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s1""));
440+
Assert.Equal(""0.12 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s2""));
441+
Assert.Equal(""0.123 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s3""));
442+
Assert.Equal(""0.1235 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s4""));
443+
}}
444+
finally
445+
{{
446+
CultureInfo.CurrentUICulture = oldCulture;
447+
}}
448+
}}
449+
450+
[Fact]
451+
public void ToString_SFormatAndCulture_FormatsNumberWithGivenDigitsAfterRadixForGivenCulture()
452+
{{
453+
var culture = CultureInfo.InvariantCulture;
454+
Assert.Equal(""0.1 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s1"", culture));
455+
Assert.Equal(""0.12 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s2"", culture));
456+
Assert.Equal(""0.123 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s3"", culture));
457+
Assert.Equal(""0.1235 {_baseUnitEnglishAbbreviation}"", new {_quantity.Name}(0.123456{_numberSuffix}, {_baseUnitFullName}).ToString(""s4"", culture));
458+
}}
306459
}}
307460
}}");
308461
return Writer.ToString();

CodeGen/Generators/UnitsNetGenerator.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,22 @@ public static void Generate(string rootDir, Quantity[] quantities)
4343
Directory.CreateDirectory($"{outputDir}/Quantities");
4444
Directory.CreateDirectory($"{outputDir}/Units");
4545
Directory.CreateDirectory($"{testProjectDir}/GeneratedCode");
46+
Directory.CreateDirectory($"{testProjectDir}/GeneratedCode/TestsBase");
47+
Directory.CreateDirectory($"{testProjectDir}/GeneratedCode/QuantityTests");
4648

4749
foreach (var quantity in quantities)
4850
{
4951
var sb = new StringBuilder($"{quantity.Name}:".PadRight(AlignPad));
5052
GenerateQuantity(sb, quantity, $"{outputDir}/Quantities/{quantity.Name}.g.cs");
5153
GenerateUnitType(sb, quantity, $"{outputDir}/Units/{quantity.Name}Unit.g.cs");
52-
GenerateUnitTestBaseClass(sb, quantity, $"{testProjectDir}/GeneratedCode/{quantity.Name}TestsBase.g.cs");
53-
GenerateUnitTestClassIfNotExists(sb, quantity, $"{testProjectDir}/CustomCode/{quantity.Name}Tests.cs");
54+
55+
// Example: CustomCode/Quantities/LengthTests inherits GeneratedCode/TestsBase/LengthTestsBase
56+
// This way when new units are added to the quantity JSON definition, we auto-generate the new
57+
// conversion function tests that needs to be manually implemented by the developer to fix the compile error
58+
// so it cannot be forgotten.
59+
GenerateQuantityTestBaseClass(sb, quantity, $"{testProjectDir}/GeneratedCode/TestsBase/{quantity.Name}TestsBase.g.cs");
60+
GenerateQuantityTestClassIfNotExists(sb, quantity, $"{testProjectDir}/CustomCode/{quantity.Name}Tests.cs");
61+
5462
Log.Information(sb.ToString());
5563
}
5664

@@ -68,7 +76,7 @@ public static void Generate(string rootDir, Quantity[] quantities)
6876
Log.Information("");
6977
}
7078

71-
private static void GenerateUnitTestClassIfNotExists(StringBuilder sb, Quantity quantity, string filePath)
79+
private static void GenerateQuantityTestClassIfNotExists(StringBuilder sb, Quantity quantity, string filePath)
7280
{
7381
if (File.Exists(filePath))
7482
{
@@ -95,7 +103,7 @@ private static void GenerateUnitType(StringBuilder sb, Quantity quantity, string
95103
sb.Append("unit(OK) ");
96104
}
97105

98-
private static void GenerateUnitTestBaseClass(StringBuilder sb, Quantity quantity, string filePath)
106+
private static void GenerateQuantityTestBaseClass(StringBuilder sb, Quantity quantity, string filePath)
99107
{
100108
var content = new UnitTestBaseClassGenerator(quantity).Generate();
101109
File.WriteAllText(filePath, content, Encoding.UTF8);

0 commit comments

Comments
 (0)