Unit Testing Don’ts

Tags: Unit Testing

Unit testing has many benefits in modern software development. Here are a few: increased software quality, facilitating refactoring, driving SOLID design, and faster development cycles. To realize the benefits though, you need to do unit testing right. If you do it wrong, you not only won’t enjoy the benefits; you’ll end up with more headaches and cruft. 

Here are some tips based on my experience transforming several code bases’ unit tests from crusty old broken useless unit test heaps to sparkly new useful unit test suites.

Tip # 1: Don’t write useless unit tests

I’ve seen many unit tests that simply didn’t need to be. They had no reason for existing. They provided no value. Here’s an example.


 	[TestMethod]
        public void GetAllOfferFeatureMapping_Calls_From_OfferFeatureMappingRepo()
        {
            //Arrange
            List featureOffers = new List()
            {
                new FeatureOffer() { OfferID = 1, FeatureType = FeatureType.ContentLibrary, Name = "name", VerticalId = 2 },
                new FeatureOffer() { OfferID = 1, FeatureType = FeatureType.ExperimentCenter, Name = "name2", VerticalId = 3 }
            };
            _offerFeatureMappingRepo.Setup(x => x.GetAll(It.IsAny())).Returns(featureOffers);

            //Act
            var result = _coreService.GetAllOfferFeatureMapping(It.IsAny());

            //Assert
            _offerFeatureMappingRepo.Verify(x => x.GetAll(It.IsAny()), Times.Once());
            Assert.AreEqual(result.Count(), featureOffers.Count);

            bool matches = true;
            for(int i = 0; i < featureOffers.Count; i++)
            {
                if (result.ElementAt(i).OfferID != featureOffers[i].OfferID ||
                    result.ElementAt(i).FeatureType != featureOffers[i].FeatureType ||
                    result.ElementAt(i).Name != featureOffers[i].Name ||
                    result.ElementAt(i).VerticalId != featureOffers[i].VerticalId)
                    matches = false;
            }
            Assert.IsTrue(matches);
        }

//service layer method under test public IEnumerable GetAllOfferFeatureMapping(int featureID) { return _offerFeatureMappingRepository.GetAll(featureID); }

This test is ostensibly testing a service layer method. It mocks a repository and sets up the mock to return some test data. Then it calls the service layer and asserts that the data is returned from the service layer. Great! But what is this testing? One line of service layer code! What for? In reality, this ends up testing that the mocking framework is working. Useless.

Tip # 2: Don’t make tests an afterthought

Test should be written before the code is written (TDD) or at least in parallel with the code. One company I worked for, prior to my arrival, spent an entire month writing unit tests. This was long after the code was in production. The tests produced were of questionable value, but one thing was certainly unquestionable, after this painful month, developers viewed unit tests as pointless busywork.

Tip # 3: Don’t give unit tests to junior developers or interns

Letting experienced developers write the “real” code and pawning off unit tests on interns or inexperienced junior developers might seem like a good idea, but it isn’t. Interns and juniors most likely don’t have business knowledge. They also don’t have intimate knowledge of what the code they are testing is supposed to do. They didn’t write it after all. You’ll end up with the same problems as tip #2, tests of minimal value and developers that view unit testing as having secondary importance. Additionally, you won’t get the benefit your unit tests driving better design.

Tip #4: Don't give your tests crummy names

Unit test names should clearly indicate what the test is testing. There are lots of naming styles used, the one I like is this: NameOfMethodUnderTest_Conditions_ExpectedResult. If you go back to your tests after a few weeks and you can decipher what they are testing within a few seconds, you’ve done something wrong.

Tip #5: Don't fall in love with coverage percentages

Don’t create lots of tests for the sake of having lots of tests and inflating your coverage percentages.  Each test should have value. Having thirty tests for one class in not necessarily better than having ten. If all the edge cases are covered by ten tests, wonderful, stop there and move on.  If a method won't benefit from having unit tests, move on.

Tip #6: Don't test too much in one test

Within reason, avoid writing tests that assert more than one result. This is a rule widely espoused, but one I break fairly often. Sometimes it just makes sense to slip another assert in an existing test rather than copy pasting the entire test and changing the assert. Strive to follow the one assert per test rule, but use your judgement.

Tip #7: Run unit tests automatically

What happens when you don’t run your tests automatically? They become useless, out of date, and eventually ignored. All that diligent work becomes a source of clutter and junk in your code base. Automate the execution of your tests! Preferably after every check-in! If the tests fail, that should break the build, and the culprit should be publicly derided until the build is fixed!

Unit testing can have value. Treat it with the care and attention it deserves and it will provide significant product quality and development benefits.  Treat it like an afterthought and it will be a worthless encumbrance.

No Comments

Add a Comment