Anti-Patterns Test Driven Development
- Transfer
I hope that as a competent developer, you have an idea about unit testing and make yourself a few mental notes on what to avoid when writing tests. Meet:
A unit test that successfully executes all cases and looks to work correctly, but upon closer examination, it turns out that it does not actually test what it should.
A test that requires hard work on initialization before starting the actual testing. Sometimes it happens to see how hundreds of lines are called for a single test, while creating many objects. Because of this “noise”, it is difficult to verify what is being tested.
Unit test, which, although it tests the application correctly, spreads over thousands of lines of code and contains too many cases. This may be a sign that the system under test is the antipattern of the Almighty Object (God Object).
Mocking can be very convenient and correct. But it happens that developers lose their sense of proportion and use it even for those parts of the system that, in principle, should participate in testing. In this case, the unit test contains so many mocks, stubs, and fakes that part of the system remains untested.
A unit test that breaks encapsulation in an attempt to achieve 100% code coverage and knows too much about the system under test. When refactoring a system, such a test breaks too often and needs to be fixed.
The case when one unit-test creates data that is stored somewhere, and another test then reuses it. If, for some reason, the “data generator" is called later or is skipped, the test using its data will fail.
A test that depends on something specific to a given environment. As a result, the test passes successfully with a particular developer, but is not performed with others.
Unit-test, which checks the entire result of the work, while in reality only its small part is important. As a result, you often have to update the test to reflect changes in minor things. Typical for testing web applications.
A test that at first glance does not do any testing due to the lack of assertions, but actually pulls the strings of the system and relies on throwing some kind of exception in case of problems. The test environment is expected to catch this error and display the test as failed.
A unit test that tests many minor (and usually simple) trivia, but does not test the basic behavior.
Unit-test, which clogs the console with a lot of diagnostic messages, logs and other information, even if the test passes successfully. Sometimes it is the result of unnecessary code that was not deleted after debugging the test.
A test that catches exceptions and “swallows” them, either replacing it with a less informative message, or simply displaying an error on the console, allowing the test to complete successfully.
A test that depends on the fact that virtually unordered data always appears in the same order.
A close relative of the Local Hero. This is a unit test that requires some data to be filled before starting. If this data is not available, the test crashes, leaving little information about the cause of the problem and forcing the developer to dig into the pile of code in order to determine what data and where it should come from.
Unit test in which all cases are poorly named (for example, test1, test2, test3). As a result, the purpose of the test case is unclear, and the only way to understand what’s broken is to go into the test code and pray that it is clear.
A case that does not apply to the unit test in which it is located. It actually tests a completely different object, most often the object used by the main test object. Also known as Far Cousin.
Unit test, which relies on the features of a particular operating system. A good example would be a test that expects a line feed that is accepted on Windows and breaks when executed under Linux.
A test that was written in order to pass successfully, and not in order to fail first (fail first principle). A side effect is insufficiently deep testing and successful passing where the correct test should fall.
Instead of writing a new case method, a new assert is simply added to the existing case.
A combination of several anti-patterns, especially the Hare and the Giant. Such a unit test consists of a single method that tests the entire functionality of the object. A typical indicator of a problem is the name of the test method by the name of the unit test and a large number of initialization lines and assertes.
A test that, due to shared resources, can see the data from other tests and may crash even if the system under test is fully valid. A typical example is the use of static fields to store collections. If they are not cleaned properly, then unexpected side effects in other tests are possible. Also known as the Uninvited Guests anti-pattern.
Unit test that runs extremely slowly. When a developer launches it, he has enough time to go to the toilet or smoke. Or, what could be worse, he won’t wait for testing to complete before he commits in the evening and goes home.
From the translator: admiration for clever thoughts - to the author, kicks for the translation - to me. :)
Liar (The Liar)
A unit test that successfully executes all cases and looks to work correctly, but upon closer examination, it turns out that it does not actually test what it should.
Excessive Setup
A test that requires hard work on initialization before starting the actual testing. Sometimes it happens to see how hundreds of lines are called for a single test, while creating many objects. Because of this “noise”, it is difficult to verify what is being tested.
Giant
Unit test, which, although it tests the application correctly, spreads over thousands of lines of code and contains too many cases. This may be a sign that the system under test is the antipattern of the Almighty Object (God Object).
Fake (The Mockery)
Mocking can be very convenient and correct. But it happens that developers lose their sense of proportion and use it even for those parts of the system that, in principle, should participate in testing. In this case, the unit test contains so many mocks, stubs, and fakes that part of the system remains untested.
The Inspector
A unit test that breaks encapsulation in an attempt to achieve 100% code coverage and knows too much about the system under test. When refactoring a system, such a test breaks too often and needs to be fixed.
Generous Leftovers
The case when one unit-test creates data that is stored somewhere, and another test then reuses it. If, for some reason, the “data generator" is called later or is skipped, the test using its data will fail.
The Local Hero
A test that depends on something specific to a given environment. As a result, the test passes successfully with a particular developer, but is not performed with others.
Crakhobor (The Nitpicker)
Unit-test, which checks the entire result of the work, while in reality only its small part is important. As a result, you often have to update the test to reflect changes in minor things. Typical for testing web applications.
The Secret Catcher
A test that at first glance does not do any testing due to the lack of assertions, but actually pulls the strings of the system and relies on throwing some kind of exception in case of problems. The test environment is expected to catch this error and display the test as failed.
Dodger
A unit test that tests many minor (and usually simple) trivia, but does not test the basic behavior.
Screamer (The Loudmouth)
Unit-test, which clogs the console with a lot of diagnostic messages, logs and other information, even if the test passes successfully. Sometimes it is the result of unnecessary code that was not deleted after debugging the test.
The Greedy Catcher
A test that catches exceptions and “swallows” them, either replacing it with a less informative message, or simply displaying an error on the console, allowing the test to complete successfully.
The Sequencer
A test that depends on the fact that virtually unordered data always appears in the same order.
Hidden Dependency
A close relative of the Local Hero. This is a unit test that requires some data to be filled before starting. If this data is not available, the test crashes, leaving little information about the cause of the problem and forcing the developer to dig into the pile of code in order to determine what data and where it should come from.
Counter (The Enumerator)
Unit test in which all cases are poorly named (for example, test1, test2, test3). As a result, the purpose of the test case is unclear, and the only way to understand what’s broken is to go into the test code and pray that it is clear.
The Stranger
A case that does not apply to the unit test in which it is located. It actually tests a completely different object, most often the object used by the main test object. Also known as Far Cousin.
Adherent of the OS (The Operating System Evangelist)
Unit test, which relies on the features of a particular operating system. A good example would be a test that expects a line feed that is accepted on Windows and breaks when executed under Linux.
Success Against All Odds
A test that was written in order to pass successfully, and not in order to fail first (fail first principle). A side effect is insufficiently deep testing and successful passing where the correct test should fall.
The Hare (The Free Ride)
Instead of writing a new case method, a new assert is simply added to the existing case.
The Chosen One (The One)
A combination of several anti-patterns, especially the Hare and the Giant. Such a unit test consists of a single method that tests the entire functionality of the object. A typical indicator of a problem is the name of the test method by the name of the unit test and a large number of initialization lines and assertes.
The Peeping Tom
A test that, due to shared resources, can see the data from other tests and may crash even if the system under test is fully valid. A typical example is the use of static fields to store collections. If they are not cleaned properly, then unexpected side effects in other tests are possible. Also known as the Uninvited Guests anti-pattern.
Brake (The Slow Poke)
Unit test that runs extremely slowly. When a developer launches it, he has enough time to go to the toilet or smoke. Or, what could be worse, he won’t wait for testing to complete before he commits in the evening and goes home.
From the translator: admiration for clever thoughts - to the author, kicks for the translation - to me. :)