patterncsharpMinor
Representing and handling Data Sizes
Viewed 0 times
handlingsizesanddatarepresenting
Problem
In a very specific application I have, I needed the ability to easily convert between different data sizes. I.e. when I give an input of 1,048,576KiB, I needed it to say 1GiB, etc.
So, I built a
It's pretty robust, includes operations for subtraction, addition, multiplication and division,
I'd like to think it might be useful for others as well.
First bit is the
```
public struct DataSize
{
public ulong SizeInBytes { get; }
public SizeScale Scale { get; }
public double Size => GetSize(Scale);
public DataSize(ulong sizeInBytes)
{
Scale = SizeScale.Bytes;
SizeInBytes = sizeInBytes;
}
public DataSize(ulong sizeInBytes, SizeScale scale)
{
Scale = scale;
SizeInBytes = sizeInBytes;
}
public DataSize(double size, SizeScale scale)
{
Scale = scale;
if (scale == SizeScale.Bits)
{
SizeInBytes = (uint)(size / 8);
return;
}
if (((int)scale & 0x03) == (int)SizeScale.Bytes)
{
SizeInBytes = (uint)(size Math.Pow(10, 3 (((int)scale & 0xFF00) >> 8)));
return;
}
SizeInBytes = (uint)(size Math.Pow(2, 10 (((int)scale & 0xFF00) >> 8)));
}
public double GetSize(SizeScale scale)
{
if (scale == SizeScale.Bits)
{
return SizeInBytes * 8.0;
}
if (((int)scale & 0x03) == (int)SizeScale.Bytes)
{
return SizeInBytes / Math.Pow(10, 3 * (((int)scale & 0xFF00) >> 8));
}
return SizeInBytes / Math.Pow(2, 10 * (((int)scale & 0xFF00) >> 8));
}
///
/// Returns a that is the highest value which will have a non-zero whole-number component.
///
/// When set to the result will be a B type, when set to the result will be a iB type. If set to the same base unit as the source value will be used.
/// A object.
public DataSize G
So, I built a
struct for it.It's pretty robust, includes operations for subtraction, addition, multiplication and division,
== and !=, IsSame etc.I'd like to think it might be useful for others as well.
First bit is the
struct:```
public struct DataSize
{
public ulong SizeInBytes { get; }
public SizeScale Scale { get; }
public double Size => GetSize(Scale);
public DataSize(ulong sizeInBytes)
{
Scale = SizeScale.Bytes;
SizeInBytes = sizeInBytes;
}
public DataSize(ulong sizeInBytes, SizeScale scale)
{
Scale = scale;
SizeInBytes = sizeInBytes;
}
public DataSize(double size, SizeScale scale)
{
Scale = scale;
if (scale == SizeScale.Bits)
{
SizeInBytes = (uint)(size / 8);
return;
}
if (((int)scale & 0x03) == (int)SizeScale.Bytes)
{
SizeInBytes = (uint)(size Math.Pow(10, 3 (((int)scale & 0xFF00) >> 8)));
return;
}
SizeInBytes = (uint)(size Math.Pow(2, 10 (((int)scale & 0xFF00) >> 8)));
}
public double GetSize(SizeScale scale)
{
if (scale == SizeScale.Bits)
{
return SizeInBytes * 8.0;
}
if (((int)scale & 0x03) == (int)SizeScale.Bytes)
{
return SizeInBytes / Math.Pow(10, 3 * (((int)scale & 0xFF00) >> 8));
}
return SizeInBytes / Math.Pow(2, 10 * (((int)scale & 0xFF00) >> 8));
}
///
/// Returns a that is the highest value which will have a non-zero whole-number component.
///
/// When set to the result will be a B type, when set to the result will be a iB type. If set to the same base unit as the source value will be used.
/// A object.
public DataSize G
Solution
I've only looked at your unit tests, not your actual code, but there are a few things that stand out.
TestCategory
It seems odd that you're marking every single test with a category that is essentially the name of the class being tested. You can sort tests by class name, run tests by class name, so it just feels wrong. I tend to use categories for cross cutting tests that have a similar purpose that I might want to run / exclude from test runs. So, things like "Integration Tests", "Database Tests", "Some large feature Tests", etc.
Duplication / Framework choice
There's quite a lot of duplication in your tests. Some people like to be explicit about what they're doing, so don't like breaking down the tests into common method calls. I don't know how tied to MS test framework you are, however other frameworks like NUnit make it quite easy to maintain explicit testing whilst reducing duplication. For example:
First, notice that the Category attribute can be used at a class level and applies to all tests within the class. Secondly, notice that because I'm supplying the input and expected values to the test, the test itself becomes a one-liner, which is the common method call.
Test Naming
This may be somewhat subjective, but I don't find your test names particularly intuitive. It's not obvious to me from the name what
Is the SizeScale_ actually relevant to the test (are you testing subtraction, or subtraction for specific scales?) I think the bit that's missing for me (and is the mostly subjective bit) is that your name doesn't specify an expectation. So instead of
TestCategory
It seems odd that you're marking every single test with a category that is essentially the name of the class being tested. You can sort tests by class name, run tests by class name, so it just feels wrong. I tend to use categories for cross cutting tests that have a similar purpose that I might want to run / exclude from test runs. So, things like "Integration Tests", "Database Tests", "Some large feature Tests", etc.
Duplication / Framework choice
There's quite a lot of duplication in your tests. Some people like to be explicit about what they're doing, so don't like breaking down the tests into common method calls. I don't know how tied to MS test framework you are, however other frameworks like NUnit make it quite easy to maintain explicit testing whilst reducing duplication. For example:
[TestFixture, Category("Size Scale Tests")]
public class SizeScaleTests
{
[TestCase(SizeScale.None, null)]
[TestCase(SizeScale.Bytes, "B")]
[TestCase(SizeScale.Bits, "b")]
[TestCase(SizeScale.Kilobytes, "KB")]
public void Validate_SizeScale_Abbreviation(SizeScale inputScale, string expectedAbbreviation)
{
Assert.AreEqual(expectedAbbreviation, inputScale.Abbreviation());
}
}First, notice that the Category attribute can be used at a class level and applies to all tests within the class. Secondly, notice that because I'm supplying the input and expected values to the test, the test itself becomes a one-liner, which is the common method call.
Test Naming
This may be somewhat subjective, but I don't find your test names particularly intuitive. It's not obvious to me from the name what
Subtract_2_SizeScale_Bytes_1_SizeScale_Bytes tests. It turns out it's testing that when 1 is subtracted from 2, it results in 1. So your naming convention seems to be something like:__SizeScale___SizeScale_Is the SizeScale_ actually relevant to the test (are you testing subtraction, or subtraction for specific scales?) I think the bit that's missing for me (and is the mostly subjective bit) is that your name doesn't specify an expectation. So instead of
Subtract_1_From_2_ShouldEqual_1 I have to read the test to figure out what the expectation is.Code Snippets
[TestFixture, Category("Size Scale Tests")]
public class SizeScaleTests
{
[TestCase(SizeScale.None, null)]
[TestCase(SizeScale.Bytes, "B")]
[TestCase(SizeScale.Bits, "b")]
[TestCase(SizeScale.Kilobytes, "KB")]
public void Validate_SizeScale_Abbreviation(SizeScale inputScale, string expectedAbbreviation)
{
Assert.AreEqual(expectedAbbreviation, inputScale.Abbreviation());
}
}<ActionToTest>_<InputValue>_SizeScale_<Scale>_<Parameter2>_SizeScale_<Scale>Context
StackExchange Code Review Q#140339, answer score: 3
Revisions (0)
No revisions yet.