I have a love / hate relationship with test driven development and unit testing.\n\nI’ve been both an ardent supporter of these “best practices,” but I’ve also been more than skeptical of their use.\n\nOne of the big problems in software development is when developers—or sometimes managers—who mean well apply “best practices” simply because they are best practices and don’t understand their reason or actual use.\n\nI remember working on one such software project where I was informed that the software we were going to be modifying had a huge number of unit tests––around 3,000.\n\nNormally this is a good sign.\n\nThis probably means the developers on the project implemented other best practices as well, and there is going to be some semblance of structure or meaningful architecture within the code base.\n\nI was excited to hear this news since it meant that my job as the mentor / coach for the development team was going to be easier. Since we already had unit tests in place, all I had to do was get the new team to maintain them and start writing their own.\n\nI opened up my IDE and loaded the project into it.\n\nIt was a big project.\n\nI saw a folder labeled “unit tests.”\n\nGreat. Let’s run them and see what happens.\n\nIt only took a few minutes and—much to my surprise—all the tests ran and everything was green. They all passed.\n\nNow I really became skeptical. Three thousand unit tests, and they all passed?\n\nWhat is going on here?\n\nMost of the time when I am first pulled onto a development team to help coach them, there are a bunch of failing tests if there are any unit tests at all.\n\nI decided to spot check one test at random.\n\nAt first glance, it seemed reasonable enough.\n\nIt wasn’t the best, most explanative test I’d ever seen, but I could make out what it was doing.
\n\nBut then I noticed something…\n\nThere was no assert.\n\nNothing was actually being tested.\n\nThe test had steps and those steps were running, but at the end of the test where it is supposed to check something, there was no check.\n\nThe “test” wasn’t testing anything.\n\nI opened up another test.\n\nWorse.\n\nThe assert statement, which was testing something at some point, was commented out.\n\nWow, that’s a great way to make a test pass; just comment out the code that’s making it fail.\n\nI checked test after test.\n\nNone of them were testing anything.\n\nThree thousand tests and they were all worthless.\n\nThere is a huge difference between writing unit tests and understanding unit testing and test-driven development.\n\n
What Is Unit Testing?
\n\nThe basic idea of unit testing is to write tests which exercise the smallest “unit” of code possible.\n\nUnit tests are typically written in the same programming language as the source code of the application itself and written to utilize that code directly.\n\nThink of unit tests as code that tests other code.\n\nWhen I use the word “test” here, I’m using it fairly liberally because unit tests aren’t really tests. They don’t test anything.\n\nWhat I mean by this is that when you run a unit test, you don’t typically find out that some code doesn’t work.\n\nIt’s when you write a unit test that you find that information out.\n\nYes, the code could change later and that test could fail, so in that sense, a unit test is a regression test. In general, however, a unit test is not like a regular test where you have some steps you are going to execute and you see whether the software behaves correctly or not.\n\nAs a developer writing a unit test, you discover whether the code does what it is supposed to or not while you are writing the unit test because you are going to be continually modifying the code until the unit test passes.\n\nWhy would you write a unit test and not make sure that unit test passes?\n\nWhen you think about it this way, unit testing is more about specifying absolute requirements for specific units of code at a very low level.\n\nYou can think of a unit test as an absolute specification.\n\nThe unit test specifies that under these conditions with this specific set of input, this is the output that I should get from this unit of code.\n\nTrue unit testing tests the smallest cohesive unit of code possible, which in most programming languages—at least object oriented ones—is a class.\n\n
What Is Sometimes Called Unit Testing?
\n\nOftentimes, unit testing is confused with integration testing.\n\nSome “unit tests” test more than one class or test larger units of code.\n\nPlenty of developers will argue that these are still unit tests since they are whitebox tests written in code at a low level.\n\nYou shouldn’t argue with these people.\n\nJust know in your mind that these are really integration tests and that true unit tests test the smallest unit of code possible in isolation.\n\nAnother thing that is often called unit testing—but isn’t really anything at all—is writing unit tests that have no assert. In other words, unit tests that don’t actually test anything.\n\nAny test, unit test or not, should have some kind of check—we call it an assertion—at the end that determines whether it passes or fails.\n\nA test that always passes is useless.\n\nA test that always fails is useless.\n\n
The Value of Unit Testing
\n\n
What Is Test-Driven Development (TDD)?
\n\n
What Is the Purpose of TDD?
\n\nJust like unit testing itself can be a best practice that is misapplied, TDD can be as well.\n\nIt’s very easy to call what you are doing TDD and to even follow the practice and not understand why you are doing it or the value—if any—it is providing.\n\nThe biggest value of TDD is that tests happen to make excellent specifications.\n\nTDD is essentially the practice of writing unambiguous specifications, which can be automatically checked, before writing code.\n\nWhy are tests such great specifications?\n\nBecause they don’t lie.\n\nThey don’t tell you your code should work one way and then tell you after you spend two weeks pounding Mountain Dew and get everything working it should actually work another way and “it’s all wrong; that’s not what I said at all.”\n\nTests, if properly written, either pass or fail.\n\nTests specify in no uncertain terms exactly what should happen under a certain set of circumstances.\n\nSo, in that respect, we could say the purpose of TDD is to make sure we fully understand what we are implementing before we implement and that we “got it right.”\n\nIf you sit down to do TDD and you can’t figure out what the test should test, it means you need to go ask more questions.\n\nThe other value of TDD is in keeping the code lean and succinct.\n\nCode is costly to maintain.\n\nI often joke that the best programmer is the one who writes the least code or even finds way to delete code because that programmer has found a surefire way to reduce errors and to decrease the maintenance cost of the application.\n\nBy utilizing TDD, you can be absolutely sure that you do not write any code that is not necessary since you will only ever write code to make tests pass.\n\nThere is a principle in software development called YAGNI, or you ain’t going to need it.\n\nTDD prevents YAGNI.\n\n
A Typical TDD Workflow
\n\nIt can be a little difficult to understand TDD from a purely academic perspective, so let’s explore what a sample TDD session might look like.\n\nYou sit down at your desk and quickly sketch out what you think will be a high-level design of a feature to allow a user to login to the application and change their password if they forget it.\n\nYou decide that you are going to start off by first implementing the login functionality by creating a class that will handle all the logic for doing the login process.\n\nYou open up your favorite editor and create a unit test which is called “Empty login does not log user in.”\n\nYou write the unit test code that first creates an instance of a Login class (which you haven’t created yet).\n\nThen, you write some code to call a method on the Login class that passes in an empty username and password.\n\nFinally, you write an assertion, or assert, which asserts that the user is indeed not logged in.\n\nYou attempt to run the test, but it doesn’t even compile because you don’t have a Login class.\n\nYou remedy that situation by creating the Login class along with a method on that class for logging in and another for checking the status of a user to see if they are logged in.\n\nYou leave the functionality in this class and methods completely empty.\n\nYou run the test and this time it compiles, but quickly fails.\n\nNow, you go back and implement just enough functionality to make the test pass.\n\nIn this case, it would mean always returning that the user is not logged in.\n\nYou run the test again, and now it passes.\n\nOn to the next test.\n\nThis time you decide to write a test called “User is logged in when user has valid username and password.”\n\nYou write a unit test that creates an instance of the Login class and try to login with a username and password.\n\nIn the unit test, you write an assertion that the Login class should say the user is logged in.\n\nYou run this new test, and of course it fails because your Login class always returns that the user is not logged in.\n\nYou go back to your Login class and implement some code to check for the user being logged in.\n\nIn this case, you’ll have to figure out how to keep this unit test isolated.\n\nFor now, the simplest way to make this work is to hardcode the username and password you used in your test and if it matches, then you’ll return that the user is logged in.\n\nYou make that change, run both tests, and they both pass.\n\nNow you look at the code you created, and see if there is a way you can refactor it to make it more simple.\n\nSo on you go, creating more tests, writing just enough code to make them pass, and then refactoring the code you wrote until there are no more test cases you can think of for the functionality you are trying to implement.\n\n
These Are Just the Basics
\n\n
