Probably only get one test out tonight.
I want to get the ToSystemType finished, which requires the DebuggerDisplay.
I think the simplest is reflection to grab the attribute and check the contents.
I'm mostly using copilot for the reflection stuff. I don't wanna dig through things; and it's usually right.
ANNNNnd I forgot to turn on TCR. Fortunately I saved the copilot test and ... waitied. There was no sound. Really gladd I added that beeping.
It makes SUCH a world of difference. I didn't expect the beeping to become the trigger for "it's safe to continue"; but damn did my brain hook onto it.
OK... that's actually all I need for the ToSystemType... faster than I expected... so I'll start on the next piece... which I think is my 'database types'.
These are the pieces of information required to create a cosmos container. CosmosAccountId
, CosmosDatabaseId
, and CosmosCollectionId
. I used names in other implementations; but I want to be consistent in these names. I could go with a Name
suffix... which some of the actual cosmos classes probably use... ... let's go with Name
. I think that's how I referrer to them more often.
CosmosAccontName
Start with a test class, created in the CosmosItemTests
file
[TestClass]
public sealed class CosmosAccountNameTests
{
}
OH... I ran into an issue when I was trying to demo this with at work. It DOES NOT currently play nice with root level code files. My untested script fucks up the pathing for the root.
Gonna go fix that first
Fixing the script!
When I save the prod code I get dotnet test D:\src\github\fyzxs\MtgDiscoveryTdd\src\Lib.Cosmos.Tests\Lib.Cosmos.Tests.csproj --no-restore --configuration DEBUG --filter "FullyQualifiedName~Lib.Cosmos.Tests..CosmosItemTests" -v n
which is clearly... oopsie'd. But correct outside of the extra dot.
I'll just mod to check the path manipulation before adding the dot.
OK; non-test project on the root works
more tweaks
and now works on both.
And test saves work.
OK...
back to code
Created this test
[TestClass]
public sealed class CosmosAccountNameTests
{
[TestMethod, TestCategory("unit")]
public void CosmosAccountName_ShouldExist()
{
//arrange
//act
ToSystemType<string> _ = new CosmosAccountName();
//assert
}
}
adding this gets the test to pass
public class CosmosAccountName : ToSystemType<string>
{
public override string AsSystemType() => throw new NotImplementedException();
}
Works? no. Passes; yes.
I was about to write another test... I mean I DID write it... but I need to move the class out first so that it can be revcerted.
Fortunately I created some TCR Recipies for refactors I have to adjust.
Like this one
# Move Class out of Test Project
Problem: Extracting non-test class to the non-test project results in TCR; R-ing.
Solution:
Make the namespace to block-scoped
Put the class into the namespace it'll end up in while in the test file.
Move the class into it's own file (do not fix namesspaces). The build will fail, that's ok
Save the test class
Now move the class to the non-test project
undoe the block-scope in both files
That didn't go perfectly. The test class was in the wrong location so TCR couldn't find it to run. I'm being nice and it doesn't revert in that case.
Huh... I added the nexst test and it keeps claiming the it won't build. .. Oh... because I have analyzers on max... and warnings as errors. AKA - Fix code.
I had
[TestMethod, TestCategory("unit")]
public void AsSystemType_ShouldReturnExpected()
{
//arrange
CosmosAccountName subject = new ();
//act
string actual = subject.AsSystemType();
//assert
_ = actual.Should().Be("potato");
}
private sealed class TestCosmosAccountName : CosmosAccountName { }
and the prep class TestCosmosAccountName isn't used, so it wanted to nuke it.
then I update the class to make the test pass
public class CosmosAccountName : ToSystemType<string>
{
public override string AsSystemType() => "potato";
}
I'm still getting a red test. Just... not the same red as without TCR. Better? Worse? Different.
Maybe I'm letting too much sit in the test class first... we'll see as this grows.
OK; I got it to this
[TestMethod, TestCategory("unit")]
public void AsSystemType_ShouldReturnExpected()
{
//arrange
CosmosAccountName subject = new TestCosmosAccountName("potato");
//act
string actual = subject.AsSystemType();
//assert
_ = actual.Should().Be("potato");
}
[TestMethod, TestCategory("unit")]
public void AsSystemType_ShouldReturnProvided()
{
//arrange
CosmosAccountName subject = new TestCosmosAccountName("new_value");
//act
string actual = subject.AsSystemType();
//assert
_ = actual.Should().Be("new_value");
}
private sealed class TestCosmosAccountName : CosmosAccountName
{
private readonly string _origin;
public TestCosmosAccountName(string origin) => _origin = origin;
public override string AsSystemType() => _origin;
}
Which is an interesting commit since it's all test changes.
Adding a check for abstract
[TestMethod, TestCategory("unit")]
public void CosmosAccountName_ShouldBeAbstract()
{
//arrange
Type subject = typeof(CosmosAccountName);
//act
bool actual = subject.IsAbstract;
//assert
_ = actual.Should().BeTrue();
}
The test fails. It's a red test. Now I make it green.
I know I saw; and have felt; that TCR kinda negates the red step... but I'm not really seeing it. Without TCR; we write the test. See it fail. Then edit the code.
With TCR we write the test. See it fail. Then edit the code. NOW - we can't set a default value for the method to return. With C#; it's going to throw an exception. But except that first red test; which is the easieset to visually verify... we still get our red.
Especially when you do simplest; in FizzBuzz terms; return "1";
is easy to accept. From there; we get red; always (evidence pending).
I tried to just add abstract to CosmosAccountNAame; and it reverts... since I have a test trying to instantiate it. Guess I forgot one of the tests.
Now... how do I get a test in place to get rid of the override method
public abstract class CosmosAccountName : ToSystemType<string>
{
public override string AsSystemType() => "potato";
}
I'm just gonna delete it.
public abstract class CosmosAccountName : ToSystemType<string>;
I committed it as ! R
Over all it's a bit of a risky move. It's a refactor as it should be managed by test coverage.
Again - The ACN commits are best effort. Let's be honest withour changes and choose the things that'd help us find issues in the future. Removing a method from a class... risky. I'm calling it a refactor since the function may not have been moved out of everywhere. It's public... so... Small code base, so we can have A LOT more confidence; but ripping something like that out normally - risky.
Next one - CosmosDatabaseName
OK... so... for this one... it's identical to the last one. I'd normally copy/paste the tests, change the class name, call it good.
TESTS ARE INDICATORS OF DESIGN ISSUES
So... what's the design issue when the two classes are
public abstract class CosmosAccountName : ToSystemType<string>;
public abstract class CosmosDatabaseName : ToSystemType<string>;
Maybe there is a design issue here - been using these classes for years and haven't sniffed it out yet.
The smell is duplicate tests. Which imply a missing abstraction. Normally that abstract is in the prod code. But... this case... it's in the tests. We have tests that are identical except for the type.
These play a litle sneaky since it has it's own generic; I can't rip out a simple test helper.
But... if we look at the test class... we really only need a couple of the tests
[TestClass]
public sealed class CosmosAccountNameTests
{
[TestMethod, TestCategory("unit")]
public void CosmosAccountName_ShouldBeAbstract()
{
//arrange
Type subject = typeof(CosmosAccountName);
//act
bool actual = subject.IsAbstract;
//assert
_ = actual.Should().BeTrue();
}
}
Just one of the tests we had... but add one to ensure inheritance... which the original 'Exists' method did
now it'
public sealed class CosmosAccountNameTests
{
[TestMethod, TestCategory("unit")]
public void CosmosAccountName_ShouldBeAbstract()
{
//arrange
Type subject = typeof(CosmosAccountName);
//act
bool actual = subject.IsAbstract;
//assert
_ = actual.Should().BeTrue();
}
[TestMethod, TestCategory("unit")]
public void CosmosAccountName_ShouldDeriveFromToSystemType()
{
//arrange
Type subject = typeof(CosmosAccountName);
//act
bool actual = subject.IsSubclassOf(typeof(ToSystemType<string>));
//assert
_ = actual.Should().BeTrue();
}
}
Which lets me make a base test class like
[TestClass]
public abstract class BaseToSystemTypeTests<TType, TSystemType>
{
[TestMethod, TestCategory("unit")]
public void Class_ShouldBeAbstract()
{
//arrange
Type subject = typeof(TType);
//act
bool actual = subject.IsAbstract;
//assert
_ = actual.Should().BeTrue();
}
[TestMethod, TestCategory("unit")]
public void ClassShouldDeriveFromToSystemType()
{
//arrange
Type subject = typeof(TType);
//act
bool actual = subject.IsSubclassOf(typeof(TSystemType));
//assert
_ = actual.Should().BeTrue();
}
}
Which works. While it's a LITTLE bit of a cheat... I'd copy paste entire test classes. This helps reduce that churn.
Gonna create my "test convenience" project called TestConvenience.Universal and move the base class to Tests folder
Which... makes really funny looking test classes
[TestClass]
public sealed class CosmosAccountNameTests : BaseToSystemTypeTests<CosmosAccountName, string>;
And this does kinda force a full implementation... ya know... having a couple tests written ahead of time that need to be solved from the get go.
It's a ... hack. But I did it before without TCR. For ME... TCR drives more test abstractions so I don't have to go through the TCR-ness for identical things.
...
Not sure how I feel about that. I /like/ it on the surface... but it's kinda questionable. I don't like my tests getting smart like that.
The symetry though... this I like
public abstract class CosmosAccountName : ToSystemType<string>;
[TestClass]
public sealed class CosmosAccountNameTests : BaseToSystemTypeTests<CosmosAccountName, string>;
A couple more classes popped into existance while using TCR. It's not hurting so much. I expected a bit more pain. I expected pain because I often slack on doing strict TDD. This keeps you honest. I like my tools to support me in doing the things I know help produce good code.
OH - if you're curious about the commits; I finally uploaded them to here