Where did u go xUnit

    The idea of ​​this note - as a hypothesis - appeared a long time ago, and somehow it didn’t work out ... But “the other day” (by the time of publication — weeks already) I saw confirmation of my assumption of what is called “first-hand” (see Kent Beck's answer to Unit Testing: Did the notion of using setup () and teardown () methods in test fixtures originate from JUnit? ) and decided to realize this idea.

    This is not about TDD itself, but rather about the first steps in this direction. But, I think, knowledge of the origins and understanding of the logic of the creators is an important point in mastering what has grown in the field sown by them ... And in this case, too.

    So ... A long time ago, a certain Kent by the name of Beck with his friend WardWe were programming in the Smalltalk environment. I don’t know exactly what tasks they solved - and this is not so important - but they did it in such a way that you can indulge today (with very rare exceptions) only in environments of this family. The fact is that in Smalltalk there is absolutely no gap between writing a program and its execution. And therefore, you can come up with code on the go, write and execute it right there. Moreover, you can perform, however strange it may sound, even before writing ... And this is not a fairy tale - I can show how it looks in practice.

    The first Smalltalk tool we need is called Workspace. By and large, this is a primitive (if not stronger) text editor. The only thing Workspace stands out in a long line of text editors is the ability to execute what is written. (A similar tool is, for example, in Еlipse, it is called Display. It differs, apart from all the little things, for the worse, the inability to execute code without a running program, which, however, is not the fault of this tool, but rather, the trouble of all systems with a “crooked “static” typing.) This is how Workspace looks in Smalltalk:

    Workspace
    As you can see from the context menu, you can simply execute a line (or selected text), print the result, or look at it in one of two inspectors available in this Smalltalk environment etc.

    “All this is wonderful, but how is this related to unit tests?” An impatient reader will ask very reasonably. To answer, consider some simple task. Suppose, for example, we want to make a partner for the game “Bulls and Cows” from our Smalltalk environment . Let us leave aside the excesses in the form of a specialized graphical interface and try to do this in the simplest way possible. The same Workspace is quite suitable for this: we ask the system to first create a game object, then send it messages with our guessing option, and the game returns a hint (the number of bulls and cows) ... for example, in the form of a point: for example, 2 @ 3(a 2message is sent to the object @with the parameter 3- as a result, we get an instance of the class Point, wherex = 2, y = 3) will mean two bulls and three cows; the answer 4 @ 0means that the key is unraveled.

    The general plan is ready, we proceed to its implementation. You can develop it in the way that is usually done: by creating classes, methods in them, etc. But you can do


    something else - just start playing: We expect that in response to this, the system will create an object of the class we need. To verify this, we can inspect the resulting object. We select the Inspect It ... menu item and we get a system warning: it does not know what should be understood by the name BullsAndCows.

    Missing class notification
    In general, Smalltalk is very friendly towards its user. For example, in this situation, this manifests itself in the fact that the process of compiling the code (namely, at this stage we have now stopped) when there is a misunderstanding (the language does not turn to call it an error) does not end. The system only pauses the process, offering the user ways to resolve the problem. In this case, we are interested in creating a new class (“define new class”).


    In the proposed “template” (which is actually an expression in the Smalltalk language that provides for the creation of a new class), it is only desirable to indicate the category (package) into which the created class will be placed - let's call it unpretentious: "BullsAndCows."


    Click OK ... and we see the inspector window that opens with the created instance of the game.


    We got what we wanted. The next step: we inform the game of our option.

    game guess: ???

    Here we have to think: what is the best idea to imagine? Most likely, the same as the key ... but we haven’t remembered the key yet ... I have three options "automatically" born: 1) create a special class for guessing, or 2) use a number, or 3) use a string. To choose, I have to think ... I stop my choice on the line, because (looking ahead) I understand that in the future I will have to match the key and the guess, character by character, and the line is an indexed collection of characters.


    The execution of the second line leads to an error.


    According to the message in the window title, we see that the BullsAndCows class instance simply does not know how to react to the received message. Under the heading, there are possible options for further actions: continue (ignoring the error), interrupt, debug, or create. As you can see, interruption of execution is far from the only option in this case. You can continue, but it will not lead to anything - the method itself will not appear. Debugging in this case also makes no sense - everything is clear: you need to create the appropriate method.


    The system is interested in which class to create this method (in BullsAndCows itself or somewhere higher in the hierarchy?). We need the first option. Since in Smalltalk it is customary to structure a set of methods of an object, relating methods to different categories, the system will offer to do this.


    Among the proposed standard categories, I liked testing. The method is created and opened in the debugger for editing.


    Note that the exception did not “unwind” the call stack (it is shown at the top of the window) and we can continue to run the program as soon as we want. But first, let's define an implementation for #guess :. But for this we will have to make a decision on the question from which, in fact, it was worth starting: what should our game answer in this particular case? I propose to pretend that the user did not guess anything at all: we will return him “0 bulls and 0 cows”. We implement it as simple as possible (Fake It):


    In order for the changes in the method code to be compiled, they must be “accepted” (Accept). After that, click the Proceed button to continue the execution of our game session ... And we get the expected result.


    I hope that here the principle has already become clear ... At the following iterations, putting forward increasingly stringent requirements for the behavior of our system, we will gradually develop it: change (complicate) the created methods, add new ones if necessary, introduce new classes, etc. But each step will begin with the fact that we think over the correct behavior of the currently developing part of the system, highlight its “signs” - the expected results - and those actions that must be performed to obtain these expected results. Next, we simply record these actions and begin the execution of the resulting program, without worrying about the presence of even the basic infrastructure for its operation. By any means, we make the written program work the way we want. If necessary, improve it.

    Before SUnit, a short step remained: to keep the “expected result” every time is not profitable - the already limited resources of human intelligence are wasted in vain. There is a natural desire to write them somewhere, from which the idea arises that you can write right here - in Workspace. This also adds the desire to automate the process of comparing the results with the expected ones, to keep all the already worked out options for using the system with all the checks, and to use them in the future to exclude regression ... The requirements for the framework are almost ready. Next, we will implement them in the simplest way - this is what Kent Beck writes (see the source link above):

    “When I started designing the first version of xUnit, I used one of my usual tricks: turn something into objects; in this case, the entire Workspace turned into a class. Each fragment would then be represented by a method (with the prefix “test” as a primitive annotation). ”

    ... Then follows one of the most important discoveries in the field of software development, which at this point already lies on the surface: the process of formalizing requirements and obtaining the initial system design from them practically coincides with the process of writing automatic tests. And this is the basis of TDD: it remains only to generalize the experience gained, to streamline the practice of writing control tests before implementing the functional, analyze your experience and make sure that there are several basic template techniques ... and the TDD methodology in its “classical” form is ready.

    ***

    Instead of a conclusion - a little criticism. I don’t really like architecture received by Beck and later replicated everywhere. Instead of test methods, it would be convenient to have tests in the form of separate objects, necessary interconnected and managed by the appropriate IDE tools. This approach can be as natural and convenient as possible in a dynamic, living environment such as Smalltalk. ... In general, this is a topic for individual articles - with preliminary research and development. And the starting point for them is the conclusion that the widely used xUnit and its clones are only the first approximation to solving the problem of using tests to develop software systems - so what happens?

    Also popular now: