No one denies the usefulness of tests in any complex system. Without tests, you can quickly go into chaos and spend most of your time in the debugger, searching for and catching indirect effects from changes in one or another part of the application. Tests are important, necessary, and so on.
According to science, tests are system documentation. Competently written tests make it clear how the system works, how it behaves, and all this should be read as a ready-made specification of the behavior of the system. Those. ideally, a coherent and understandable text should be obtained. This is an ideal, to which test methods are gradually approaching, starting from unit testing and most clearly manifested in behavioral / acceptance testing, when the tests themselves are already written in the business language (recall the Fitnesse at this point).
When writing tests, you should not skimp on lines of code and classes, it is only important to structure them correctly. I believe that it may be quite normal situation when you have a test class consists of only one test method - do not be ashamed of this, it is much better than classes on 20 screens. HD screens.
')
In general, everything should be directed to the maximum clarity and clarity of tests, so that all interrelations are clearly visible. To be able to restore the logic of the program for only one test. Not only Assert DSL (Domain Specific Language), but also file naming, the
Arrange Act Assert approach will go into the matter of readability. All this is
not new approaches as it turns out, but not widely known yet, judging by what I see in the projects around me. Yes, and I myself came across new topics by chance, studying the
source codes of StructureMap.
In order not to torment, I will immediately tell you what basic steps are proposed to improve the tests:
- Name the test files according to the main method being tested.
- Use DSL to create objects to make methods as concise as possible.
- Try to write tests in the style of "one test method - one assert".
- Structure the insides of the dough.
- Create and use Assert DSL.
I think that for the majority of many of the items listed are not news, and almost all of them are used in real development.
In a broad sense, this fits into the paradigm of the
Arrange Act Assert , which suggests that it is necessary to clearly distinguish the preparation for the test, the action, the test. In this case, it turns out that each test class will describe a specific preparation for the test. In SetUp or in FixtureSetUp, the Act will ideally be specified and the tests will already check the result - Assert.
It is best to show it with an example.
Suppose we have a class Pirate, which is the implementation of the actions and capabilities of the pirate in the game. A pirate can move around the field, pick up and leave gold, fight, swim, kill and die. A lot of things he could and it would be wrong to push all the tests into one file and demarcate the tests by regions. It is much better to make several test classes, for example:
- PirateMovementTests
- PirateAndGoldTests
- PirateDefaultSettingsTests
- PirateActionTests
Hmm, the pirate is not so branched and more methods to devote them to a separate class. But then we have a playing field, in which there are more responsible methods. Suppose a class Field which is responsible for creating the playing field and general movement control. His tests will be:
- WhenCreateField
- WhenCreatePlayableField
- WhenGenerateShips
Then the tests inside the class name for example:
- MaxSizeShouldBeDefined
- ShouldGenerateSeaOnBorder
- ShouldGenerateShips
Then when viewing the tests and results, you can read them as
When [game] Create Field [ it] Should Generate Sea On [ field's] Border is almost pure English. With pirates, you just need to write a little more in the method name, i.e.
- PirateShouldLostGoldIfHeKilled
- ActionSurrenderShouldSendPirateOnShip
In the case when we can call the test class from the word
When , this is a pure example on the Arrange Act Assert. For example:
- Arrange - when the playing field is created . In the initialization of the test class, you can prepare everything to create a test playing field.
- Act - the creation of the playing field . It can be used both in the test method itself and in the initialization of the test method.
- Arrange - check performance . Ideally, the test method can only consist of it.
Example:
[TestFixture] public class WhenCreateField { private Field field; private TestEmptyRules rule; [TestFixtureSetUp] public void ClassInit() {
All the preparation is done in the initialization of the test class, and then only the checks go.
One test - one test. This can be seen in the examples above. Immediately it is clear what is being tested and what should be the result. Often there is a great temptation to add Assert to an already existing test - this is called “add a bunny” here, and so “bunnies” can then get a bit worse because they will embarrass minds and steal extra time when raising tests after refactoring.
Further, the structuredness of the tests is very important. Compare:
[Test] public void HeMayKillFoes() { var airplaneCell = new AirplaneCell(4, 5); var player = Black.Pirate; var foe = Red.Pirate; airplaneCell.PirateComing(foe); airplaneCell.PirateComing(player); airplaneCell.Pirates.ShouldContain().Exact(player); } [Test] public void HeMayKillFoes() {
Despite the simplicity of the tests, and they should be simple, the first option, in my opinion, will take more time to realize where the preparations are underway, where the key method will be checked, and where the verification itself is. In the second variant, the look easily determines the boundaries of the components and the mind is quicker aware of key points. I guarantee that you yourself will then be easier to examine your tests and recall what exactly is being tested.
Another example:
[Test] public void AtSecondTimeHeCanNotTransferToShip() {
Key points are highlighted and the case for which the test is created is immediately clear. At corridor testing of this approach, it turned out that the Act is the most controversial point in writing tests. Different people often see differently what needs to be included in the test setup, and what is included in the action being tested. The same moment is mentioned in all articles devoted to AAA, the same answer is given, to which we arrived with our colleagues: distinguish between what you consider necessary and how you agree. Yes, thank you cap! There are no strict rules.
Now the key point, which probably already noticed the most attentive and writing tests comrades. In tests there is no explicitly
Assert construction.
Honestly, I like this approach much more, because the
first impulse is to write the property that needs to be tested . Then you already understand that you need to enter the desired Assert, which I used to hang abbreviations for InteliSense. For example, for Assert.AreEqual / Assert.That ($ actual $, Is.EqualTo ($ expected $)) was
aae , i.e. I typed this combination, pressed Tab and already have a pattern in the code. But this is inconvenient, it was necessary to configure ReSharper, remember that assert goes first.
It is much more convenient to use the potential of the language in terms of writing extension methods and using it. Thus, you will always have a hint for all developers, regardless of whether they use ReSharper or CodeAssist, or some other system.

In the illustration above you can see that the code is written on an intuitive, semantic level. The first impulse is to write the value for testing, then we think how and with what to compare it, and the last step is to record the desired value. Please note that IntelliSense suggests the type of expected value to check.

This illustration shows the standard approach to writing validation for nUnit. We must remember that the service code Assert.That comes first. IntelliSense does not always help when writing a value for verification. The illustration shows the real work without scrolling to any item in the drop-down list. After writing the value to check, again, you need to “remember” the service word, write the type of check and when entering the expected IntelliSense value is powerless.
Next, compare visually two approaches:
[Test] public void FieldShouldBeCreated() {
And another example:
[Test] public void MaxSizeShouldBeDefined() {
It seems to me that the first options, where extension methods are used, are more understandable and easy to read.
The main advantage is that you can clearly see what is and what is expected . Although nUnit is good in this respect, which somewhat levels the difference in approaches, compared to MSTest, this is heaven and earth. In general, MSTest does not understand for beginners where the expected result is, and where it is received.
Another advantage of writing extension methods for checking tests can be their logical structure, consonant with the domain model. It is best to demonstrate this again with an example:
[Test] public void ItShouldBeGrassByDefault() {
Which way is clearer?
I think it’s easy to develop your own test DSL for a specific use. But as an example (not yet polished use):
public static CollectionAssertCases ShouldContain(this IEnumerable enumerable) { return new CollectionAssertCases(enumerable); } public class CollectionAssertCases { private readonly IEnumerable enumerable; public List AsList { get { return new List(enumerable); } } public CollectionAssertCases(IEnumerable enumerable) { this.enumerable = enumerable; } public void Elements(params T[] elements) { Assert.That(enumerable, Is.EquivalentTo(elements)); } } public static void OnlyCellsOf(this CollectionAssertCases collection, CellType cellType) { Assert.IsTrue(collection.AsList.All(c => c.CellType == cellType)); }
Besides the fact that DSL for testing becomes more understandable and concise, it allows you to get rid of technically unimportant details. Reduce duplication of code and increase the speed of writing tests.
Another useful application can be found in technical code testing. For example, I do not want any class property to suddenly become writable, and I can write a test for it easily.
[Test] public void PiratesStateCanNotBeSetDirectly() {
The test is not cluttered with unnecessary methods and actions when working with reflection, but gives control over changing the API, if you work in a large or inexperienced team where it is not always possible to agree on one or another use of properties. It works the same way as “foolproof”. Agree that the test will not cause particularly vivid emotions, even among colleagues, ossified in their rejection, of reflection.
Again, following the results of corridor testing on colleagues, it was discovered that the power of habit is a great thing - to immediately read the tests and write them is somewhat unusual. The eyes are looking for a static class Assert and do not find what causes a slight bewilderment, while it turned out that the word Assert in the comments for some reason does not rush into the eyes. I think this will pass quickly, since the flexibility of the mind must be present to the developers though.
The advantages to the described approach include:- The process of writing a test in the course of human thought;
- Explicitly indicating the type of expected data in the prompt when creating an Assert expression;
- Readability test of the test;
- Readability tests in general.
By cons I would take:- Threshold of entry. Beginners will need to explain what exists in the DSL project for checks and the rules for building it, in order to find the necessary methods using IntelliSense.