Chapter 10. Behavioral Testing

In Chapter 7, Testing Web Applications, you learned how to write unit tests in order to test small pieces of code in an isolated way. Even though this is a must, it is not enough alone to make sure your application works as it should. The scope of your test could be so small that even though the algorithm that you test makes sense, it would not be what the business asked you to create.

Acceptance tests were born in order to add this level of security to the business side, complementing the already existing unit tests. In the same way, BDD originated from TDD in order to write code based on these acceptance tests in an attempt to involve business and managers in the development process. As PHP is one of the favorite languages of web developers, it is just natural to find powerful tools to implement BDD in your projects. You will be positively surprised by what you can do with Behat and Mink, the two most popular BDD frameworks at the moment.

In this chapter, you will learn about:

  • Acceptance tests and BDD
  • Writing features with Gherkin
  • Implementing and running tests with Behat
  • Writing tests against browsers with Mink

Behavior-driven development

We already exposed in Chapter 7, Testing Web Applications, the different tools we can use in order to make our applications bug-free, such as automated tests. We described what unit tests are and how they can help us achieve our goals, but this is far from enough. In this section, we will describe the process of creating a real-world application, how unit tests are not enough, and what other techniques we can include in this life cycle in order to succeed in our task—in this case, behavioral tests.

Introducing continuous integration

There is a huge difference between developing a small web application by yourself and being part of a big team of developers, managers, marketing people, and so on, that works around the same big web application. Working on an application used by thousands or millions of users has a clear risk: if you mess it up, there will be a huge number of unhappy affected users, which may translate into sales going down, partnerships terminated, and so on.

From this scenario, you can imagine that people would be scared when they have to change anything in production. Before doing so, they will make sure that everything works perfectly fine. For this reason, there is always a heavy process around all the changes affecting a web application in production, including loads of tests of all kinds.

Some think that by reducing the number of times they deploy to production, they can reduce the risk of failure, which ends up with them having releases every several months with an uncountable number of changes.

Now, imagine releasing the result of two or three months of code changes at once and something mysteriously fails in production: do you know where to even start looking for the cause of the problem? What if your team is good enough to make perfect releases, but the end result is not what the market needs? You might end up wasting months of work!

Even though there are different approaches and not all companies use them, let's try to describe one of the most famous ones from the last few years: continuous integration (CI). The idea is to integrate small pieces of work often rather than big ones every once in a while. Of course, releasing is still a constraint in your system, which means that it takes a lot of time and resources. CI tries to automatize this process as much as possible, reducing the amount of time and resources that you need to invest. There are huge benefits with this approach, which are as follows:

  • Releases do not take forever to be done, and there isn't an entire team focused on releasing as this is done automatically.
  • You can release changes one by one as they come. If something fails, you know exactly what the change was and where to start looking for the error. You can even revert the changes easily if you need to.
  • As you release so often, you can get quick feedback from everyone. You will be able to change your plans in time if you need to instead of waiting for months to get any feedback and wasting all the effort you put on this release.

The idea seems perfect, but how do we implement it? First, let's focus on the manual part of the process: developing the features using a version control system (VCS). The following diagram shows a very common approach:

Introducing continuous integration

As we already mentioned, a VCS allows developers to work on the same codebase, tracking all the changes that everyone makes and helping on the resolution of conflicts. A VCS usually allows you to have different branches; that is, you can diverge from the main line of development and continue to do work without messing with it. The previous graph shows you how to use branches to write new features and can be explained as follows:

  • A: A team needs to start working on feature A. They create a new branch from the master, in which they will add all the changes for this feature.
  • B: A different team also needs to start working on a feature. They create a new branch from master, same as before. At this point, they are not aware of what the first team is doing as they do it on their own branch.
  • C: The second team finishes their job. No one else changed master, so they can merge their changes straight away. At this point, the CI process will start the release process.
  • D: The first team finishes the feature. In order to merge it to master, they need to first rebase their branch with the new changes of master and solve any conflicts that might take place. The older the branch is the more chances of getting conflicts you will have, so you can imagine that smaller and faster features are preferred.

Now, let's take a look at how the automated side of the process looks. The following graph shows you all the steps from the merging into master to production deployment:

Introducing continuous integration

