📜 ⬆️ ⬇️

Unit Test Evolution

A lot of words are said about how to write unit tests, and generally about the benefits of TDD . Then some BDD loomed on the horizon. You have to figure out which of them is better and what's the difference between them. Maybe this is the reason why most developers decided not to bother and still do not use either one or the other?

In short: BDD is a further development of TDD ideas, therefore, it should be used. And I will try to explain the difference between TDD and BDD with a simple example.

Consider 3 revisions of one unit test, which I found in one real project.

Attempt number 1

The first version of this unit test was:

public class ReferenceNumberTest { @Test public void testValidate() { assertFalse( ReferenceNumber.validate("1234567890123") ); assertFalse( ReferenceNumber.validate("1234567") ); assertTrue( ReferenceNumber.validate("12345678") ); } } 

We call this a typical unit test. He tests the code, but only. He has no more advantages. It is after such a code that skeptics conclude that unit tests have no particular benefit.

Attempt number 2

At some point, the developer came and decided to apply some " best practices " from TDD to this code: split the test method into several small ones, so that each of them would test only one thing and give them the appropriate names.

Here is what he did:
 public class ReferenceNumberTest { @Test public void testTooLong() { String len13 = "1234567891111"; assertEquals(len13.length(), 13); assertEquals(ReferenceNumber.validate(len13), false); } @Test public void testTooShort() { String len7 = "1234567"; assertEquals(len7.length(), 7); assertEquals(ReferenceNumber.validate(len7), false); } @Test public void testOk() { String len8 = "12345678"; assertEquals(len8.length(), 8); assertEquals(ReferenceNumber.validate(len8), true); String len12 = "123456789111"; assertEquals(len12.length(), 12); assertEquals(ReferenceNumber.validate(len12), true); } } 

We call it a good unit test. It is much easier to read: it is easy to guess from the name of the variables that 13 characters are too much, 7 are too few, and 8 characters is normal.

Attempt number 3

After some time, another developer comes along and notices that even this good unit test is not readable and does not provide enough information about how the ReferenceNumber class works. It can be understood, but for this you still need to get into the code and think a little.

The developer continues the process of splitting and renaming:

 public class ReferenceNumberTest { @Test public void nullIsNotValidReferenceNumber() { assertFalse(ReferenceNumber.validate(null)); } @Test public void referenceNumberShouldBeShorterThan13() { assertFalse(ReferenceNumber.validate("1234567890123")); } @Test public void referenceNumberShouldBeLongerThan7() { assertFalse(ReferenceNumber.validate("1234567")); } @Test public void referenceNumberShouldContainOnlyNumbers() { assertFalse(ReferenceNumber.validate("1234567ab")); assertFalse(ReferenceNumber.validate("abcdefghi")); assertFalse(ReferenceNumber.validate("---------")); assertFalse(ReferenceNumber.validate(" ")); } @Test public void validReferenceNumberExamples() { assertTrue(ReferenceNumber.validate("12345678")); assertTrue(ReferenceNumber.validate("123456789")); assertTrue(ReferenceNumber.validate("1234567890")); assertTrue(ReferenceNumber.validate("12345678901")); assertTrue(ReferenceNumber.validate("123456789012")); } } 

We call this the BDD style specification . The names of the methods speak almost human language about how the code should work. Mentally inserting spaces in front of capital letters, we get the specification of the code in English. To understand how the class works, we don’t have to get into the code - just read the naming. And if in the course of changing the code an error was introduced into it, and the unit test broke down, we can certainly determine what error was made in the code by the name of the broken test method.

By the way, I was going to show this example of the evolution of unit test at the devclub.eu seminar on BDD in Tallinn. And so, the day before the workshop, I discovered that I forgot to copy the source code of the ReferenceNumber class itself, which we are testing here all the way. What to do? Panic! One day left before the seminar! I had to urgently write it myself again.

And now look at these three test classes and think about which of them helped me to recover the logic of the ReferenceNumber class.

And finally, BDD

It can be said that the third version differs from the previous ones in that it describes the behavior of the class. This is achieved by using words like “should” and “contain”: “my class should behave this way and that”, “my method should do this and that”.

So, the idea of ​​BDD is to use the words “spec” and “should” instead of the words “test” and “assert”. Yes, the difference is only in words, but this, according to the BDD authors, makes the specifications readable, and writing the tests of the specifications to the code is natural for the human brain.

You can verify this by looking at the same example, translated from JUnit into Easyb :
 description "ReferenceNumber" it "should not be null", { ReferenceNumber.validate(null).shouldBe false } it "should be shorter than 13", { ReferenceNumber.validate("1234567890123").shouldBe false } it "should be longer than 7", { ReferenceNumber.validate("1234567").shouldBe false } it "should contain only numbers", { ReferenceNumber.validate("1234567ab").shouldBe false ReferenceNumber.validate("abcdefghi").shouldBe false ReferenceNumber.validate("---------").shouldBe false ReferenceNumber.validate(" ").shouldBe false } it "valid reference number examples", { ReferenceNumber.validate("12345678").shouldBe true ReferenceNumber.validate("123456789").shouldBe true ReferenceNumber.validate("1234567890").shouldBe true ReferenceNumber.validate("12345678901").shouldBe true ReferenceNumber.validate("123456789012").shouldBe true } 

A report on the launch of these specification tests can actually serve as documentation:
5.31 KB

In addition to it and should, there are other important words in BDD, such as given, when and then, as well as before and after, well, in addition, ensure, narrative and “should behave as”. BDD is also suitable not only for unit tests, but also for functional / integration tests, but this is already beyond the scope of this article. Now we are interested in the level of unit tests. The purpose of this article is to show that they can be written in different ways.

It remains to add that there are libraries for writing BDD specifications for other languages: Java ( JDave , JBehave ), Ruby ( RSpec , RBehave , Cucumber ), Groovy ( Easyb ), Scala ( Scala-test ), PHP ( Behat ), CPP ( CppSpec ), .Net ( SpecFlow , Shouldly ), Python ( Lettuce , Cucumber ).
And if, for reasons beyond your control, you cannot transfer from JUnit to something else - nothing, too, just remember the third example. By the way, in this case the Harmcrest library is useful to you.

As Kozma Rods bequeathed: Comrade, BDDi!

Source: https://habr.com/ru/post/107262/

All Articles