Not all customers fully understand what “development” stands for, and even fewer know what testing comprises. And when it comes to code… well, let’s just say that rare is the client who can say what’s required for a developer’s code to be testable.
Developer and tester. Two different professions, two different roles on the team, yet working closely together. So how can a developer make the tester’s job easier? And what does it mean to code according to “best practices”?
For the answers, we’ll play the role of a “non tech‐savvy” customer and ask our PHP web development team leader, Valery Makarenko, several “development 101” questions.
— Valery, what does testing mean from your (a developer’s) point of view? How do you make your code more “testable”?
For developers, it is convenient to classify tests into two groups: “unit tests” and “functional tests”. “Unit tests” evaluate software from the inside, and “functional tests” approach software from the outside.
With these terms defined, let’s look closer at both. Unit tests, as evidenced by their name, are meant for testing individual “units” of software, like classes and libraries. Unit tests demand that a code have as few dependencies as possible in order to be successful. This allows us to eliminate single units for separate testing. For example, if the source code for a class method is connected to a database, we’d say this code is “highly dependent” because it can’t be tested with the database attached. In order to render the code testable, we’d need to extract the database connection to a separate object. When we replace the connection with a “mock object”, we can easily test the class method’s performance.
Another example is testing the behavior of an “event listener” (design pattern: observer). Two separate mechanisms exist — one for creating events, one for creating subscribers. This separation makes the code more testable.
On our PHP team, unit testing is performed within a PHPUnit framework. Here’s a coding example:
Now let’s discuss functional tests, which are used to test an entire system’s functionality. Functional tests are executed from “outside” the software, meaning that the tester doesn’t know the application’s internal structure. For this reason, functional testing is also known as “black‐box testing”. Whichever name you prefer, these tests allow for the end‐user’s behavior, or the processing of external API calls, to be imitated.
For example, let’s pretend we’re creating test cases and executing them during the functional testing of software that works with an API. According to our requirements, working with an API is only allowed in the presence of a valid key — so our test will imitate an API request using an invalid key, and confirm whether the API correctly returns an error message.
The example above is written in Gherkin, a domain‐specific language (DSL) that lets you describe programming tasks in terms of business requirements. The tests themselves are performed within a Behat framework for PHP, which is intended for functional testing.
— How is debugging approached?
Every successful debugging has three main parts:
- Perform testing (either manual or automated) to identify errors during development.
- Set up logging for all of the application’s main functions, not just error‐logging. It is much easier to find and eliminate a problem in the working system when you know which parts of the system were working the moment an error occurred and which parts were not in use.
- The debugging itself. Any good debugging incorporates tools called “debuggers”. These not‐so‐exotically‐named tools are usually included in an integrated development environment (IDE). A debugger allows a tester to 1) see the state of a program at any specific point of the source code, 2) print or set any value of a variable, 3) execute a function, and 4) show the result immediately during runtime.
Here’s a screenshot showing part of the debugging process:
— What do you consider “best practices”? How do you decide whether to follow them or not?
In the development process, the concept of “best practices” includes programming‐related practices and management‐related/organizational ones. Both types are invaluable in any technology.
With this distinction in mind, “organizational best practices” include:
- Following the proper coding conventions, including comments and naming guidelines, and following the correct coding standards (for example, the “PSR‐standard family” when using PHP)
- Creating tests before writing the code (test‐driven development vs. behavior‐driven development)
- Implementing continuous delivery (i.e. by using Jenkins CI or Capifony)
- Obligatory usage of version control systems (Git)
- Conducting regular code reviews
“Programming best practices” refer to:
- Following the principles of SOLID design
- Adhering to software design patterns (for example, MVC)
- Obligatory usage of exceptions and their processing
- Ensuring that functions and class methods always return a result (or an exception)
- Performing recurrent code refactoring
When considering whether you should follow/not follow certain guidelines, let’s use database design as an example. When designing a relational database, you’re supposed to follow the principles of database normalization. However, in some cases the most “correctly” built database works slowly — and in order to optimize the work speed, you have to break some standard database rules. For example, you might store calculated data instead of calculating it on request.
We hope this blog has adequately converted our nonsensical, tech‐speak gibberish into easily‐understandable English that explains some of the terms used by our developers. Now, what other development terms and processes have you always wanted to know about but been afraid to ask? Contact us today and we’ll happily clue you in!