Showing posts with label testing. Show all posts
Showing posts with label testing. Show all posts

Saturday, February 2, 2019

No junctions with SQL Server FILESTREAM columns

We're using a SQL Server table has a FILESTREAM column, and we have some tests to verify reading & writing to this column. A little while back I started getting this error when I tried to run the tests that accessed this column:

System.ComponentModel.Win32Exception: The system cannot find the path specified.

I found a lot of information out there about this error: it can be caused by permission issues, by bugs in earlier version of SQL Server, and sometimes registry & setup errors. But none of the remedies they suggested worked: the SQL Server process had full control of all the directories involved, the registry hacks didn't change anything, and we're using 2017 Developer Edition. I thought it might be because I'd renamed by laptop (the test SQL Server instance is local), but after fixing the server name within SQL Server the error kept happening.

Then I remembered: our test database setup script has hard-coded full paths for the database files and I had set up a file junction on my laptop because I'd wanted it live in a different folder but didn't want to break compatibility. I wondered if that could be the culprit. I deleted the existing test database and re-created it in a different place altogether that didn't involve the junction. Sure enough, things started working! (I then re-wrote the setup script to use the default directories.)

I don't know if using junctions with FILESTREAM has a bug, is not supported, or I just didn't have all the permissions set up correctly, but either way the two things did not place nice together.

Thursday, August 16, 2018

Stop describing me!

Anyone who's ever worked in software knows how difficult it is to keep the code and the documentation in agreement. It's all too common to find errors in documentation, and not just typos, but misleading or even just false statements. This is (generally) not the result of malice, laziness, or stupidity, but is rather a natural consequence of trying to keep two unrelated things in sync with each other. Programming and natural languages are just not similar enough to make this a simple or automatic task. It's one reason experienced programmers are prone to say that all code comments are lies.

Additionally, documentation is far too often a crutch used to make up for the fact that the design is not intuitive. As Chip Camden once said, "Documentation helps, but documentation is not the solution - simplicity is." That doesn't mean that software can't be doing complex things - doing complex things is the whole point! What it means is that the interfaces to software, whether they be APIs or GUIs, have to make the complex thing simple.

Documentation then is typically a symptom of poor design and a difficult symptom to manage at that. But how then do we communicate the what and why of a software system?

The answer is self-documenting systems. "Self-documenting system" sounds like the tagline of some new tool somebody's trying to sell, but it's actually a design philosophy. It's an approach that says: make the endpoints, buttons, menu options, links, namespaces, class, method, parameter, and property names so obvious as to their purpose & function that, for the majority of them, documentation would be redundant.

