Test-driven development techniques are built around how nature works, and variation testing is a natural next step in the evolution of DevOps.

Ants and a leaf making the word “open”

In “Glitches are a feature of airtight development operations,” I discussed the important role that glitches play in soliciting feedback to deliver quality products. Agile DevOps teams use failures to guide them and drive development. Test-driven development (TDD) is a prerequisite for any Agile DevOps team to evaluate product delivery. The failure-centric TDD approach is only effective when used in conjunction with quantifiable testing.

TDD is modeled after how nature works and how nature produces winners and losers in evolutionary games.

Natural selection

Charles Darwin

In 1859, Charles Darwin proposed the theory of evolution in his book On the Origin of Species. Darwin’s argument was that natural variation is caused by a combination of spontaneous mutations in individual organisms and environmental pressures. Environmental pressures weed out the less adaptable organisms in favor of the development of others that are more adaptable. Every organism has mutations in its chromosomes, and these spontaneous mutations are passed on to the next generation. It then tests the emerging variability under natural selection – the variability of the environmental conditions that cause the existing stresses.

This diagram illustrates the process of adjusting to environmental conditions.

Environmental pressures on fish

FIG. 1. Different environmental pressures lead to different results under natural selection. Image screenshot fromA video of Richard Dawkins.

The picture shows a school of fish living in their habitat. The habitat varies (the gravel on the seafloor or bottom of the river bed is dark and light in color), and each fish is different in size (the body pattern and color vary from dark to light).

The graph also shows two cases (i.e., two variations of environmental pressure) :

  1. Predator presence
  2. The predator is not present

In the first case, the fish that stood out easily against the color of the gravel were at higher risk of being caught by predators. When the gravel was darker, there were fewer light-colored fish. And vice versa, when the gravel was lighter, there were fewer dark-colored fish.

In the second case, the fish are completely relaxed and mating. In the absence of predators and in the absence of mating rituals, the opposite result was expected: fish that stood out in the gravel background had a greater chance of being selected to mate and pass on their traits to their offspring.

Selection criteria

Variability is by no means arbitrary, capricious, whimsical or random in its selection. The determining factors in the selection process are usually measurable. This determining factor is often referred to as a test or goal.

A simple mathematical example illustrates this decision making process. (In this case, the choice is not determined by natural selection, but by human selection.) Suppose you were asked to build a small function that would take a positive number and then compute the square root of that number. What will you do?

The agile DevOps team’s approach is to validate failures quickly. Be humble and start by admitting that you don’t really know how to develop the function. At this point, all you know is how to describe what you want to do. Technically, you are ready for unit testing.

“Unit Tests” describe what specific results you want to achieve. It can be simply stated as “Given the number 16, I want the square root function to return the number 4”. You probably know that the square root of 16 is 4. However, you don’t know the square root of some larger numbers, such as 533.

But at the very least, you’ve set your selection criteria, your tests or your expectations.

Failure testing

The.NET Core platform can demonstrate this test. .NET typically uses xUnit.net as a unit testing framework. (To follow along with this code sample, install.NET Core and xUnit.net.)

Open the command line and create a folder in which to implement the square root solution. For example, enter:

mkdir square_root
Copy the code

Input:

cd square_root
Copy the code

Create a separate folder for unit tests:

mkdir unit_tests
Copy the code

Go to unit_tests (CD unit_tests) and initialize the xUnit framework:

dotnet new xunit
Copy the code

Now, go to square_root and create the app folder:

mkdir app
cd app
Copy the code

If necessary, create a scaffold for your code:

dotnet new classlib
Copy the code

Now open your favorite editor and start coding!

In your code editor, navigate to the unit_tests folder and open unittest1.cs.

Replace the automatically generated code in UnitTest1.cs with:

using System;
using Xunit;
using app;

namespace unit_tests{

   public class UnitTest1{
       Calculator calculator = new Calculator();

       [Fact]
       public void GivenPositiveNumberCalculateSquareRoot(){ var expected = 4; var actual = calculator.CalculateSquareRoot(16); Assert.Equal(expected, actual); }}}Copy the code

The unit test describes that the expected value of the variable should be 4. The next line describes the actual values. It is recommended to calculate the actual values by sending the input values to a component called Calculator. The component is described as processing CalculateSquareRoot information by receiving numeric values. This component has not yet been developed. But that doesn’t matter, we’re just describing the expected value here.

Finally, it describes what happens when a message is triggered to send. In this case, you have to determine whether the expected value is equal to the actual value. If so, the test passes and the goal is achieved. If the expected value is not equal to the actual value, the test fails.

Next, to implement the component called Calculator, create a new file in the APP folder and name it Calculator.cs. To implement a function that calculates square roots, add the following code to this new file:

namespace app {
   public class Calculator {
       public double CalculateSquareRoot(double number) {
           double bestGuess = number;
           returnbestGuess; }}}Copy the code

Before testing, you need to tell the unit test how to find the new component. Navigate to the unit_tests folder and open the unit_tests.csproj file. Add the following code to the