Until you merge your code into master, you are in the development environment. The CI tool will listen to all the changes on the master branch of your project, and for each of them, it will trigger a job. This job will take care of building the project if necessary and then run all the tests. If there is any error or test failure, it will let everyone now, and the team that triggered this job should take care of fixing it. The master branch is considered unstable at this point.

If all tests pass, the CI tool will deploy your code into staging. Staging is an environment that emulates production as much as possible; that is, it has the same server configuration, database structure, and so on. Once the application is here, you can run all the tests that you need until you are confident to continue the deployment to production. As you make small changes, you do not need to manually test absolutely everything. Instead, you can test your changes and the main use cases of your application.

Unit tests versus acceptance tests

We said that the goal of CI is to have a process as automatized as possible. However, we still need to manually test the application in staging, right? Acceptance tests to the rescue!

Writing unit tests is nice and a must, but they test only small pieces of code in an isolated way. Even if your entire unit tests suite passes, you cannot be sure that your application works at all as you might not integrate all the parts properly because you are missing functionalities or the functionalities that you built were not what the business needed. Acceptance tests test the entire flow of a specific use case.

If your application is a website, acceptance tests will probably launch a browser and emulate user actions, such as clicking and typing, in order to assert that the page returns what is expected. Yes, from a few lines of code, you can execute all the tests that were previously manual in an automated way.

Now, imagine that you wrote acceptance tests for all the features of your application. Once the code is in staging, the CI tool can automatically run all of these tests and make sure that the new code does not break any existing functionality. You can even run them using as many different browsers as you need to make sure that your application works fine in all of them. If a test fails, the CI tool will notify the team responsible, and they will have to fix it. If all the tests pass, the CI tool can automatically deploy your code into production.

Why do we need to write unit tests then, if acceptance tests test what the business really cares about? There are several reasons to keep both acceptance and unit tests; in fact, you should have way more unit tests than acceptance tests.

  • Unit tests check small pieces of code, which make them orders-of-magnitude faster than acceptance tests, which test the whole flow against a browser. That means that you can run all your unit tests in a few seconds or minutes, but it will take much longer to run all your acceptance tests.
  • Writing acceptance tests that cover absolutely all the possible combinations of use cases is virtually impossible. Writing unit tests that cover a high percentage of use cases for a given method or piece of code is rather easy. You should have loads of unit tests testing as many edge cases as possible but only some acceptance tests testing the main use cases.

When should you run each type of test then? As unit tests are faster, they should be executed during the first stages of deployment. Only once we know that they all have passed do we want to spend time deploying to staging and running acceptance tests.

TDD versus BDD

In Chapter 7, Testing Web Applications, you learned that TDD or test-driven development is the practice of writing first the unit tests and then the code in an attempt to write testable and cleaner code and to make sure that your test suite is always up to date. With the appearance of acceptance tests, TDD evolved to BDD or behavior-driven development.

BDD is quite similar to TDD, in that you should write the tests first and then the code that makes these tests pass. The only difference is that with BDD, we write tests that specify the desired behavior of the code, which can be translated to acceptance tests. Even though it will always depend on the situation, you should write acceptance tests that test a very specific part of the application rather than long use cases that contain several steps. With BDD, as with TDD, you want to get quick feedback, and if you write a broad test, you will have to write a lot of code in order to make it pass, which is not the goal that BDD wants to achieve.

Business writing tests

The whole point of acceptance tests and BDD is to make sure that your application works as expected, not only your code. Acceptance tests, then, should not be written by developers but by the business itself. Of course, you cannot expect that managers and executives will learn how to code in order to create acceptance tests, but there is a bunch of tools that allow you to translate plain English instructions or behavioral specifications into acceptance tests' code. Of course, these instructions have to follow some patterns. Behavioral specifications have the following parts:

  • A title, which describes briefly, but in a very clear way, what use case the behavioral specification covers.
  • A narrative, which specifies who performs the test, what the business value is, and what the expected outcome is. Usually the format of the narrative is the following:
    In order to <business value>
    As a <stakeholder>
    I want to <expected outcome>
  • A set of scenarios, which is a description and a set of steps of each specific use case that we want to cover. Each scenario has a description and a list of instructions in the Given-When-Then format; we will discuss more on this in the next section. A common patterns is:
    Scenario: <short description>
    Given <set up scenario>
    When <steps to take>
    Then <expected outcome>

In the next two sections, we will discover two tools in PHP that you can use in order to understand behavioral scenarios and run them as acceptance tests.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.4.179