The term "redundant" is used very deliberately here - it's not that documentation is never warranted, it's that we should seek to make it unnecessary, for the reasons mentioned above. There will still need to be a few bits of explanation here and there, but they'll be few, so maintaining them won't be a big burden, and they'll be very targeted, so it'll be very obvious when they need to be updated. (By targeted, I mean they'll be either high-level descriptions of the unit as a whole, or notes on some edge cases.)

Self-documenting systems lead us to the Principle of Minimum Necessary Documentation, which states:

There is an inverse relationship between how well designed something is and how much documentation it needs.

The term "inverse" is also used very deliberately. The inverse of infinity is not zero - it's darn near close to it, but it's not actually nothing. A perfect system needs no documentation, but there are no perfect systems, so there is no system that can have zero documentation, no matter how well designed it is. But for a well-designed system, a README file or some tooltips will probably suffice.

But do not assume from this that a project with no documentation except a README is well-designed, or that you can get away with just throwing together a README and taking no other thought for what's going on. Note the principle says "how much documentation it needs" not how much it has. If users have cause to say "the documentation has been skimped on", then the design is poor and no crutch was provided, which is even worse than a bad design band-aided with documentation. Intuitive, self-documenting design is the critical factor, not the documentation itself.

Now, there are a few caveats & clarifications that should be mentioned:
  • 'Self-documenting' is often used in connection with test-driven development. There is a vein of thought that says the tests should be the mechanism for documenting the code. This is a very good approach to take, but tests cannot serve as the user documentation. If the project is a public one, expecting people to read the tests is not realistic, and even with an internal project, understanding the guts of how a component works is often beyond the scope of the task at hand. Tests can and should be the developer documentation, and complete test coverage is critically important to developing robust software, but a test suite is typically too large and specialized to be the sole documentation.
  • Most systems have a fairly obvious surface. It may not be instantly obvious what a class structure or API or SDK or GUI does, but it's usually pretty easy to see what it has. There are interfaces though that don't have an easily reflectable surface, such as command-line tools. At first blush, it might seem that this is a place where more documentation is a necessary evil. Certainly there are a lot of tools that believe extensive documentation is preferable to abstraction (git, I'm looking at you). But the principle does still apply here. It is still incumbent on the developer to make the behavior intuitive (typically by following conventions), and to make the options easily discoverable. There are a myriad of ways to approach this, and it depends a lot on how flexible the tool needs to be. It requires more work, but that's our job: to make it easy to use.
It's as I've said before: deal with things directly. Don't build hedges around them, because it just takes more work and isn't as effective. Don't worry about documentation unless there truly is no other way to get the point across. Focus instead of making your code as intuitive and straightforward as possible, as this is the greatest programming good.

Tuesday, June 16, 2015

Converting from MSTest to NUnit

Ever since I was converted to the one true gospel of Test-Driven Development in 2008, I have had to convert test projects from MSTest to NUnit and back a few different times. After my last time having to do it, I decided it was time to testify.

The Facts

MSTest and NUnit have different names for the decorations on the test classes & methods:

MSTest Attribute
NUnit Attribute
Purpose
[TestMethod]
[Test]
Identifies of an individual unit test
[TestClass]
[TestFixture]
Identifies of a group of unit tests, all Tests, and Initializations/Clean Ups must appear after this declaration
[ClassInitialize]
[TestFixtureSetUp]
Identifies a method which should be called a single time prior to executing any test in the Test Class/Test Fixture
[ClassCleanup]
[TestFixtureTearDown]
Identifies a method in to be called a single time following the execution of the last test in a TestClass/TestFixture
[TestInitialize]
[SetUp]
Identifies a method to be executed each time before a TestMethod/Test is executed
[TestCleanUp]
[TearDown]
Identifies a method to be executed each time after a TestMethod/Test has executed
[AssemblyInitialize]
 N/A
Identifies a method to be called a single time upon before running any tests in a Test Assembly
[AssemblyCleanUp]
 N/A
Identifies a method to be called a single time upon after running all tests in a Test Assembly

Source: Comparing the MSTest and Nunit Frameworks, by Naysawn Naderi, 1 Feb 2007.
This article contains some information that is no longer accurate, particularly in regards to the NUnit test runner, but this chart is still correct.

There are a few other minor syntactical differences to be aware of:
  1. Methods decorated with [ClassInitialize] in MSTest must have the signature
    public static void MethodName(TestContext context)
    while methods decorated with [TestFixtureSetUp] cannot have any arguments.
  2. MSTest's Assert and NUnit's Assert both have a method IsInstanceOfType(), but the order of parameters is reversed between frameworks.
    MSTest: void IsInstanceOfType(object actual, Type expectedType)
    NUnit:    void IsInstanceOfType(Type expected, object actual)
The Editorial

The debates over whether to use MSTest or NUnit typically revolve around three factors:
  1. Speed
  2. Integration
  3. Ease of use
People are pretty evenly split as to which one is actually faster. Personally I've found that so many factors affect this (the version of Visual Studio being used, the complexity of the tests, the framework being targeted, the version of NUnit) that we have to just call this one a tie.

When it comes to integration, if you're using Resharper, then it's a moot point. Resharper can run MSTest and NUnit tests in the very same test run seamlessly, and with a far superior interface to MSTest. And if you're not using Resharper, well then I just don't know how you do it. I had to go without Resharper for a week at a new job once, and it was like trying to run in waist-deep water. Everything took five times as long, and my confidence in my code was significantly reduced.

MSTest requires at least twice as many keystrokes to write tests than NUnit - everything is more verbose, with no additional benefit (see the difference in method signatures above for a minor example). Plus, NUnit can do this:

[TestCase("ConnectionStringA")]
[TestCase("ConnectionStringB")]
public void CreatePendingPaymentTest(string connectionName)
{
 //setup

 Repository classUnderTest = new Repository(connectionName);
 classUnderTest.CreateRecord();
 
 //assert
}

[TestCase] allows you to run the same test with different settings/inputs.
You can accomplish the same thing with MSTest by writing multiple tests that call the same method with different parameters, but again, more work to get the same result.

So, with speed being equal, integration being a non-issue, and NUnit the easier to use, the verdict is clear: use NUnit. It's available through NuGet when you need to add it into your solution, and you can learn more at NUnit.org. Enjoy!

EDIT (6/18/2015):
The syntactical differences section omitted an item that is now included.

Tuesday, February 3, 2015

Learn the Rules so you can figure out who's breaking them

99.99 percent [of subatomic interactions] are explainable ... Spending your time exploring each particle trail will lead you to conclude that all the particles obey known physics, and there's nothing left to discover ... Most stars are boring; advances comes from studying the weirdies - the quasars, the pulsars, the gravitational lenses - that don't seem to fit into the models that you've grown up with ... Collect raw data and throw away the expected. What remains challenges your theories.
- Clifford Stoll, in The Cuckoo's Egg
The Cuckoo's Egg is an amazing book - it's a memoir, a techno-thriller, and a manifesto all in one. It recounts how, in the mid-1980s, an astronomer-turned-sysadmin at UC Berkley stumbled across a hacker in their system, and upon tracking down the intruder, ended up catching a ring of KGB agents in their first real stabs at cyber-espionage! It's a must-read for anyone who's ever going to write code, and just a darn good read for everyone else.

The quote above (from chapter 3) is a very insightful one because it tells us, in essence, that the really useful details are in the edge cases. Stoll's first hint that something was up was a $0.75 discrepancy in an accounting system. No one suspected foul-play, and in most scenarios, the bean-counters would just write this off and be done with it. But Stoll and his co-workers decided to dig, and found a rabbit hole so deep it literally did go all the way through to the other side of the world.

It's a principle that technology professionals need to keep in mind at all times. Yes, you will encounter that case eventually. The numbers will get that big. That reference will find some way to be null. There will be someone with 25 dependents. It's not enough to make sure it works for the most typical cases. Of course that's where to start coding, and where to focus the majority of the testing. But you can't ignore the weirdo edge cases - finding and understanding them can expose hidden problems, as well as opportunities. A lot of revenue can be lost by systems that are leaky around the edges, and a lot of unrealized revenue can be tapped by looking in the same places. Don't assume there aren't any outliers, because there always are. Find them, and either bring them in line, or exploit the opportunity they present you!

And the thing about outliers is that they can always pop back up. You think you've fixed that bug, only to see some more examples of it weeks after the release. You write off a 'harmless' variation each month, only to realize it added up to a pretty big loss by year's end. Again, always assume there are outliers, and put checks and balances in your systems to find them. Sometimes this mean making sure your test suite is robust enough; other times it means having an independent audit system.

This is especially important these days. I'm not suggesting that most weird events can be explained by a hacker, but you never know. Anyone who reads the news knows you can't be too careful. Don't assume your security is good enough! If something seems suspicious, figure it out! Outliers can just be bugs, but they can also be attacks.

Very few people are actually lazy, but we can all be lulled into a false sense of security. Just because the fire alarm isn't going off doesn't mean there aren't any fire hazards. We don't need to be paranoid, or rule-bound, but we do need to be vigilant and thorough. How one maintains such an attitude is probably the subject for a whole book and in the end is different for everyone, but it's a skill that technology professionals must develop.

Wednesday, September 15, 2010

C# Compiler Bug, or just something obscure and frustrating?

Earlier today, I started getting the strangest error message when trying to run my tests.  The message varied a little depending on what test runner was being used, but the gist of the error message was:

"Could not load file or assembly or one of its dependencies.  Signature missing argument. (Exception from HRESULT: 0x801312E3)"

The Visual Studio solution this started occurring in is pretty simple.  It contains four (4) C# 4.0 class libraries, and two .NET 4.0 test projects.  Both test projects are using NUnit 2.5.7 and Rhino Mocks 3.6. One test project was working just fine, but when I tried to run the second test project's tests, I would get this error.  It occurred no matter if I ran it through ReSharper or through the NUnit GUI.

After three hours of trial and error, I finally found that the error appeared to be with a reference to the project containing the domain objects.  To better illustrate, here is the project hierarchy:

Test Project
    Class Library A
        Domain Project
        Class Library B
            Domain Project
        Class Library C
    Class Library B
        Domain Project
    Class Library C

It's not the most straightforward tree, but not complex by any means, and compiles without error or warning.  Yet for some reason the test runners were very upset that Class Library B was referencing the Domain Project.  It appeared to be at least in some way related to Rhino Mocks - when I removed the lines of code in the Test Project that included calls to the Expect() method, but left the actual project hierarchy the same, the error went away.  (The tests of course were then useless, so this wasn't a viable alternative, but it got me closer to finding the problem.)

The tests themselves are setting up expectations on a method from Class Project B that has a class from Domain Project as its return type.  You may notice, however, that Test Project does not reference Domain Project.  Because this solution is relatively new, so far the tests just verify that the method is called with the right inputs; I haven't yet written the tests to verify output.  In other words, I don't have to deal directly with the class from Domain Project yet, so I haven't referenced that project.

It eventually turned out that this was in fact the problem.  When I added a reference to Domain Project to Test Project, the errors went away and I was able to run my tests again. It seems that Rhino Mocks requires direct references to all the types employed by a method signature, even if the C# compiler doesn't need them all to build the DLL.  It makes it so the error ends up in a bizarre no-man's land - it's not a compile-time problem, but it manifests when the DLL is being loaded, which is before what we typically think of being run-time.

I'm not one who understands compiler design and implementation very well, so it's hard for me to say what the compiler's doing here. From comparing disassembles of the DLL compiled with and without the Domain Project reference, though, it's pretty clear that without the project reference, the compiler doesn't know the return type of delegate passed into Expect(), and can't build the method signatures correctly.  (See the footnotes for more detail.)

Honestly, this feels like something the compiler should at least send up a warning about, or perhaps even fail to build on. It results in a compiled DLL that can't be used; it feels like it allows us to create invalid binaries.  Maybe detecting this kind of problem would be so enormously complex that its better to put the burden on the developer, but you'd think they'd have better documented it in that case.

So the final take-away is: when using generics and/or delegation, make sure all types implicitly referenced by your code are explicitly referenced in the project References.

This is a very remote and unusual case, but I could find absolutely nothing on Google or in any Microsoft documentation that gave any hints, and the error message itself was basically useless.  So I am putting this recap out on the Internet in the hopes that if anyone else ever runs into this, they'll have a little more insight than I did.

Footnotes

This is the C# code written:

[Test]
public void MyTest()
{
  _classFromProjectB
    .Expect(x => x.GetBatch(Arg<int>.Is.Anything, Arg<DateTime>.Is.Anything));

  // invoke the method being tested
}

Without the Domain Project reference, this is what the compiler produces:

[CompilerGenerated]
private static byte CS$<>9__CachedAnonymousMethodDelegate1;

[CompilerGenerated]
private static IClassFromProjectB <MyTest>b__0(void x)
{
  byte CS$1$0000 =
    (byte)x.GetBatch(Arg<int>.Is.Anything, Arg<DateTime>.Is.Anything);
  return (IClassFromProjectB) CS$1$0000;
}

[Test]
public void MyTest()
{
  if (CS$<>9__CachedAnonymousMethodDelegate1 == 0)
  {
    CS$<>9__CachedAnonymousMethodDelegate1 =
      (byte) new int(null, (IntPtr) <MyTest>b__0);
  }
  this._classFromProjectB.Expect<IClassFromProjectB, byte>(
     (Function<IClassFromProjectB, byte>) CS$<>9__CachedAnonymousMethodDelegate1);

  // invoke the method being tested
}

With the project reference, it produces:

[Test]
public void MyTest()
{
  this._classFromProjectB
    .Expect<IClassFromProjectB, List<DomainObject>>(
      delegate (IClassFromProjectB x) 
      {
        return x.GetBatch(Arg<int>.Is.Anything, Arg<DateTime>.Is.Anything);
      });

  // invoke the method being tested
}