code block:

<ProjectReference Include=".. /app/app.csproj" />
Copy the code

Save the unit_test.csproj file. You are now ready to run your first test.

Switch to the command line and go to the unit_tests folder. Run the following command:

dotnet test
Copy the code

Running the unit test outputs the following:

xUnit output after the unit test run fails

Figure 2. XUnit output after a unit test failure

As you can see, the unit test failed. Sending the number 16 to the Calculator component was expected to output the number 4, but the output (Actual) was 16.

Congratulations to you! The first fault is created. Unit tests provide you with a powerful feedback mechanism that urges you to fix bugs.

Repair the fault

To fix the bug, you have to improve bestGuess. At the moment, bestGuess just takes the number received by the function and returns it. That’s not good enough.

But how do you find a way to calculate the square root value? I have an idea — look at how Mother Nature solves a problem.

Iterating like nature

It is very difficult (almost impossible) to get the right value on the first (and only) attempt. You must allow yourself to make multiple guesses to increase your chances of solving the problem. One way to allow multiple attempts is to iterate.

To iterate, store the bestGuess value in the previousGuess variable, transform the bestGuess value, and then compare the difference between the two values. If the difference is 0, the problem is resolved. Otherwise, continue iterating.

This is the body of the function that generates the square root of any positive number:

double bestGuess = number;
double previousGuess;

do {
   previousGuess = bestGuess;
   bestGuess = (previousGuess + (number/previousGuess))/2;
} while((bestGuess - previousGuess) ! = 0);return bestGuess;
Copy the code

This loop (iteration) aggregates bestGuess values into the envisioned solution. Now, your well-designed unit tests have passed!

Unit test successful

Figure 3. The unit test passed.

Iteration solves the problem

As Mother Nature solves a problem, in this exercise iteration solves the problem. An incremental approach combined with incremental improvement is an effective way to achieve a satisfactory solution. The deciding factor in this example is having measurable goals and tests. Once you have this, you can continue iterating until you reach your goal.

The key point!

Ok, this was an interesting experiment, but the more interesting findings came from using this newly created solution. So far, bestGuess has always taken the number received by the function as an input parameter. What happens if you change the initial value of bestGuess?

To test this, you can test several scenarios. First, progressively refine the observations as iterations attempt to compute the square root of 25 many times:

Code iterating for the square root of 25

Figure 4. Calculate the square root of 25 by iteration.

With 25 as the initial value for bestGuess, the function takes eight iterations to compute the square root of 25. But what if you make a ridiculous mistake in designing the bestGuess initialvalues? Try it a second time. Could that million be the square root of 25? What happens in the case of this obvious error? Can you write a function that can handle such naive errors?

Come directly. Back to the test, this time starting with a million:

Stepwise refinement

Figure 5. In calculating the square root of 25, stepwise refinement is used to start bestGuess with 1 million.

A: wow! Starting with a ridiculous number, the number of iterations only tripled (from eight to 23). It’s not growing as much as your gut would expect.

Moral of the story

Aha! When you realize that iteration is not only guaranteed to solve the problem, it also has nothing to do with whether your solution’s initial guess is good or bad. No matter how wrong your initial understanding, an iterative process and measurable tests/goals can put you on the right path to a solution.

Figures 4 and 5 show steep and dramatic burn-downs. A very false start, and the iteration soon produces an absolutely correct solution.

In short, this magical approach is the essence of Agile DevOps.

Back to some deeper observations

The practice of Agile DevOps stems from people’s understanding of the world they live in. The world we live in is uncertain, incomplete and riddled with too much confusion. From a scientific/philosophical point of view, these characteristics lead to Heisenberg’s Uncertainty Principle, Wittgenstein’s Tractatus logico-philosophicus, 2. Yan Yan yan yan yan yan yan Yan Yan yan yan yan yan yan yan yan yan yan yan yan yan yan yan yan

In short, no matter how hard you try, you will never get complete information when trying to solve any problem. Therefore, it would be more helpful for us to drop our arrogance and adopt a more modest approach to solving problems. Humility will give you a huge payoff, not just as a solution, but as a by-product.

conclusion

Nature is constantly at work. It’s an ongoing process. Nature has no master plan. Everything is a response to what happened before. The feedback loop is very tight and significant progress/regression is achieved gradually. Everywhere in nature, everything is being perfected in one form or more.

Agile DevOps is a very interesting result of the maturing of the engineering model. DevOps is based on the realization that the information you have is always incomplete, so you’d better proceed with caution. Get measurable tests (e.g., hypotheses, measurable expected results), make simple attempts that may fail most of the time, then gather feedback, fix the bug, and continue testing. There is no other way but to agree that every step must have measurable assumptions/tests.

In the next article in this series, I’ll take a closer look at how mutation testing can provide timely feedback to drive results.


Via: opensource.com/article/19/…

By Alex Bunardzic (lujun9972

This article is originally compiled by LCTT and released in Linux China