📜 ⬆️ ⬇️

Unit tests, BDD and the power of fluid statements (fluent assertions) in 1C

A bit of history


Thanks to the cool uncle Kent Beck (Kent Beck) a wonderful methodology for test-driven development was born. Despite the unusual approach that turns the usual process of writing code upside down (a test for functionality is created before implementation), we can already say that development through testing has become the de facto standard. Practically in any vacancies there is a requirement for knowledge and experience of using the TDD methodology and the corresponding tools. Why, it would seem, the methodology breaking the habitual paradigm of thinking has taken root and standardized? Because “Life is too short for manual testing”, and sometimes it’s just impossible to write auto-tests on existing code, because the code written in the usual paradigm is completely dough-unfit.

It should be noted that during its existence, the methodology has managed to acquire a fork in the form of BDD. Dan North in his article ( Introducing BDD ) pointed out the difficulties of introducing TDD among developers and offered a practice called “behavioral-driven development” to solve these problems. The main feature of BDD can be called a mix of TDD and DDD, which was initially expressed in the correct naming of test methods (the names of test methods should be sentences). The apogee of BDD, for the time being, can be considered the birth of the Gherkin language and the tools that use it (Cucumber, RSpec, etc.).

What do I lead and what have 1C?


In the world of 1C TDD is just beginning to gain popularity. I have not yet seen 1C developer jobs with the requirement of knowledge of TDD. We have to admit that a significant obstacle is the lack of tools for writing tests to the code in the 1C platform core.
So what do we currently have to develop through testing in the world of 1C?

And now the question that should arise in any self-respecting member of society: "How can I personally help ... (in my case - the world of 1C development to switch to advanced methodologies)?".

Before answering this question, I want to touch on the topic of well-written statements in tests. Statements denote the expected behavior of our code. One look at the statements should be enough to understand what kind of behavior the test is trying to convey to us. Unfortunately, the classic statements do not allow this to be achieved. Often we have to take a long time to read and decipher the idea of ​​the author of the test.
Fortunately, recently there has been a trend towards the use of fluid interfaces (fluent interface), which has a very positive effect on the visibility and intuitive clarity of the code. The toolkit for testing also did not stay aside. Fluid statements, also called BDD statements, appeared. They make it possible to formulate statements in a more natural, comfortable and expressive manner.
I first encountered a similar approach in NUnit in the constraint -based assertion model ( Constraint-Based Assert Model ).
Much later, I met a bunch of mocha.js + chai.js , which caused my utter delight.
')
So, my answer to the question “How can I personally help the world of 1C development go to advanced methodologies?” - flowing statements ... for starters.

Development of fluid statements for the 1C platform


As a real developer through testing, I started development with a test. The first test method contained only 1 line:

.(5).(5); 

The implementation was surprisingly simple. The variable Expect contains the object External Processing (hereinafter the object-assertions), this object has export methods:

Each method returns the same assertion object.

The next step is the signature of the method that has been extended by the optional Message parameter, which makes the exceptions thrown by the assertions more informative.

Further, I thought about what to do with the statement of No Equal. Should there be such a statement? In classical statements it is, almost every statement has its own antipode (Equal / Equal, Filled / Uncompleted, etc.). But not in flowing statements! Thus was born the test number 2:

 .(5)..(7); 

It looks beautiful, but not realizable in 1C. Another attempt:

 .(5).().(7); 

Still beautiful and seemingly realizable. All you need to do is to flag the negation flag in the context of the assertion object, and then check any subsequent statement along the chain, taking into account this flag. In fact, I needed XOR, in 1C it looks like this:

  =  <> ; 

But the platform refused to compile the object with the He () method. The fact is that a Not - reserved word, a restriction on its use extends including. and in the name of the method. Brainstorming with colleagues did not allow to get around this problem beautifully, so the final version with denial looks like this:

 .(5)._().(7); 

If someone offers the best solution to this problem, I will be very grateful. The option of replacing Russian letters in the Latin alphabet does not offer!

As a result, the next API was born


What (Checked Value, Message = "") - saves, in the context of external processing, the checked value and the additional message for exceptions thrown by assertions.

He_ () - denies any statement following the chain.

This is Truth () - claims that the value being verified is the Truth.

ThisFlash () - claims that the value being tested is False.

Equal to (Expected) - states that the value being tested is equal to the expected value.

Greater (Lower Value) - states that the value being verified is greater than the value passed to the statement.

Greater Or Equal (Lower Or Equal Value) / Minimum (Minimum Value ) - states that the value to be checked is greater than or equal to the value passed to the statement.

Less Or. Equal (Greater. Or Equal Value) / Maximum (Maximum Value ) - states that the value to be checked is less than or equal to the value passed to the statement.

Less (Greater Value) - states that the value being verified is less than the value passed to the statement.

Filled () - states that the value being tested is different from the default value of the same type.

There is () - states that the value being tested is not Null and not Undefined.

This is Undefined () - states that the value being tested is Undefined.

This is Null () - states that the value being tested is Null.

Has Type (Type) - states that the type or name of the type passed to the assertion has the checked value.

Between (Initial Value , End Value ) - states that the value being tested is between the values ​​passed to the assertion.

Contains (Seeker) - asserts that the value being checked contains the passed in assertion. Used for strings and collections.

Has Length (Expected Length) - states that the value being tested has the length passed to the statement. Used for strings and collections.

Examples of using


 .(1 > 0).(); .(13 = 2)._().(); .(5 = 7).(); .(5).(5); .(4).(2); .(7).(7); .(.()).(9); .(90).(100); .(()).(90); .(55).(56); .(1).(); .( ).(); .().(); .(.).Null(); .("").(""); .(7).(1, 10); .(" ").(""); .(" ").(12); 

The example is a bit more complicated:

 .(" ") .() ._().("") .(12) ._().("!!!"); 

Afterword


Development is available on github . As the reader with an inquisitive mind probably noticed, the link leads to more than just a library of statements. But this is the material for the next article.

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


All Articles